mcp-security-audit

Par github · awesome-copilot

Auditer les configurations de serveurs MCP (Model Context Protocol) pour détecter des problèmes de sécurité. Utilise cette skill dans les cas suivants : - Revue des fichiers `.mcp.json` pour identifier des risques de sécurité - Vérification des args de serveurs MCP pour détecter des secrets codés en dur ou des patterns d'injection shell - Validation que les serveurs MCP utilisent des versions épinglées (pas `@latest`) - Détection de dépendances non épinglées dans les configurations de serveurs MCP - Audit des serveurs MCP enregistrés par un projet et vérification qu'ils figurent sur une liste approuvée - Vérification de l'usage de variables d'environnement versus des credentials codés en dur dans les configs MCP - Toute demande du type « ma config MCP est-elle sécurisée ? », « audite mes serveurs MCP », ou « vérifie le fichier .mcp.json » keywords: [mcp, security, audit, secrets, shell-injection, supply-chain, governance]

npx skills add https://github.com/github/awesome-copilot --skill mcp-security-audit

Audit de Sécurité MCP

Auditez les configurations de serveurs MCP pour les problèmes de sécurité — exposition de secrets, injection shell, dépendances non épinglées et serveurs non approuvés.

Overview

Les serveurs MCP donnent aux agents un accès direct aux outils des systèmes externes. Un .mcp.json mal configuré peut exposer des identifiants, permettre une injection shell ou se connecter à des serveurs non fiables. Cette skill détecte ces problèmes avant qu'ils ne se retrouvent en production.

.mcp.json → Parser les serveurs → Vérifier chaque serveur :
  1. Secrets dans args/env ?
  2. Modèles d'injection shell ?
  3. Versions non épinglées (@latest) ?
  4. Commandes dangereuses (eval, bash -c) ?
  5. Serveur sur la liste approuvée ?
→ Générer un rapport

Quand l'utiliser

  • Lors de la révision d'un fichier .mcp.json dans un projet
  • Lors de l'intégration d'un nouveau serveur MCP à un projet
  • Lors de l'audit de tous les serveurs MCP dans un monorepo ou une place de marché de plugins
  • Vérifications pré-commit pour les modifications de configuration MCP
  • Révision de sécurité des configurations des outils des agents

Vérification d'audit 1 : Secrets codés en dur

Scannez les valeurs args et env des serveurs MCP pour les identifiants codés en dur.

import json
import re
from pathlib import Path

SECRET_PATTERNS = [
    (r'(?i)(api[_-]?key|token|secret|password|credential)\s*[:=]\s*["\'][^"\']{8,}', "Hardcoded secret"),
    (r'(?i)Bearer\s+[A-Za-z0-9\-._~+/]+=*', "Hardcoded bearer token"),
    (r'(?i)(ghp_|gho_|ghu_|ghs_|ghr_)[A-Za-z0-9]{30,}', "GitHub token"),
    (r'sk-[A-Za-z0-9]{20,}', "OpenAI API key"),
    (r'AKIA[0-9A-Z]{16}', "AWS access key"),
    (r'-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----', "Private key"),
]

def check_secrets(mcp_config: dict) -> list[dict]:
    """Check for hardcoded secrets in MCP server configurations."""
    findings = []
    raw = json.dumps(mcp_config)
    for pattern, description in SECRET_PATTERNS:
        matches = re.findall(pattern, raw)
        if matches:
            findings.append({
                "severity": "CRITICAL",
                "check": "hardcoded-secret",
                "message": f"{description} found in MCP configuration",
                "evidence": f"Pattern matched: {pattern}",
                "fix": "Use environment variable references: ${ENV_VAR_NAME}"
            })
    return findings

Bonne pratique — utiliser des références de variables d'environnement :

{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["server.js"],
      "env": {
        "API_KEY": "${MY_API_KEY}",
        "DB_URL": "${DATABASE_URL}"
      }
    }
  }
}

Mauvais — identifiants codés en dur :

{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["server.js", "--api-key", "sk-abc123realkey456"],
      "env": {
        "DB_URL": "postgresql://admin:password123@prod-db:5432/main"
      }
    }
  }
}

Vérification d'audit 2 : Modèles d'injection shell

Détectez les modèles de commande dangéreux dans les args des serveurs MCP.

import json
import re

DANGEROUS_PATTERNS = [
    (r'\$\(', "Command substitution $(...)"),
    (r'`[^`]+`', "Backtick command substitution"),
    (r';\s*\w', "Command chaining with semicolon"),
    (r'\|\s*\w', "Pipe to another command"),
    (r'&&\s*\w', "Command chaining with &&"),
    (r'\|\|\s*\w', "Command chaining with ||"),
    (r'(?i)eval\s', "eval usage"),
    (r'(?i)bash\s+-c\s', "bash -c execution"),
    (r'(?i)sh\s+-c\s', "sh -c execution"),
    (r'>\s*/dev/tcp/', "TCP redirect (reverse shell pattern)"),
    (r'curl\s+.*\|\s*(ba)?sh', "curl pipe to shell"),
]

def check_shell_injection(server_config: dict) -> list[dict]:
    """Check MCP server args for shell injection risks."""
    findings = []
    args_text = json.dumps(server_config.get("args", []))
    for pattern, description in DANGEROUS_PATTERNS:
        if re.search(pattern, args_text):
            findings.append({
                "severity": "HIGH",
                "check": "shell-injection",
                "message": f"Dangerous pattern in MCP server args: {description}",
                "fix": "Use direct command execution, not shell interpolation"
            })
    return findings

Vérification d'audit 3 : Dépendances non épinglées

Signalez les serveurs MCP utilisant @latest dans leurs références de packages.

def check_pinned_versions(server_config: dict) -> list[dict]:
    """Check that MCP server dependencies use pinned versions, not @latest."""
    findings = []
    args = server_config.get("args", [])
    for arg in args:
        if isinstance(arg, str):
            if "@latest" in arg:
                findings.append({
                    "severity": "MEDIUM",
                    "check": "unpinned-dependency",
                    "message": f"Unpinned dependency: {arg}",
                    "fix": f"Pin to specific version: {arg.replace('@latest', '@1.2.3')}"
                })
            # npx with unversioned package
            if arg.startswith("-y") or (not "@" in arg and not arg.startswith("-")):
                pass  # npx flag or plain arg, ok
    # Check if using npx without -y (interactive prompt in CI)
    command = server_config.get("command", "")
    if command == "npx" and "-y" not in args:
        findings.append({
            "severity": "LOW",
            "check": "npx-interactive",
            "message": "npx without -y flag may prompt interactively in CI",
            "fix": "Add -y flag: npx -y package-name"
        })
    return findings

Bon — version épinglée :

{ "args": ["-y", "my-mcp-server@2.1.0"] }

Mauvais — non épinglée :

{ "args": ["-y", "my-mcp-server@latest"] }

Vérification d'audit 4 : Exécuteur d'audit complet

Combinez tous les vérifications dans un seul audit.

def audit_mcp_config(mcp_path: str) -> dict:
    """Run full security audit on an .mcp.json file."""
    path = Path(mcp_path)
    if not path.exists():
        return {"error": f"{mcp_path} not found"}

    config = json.loads(path.read_text(encoding="utf-8"))
    servers = config.get("mcpServers", {})
    results = {"file": str(path), "servers": {}, "summary": {}}
    total_findings = []

    # Run secrets check once on the whole config (not per-server)
    config_level_findings = check_secrets(config)
    total_findings.extend(config_level_findings)

    for name, server_config in servers.items():
        if not isinstance(server_config, dict):
            continue
        findings = []
        findings.extend(check_shell_injection(server_config))
        findings.extend(check_pinned_versions(server_config))
        results["servers"][name] = {
            "command": server_config.get("command", ""),
            "findings": findings,
        }
        total_findings.extend(findings)

    # Summary
    by_severity = {}
    for f in total_findings:
        sev = f["severity"]
        by_severity[sev] = by_severity.get(sev, 0) + 1

    results["summary"] = {
        "total_servers": len(servers),
        "total_findings": len(total_findings),
        "by_severity": by_severity,
        "passed": len(total_findings) == 0,
    }
    return results

Utilisation :

results = audit_mcp_config(".mcp.json")
if not results["summary"]["passed"]:
    for server, data in results["servers"].items():
        for finding in data["findings"]:
            print(f"[{finding['severity']}] {server}: {finding['message']}")
            print(f"  Fix: {finding['fix']}")

Format de sortie

MCP Security Audit — .mcp.json
═══════════════════════════════
Servers scanned: 5
Findings: 3 (1 CRITICAL, 1 HIGH, 1 MEDIUM)

[CRITICAL] my-api-server: Hardcoded secret found in MCP configuration
  Fix: Use environment variable references: ${ENV_VAR_NAME}

[HIGH] data-processor: Dangerous pattern in MCP server args: bash -c execution
  Fix: Use direct command execution, not shell interpolation

[MEDIUM] analytics: Unpinned dependency: analytics-mcp@latest
  Fix: Pin to specific version: analytics-mcp@2.1.0

Ressources connexes

Skills similaires