# mcpaudit

**Static security scanner for MCP servers** -- zero dependencies, AST-based, 20 rules, <200ms.

`python 3.11+` | `zero dependencies` | `MIT license`

---

## Quick Start

```bash
# Install
pip install -e .

# Scan a file
mcpaudit /path/to/server.py

# Scan a directory
mcpaudit /path/to/mcp-servers/

# JSON output for CI/CD
mcpaudit /path/to/server.py --format json

# HTML report
mcpaudit /path/to/server.py --format html --output report.html

# SARIF output (GitHub Code Scanning, VS Code SARIF Viewer)
mcpaudit /path/to/server.py --format sarif -o results.sarif

# Generate default config file
mcpaudit --init
```

## Why mcpaudit?

| | AI-Infra-Guard (Tencent) | mcpaudit |
|---|---|---|
| Requirements | Docker + 4GB RAM + LLM API key | Python 3.11+ (stdlib only) |
| Speed | Minutes (API calls) | <200ms (static analysis) |
| Method | ReAct agent with LLM | AST + regex pattern matching |
| Cost | LLM API calls | $0 (everything local) |
| Dependencies | Docker, LLM SDK | None (stdlib only) |
| CI/CD | Manual | Exit codes + JSON + HTML + SARIF |

## Security Rules (20)

Every finding includes a **CWE reference** for standards traceability and a **confidence level** (HIGH, MEDIUM, or LOW) indicating detection accuracy.

| Rule ID | Severity | What it Detects | CWE |
|---------|----------|-----------------|-----|
| `CMD-001` | CRITICAL | Command injection -- `subprocess` with `shell=True` | CWE-78 |
| `CMD-002` | HIGH | Command injection -- subprocess with f-strings or string concatenation | CWE-78 |
| `SQL-001` | CRITICAL | SQL injection -- queries with string interpolation | CWE-89 |
| `SEC-001` | CRITICAL | Hardcoded secrets -- API keys, tokens, passwords in source code | CWE-798 |
| `DESER-001` | CRITICAL | Unsafe deserialization -- `yaml.load()`, arbitrary code execution | CWE-502 |
| `PATH-001` | HIGH | Path traversal -- file operations on user input without `resolve()`/`is_relative_to()` | CWE-22 |
| `PATH-002` | MEDIUM | Hardcoded absolute paths -- `/Users/`, `/home/`, `C:\Users\` | -- |
| `PERM-001` | HIGH | Excessive permissions (`chmod 777`) | CWE-250 |
| `AUTH-001` | MEDIUM | Missing authentication in server code | CWE-306 |
| `CORS-001` | MEDIUM | CORS wildcard (`Access-Control-Allow-Origin: *`) | CWE-942 |
| `VAL-001` | MEDIUM | Missing input validation in MCP handler functions | -- |
| `REDOS-001` | MEDIUM | ReDoS -- regex with nested quantifiers | -- |
| `RATE-001` | LOW | Missing rate limiting in server code | CWE-770 |
| `TEMP-001` | LOW | Insecure temporary file creation | CWE-377 |
| `SSRF-001` | HIGH | Server-Side Request Forgery -- HTTP requests with dynamic URLs | CWE-918 |
| `FILE-001` | HIGH | Unsafe file write -- `open()` with dynamic path in write mode | CWE-73 |
| `ERR-001` | MEDIUM | Missing error handling in MCP tool handler functions | CWE-755 |
| `LOG-001` | MEDIUM | Sensitive data in logs -- logging secrets, tokens, passwords | CWE-532 |
| `RES-001` | MEDIUM | Resource exhaustion -- unbounded reads, missing timeouts | CWE-400 |
| `INFO-001` | LOW | Information disclosure -- detailed exceptions returned to users | -- |

## Configuration File

mcpaudit supports a `.mcpaudit.yml` configuration file for persistent project-level settings.

### Generate default config

```bash
mcpaudit --init
```

This creates a `.mcpaudit.yml` in the current directory with all options commented out.

### Use a custom config

```bash
mcpaudit /path/to/code --config .mcpaudit.yml
```

If no `--config` flag is passed, mcpaudit automatically loads `.mcpaudit.yml` from the current working directory when present.

### Configuration options

```yaml
# .mcpaudit.yml

# Disable specific rules by ID
exclude_rules:
  - AUTH-001
  - RATE-001

# Skip files/directories matching these glob patterns
exclude_paths:
  - "tests/*"
  - "vendor/*"

# Only show findings at or above this severity level
# Values: critical, high, medium, low, info
severity_threshold: medium

# Default output format: text, json, html, sarif
output_format: text
```

CLI flags (`--format`, `--severity`) override config file values when both are specified.

## Output Formats

### Terminal (default)

```
============================================================
  MCP Security Audit -- Resultados
============================================================
  Archivos escaneados: 1
  Lineas escaneadas: 150
  Hallazgos totales: 3
============================================================

  [FILE] server.py
     Grade: C | 3 hallazgos | 12.3ms

     [!!] [CMD-001] Command Injection (shell=True)
       Linea 42: subprocess.run() con shell=True.
       Codigo: subprocess.run(cmd, shell=True)
       Fix: Usar lista de argumentos sin shell=True.

     [!] [PATH-001] Posible Path Traversal
       Linea 18: Funcion open() sin validacion de path.
       Fix: Usar path.resolve() y path.is_relative_to(base_dir).

     [-] [AUTH-001] Sin autenticacion detectada
       Linea 1: El servidor MCP no implementa autenticacion.
============================================================
```

### JSON (for CI/CD)

```json
{
  "summary": {
    "files_scanned": 1,
    "total_findings": 3,
    "overall_grade": "C",
    "severity_counts": {
      "CRITICAL": 1,
      "HIGH": 1,
      "MEDIUM": 1,
      "LOW": 0,
      "INFO": 0
    },
    "exit_code": 2
  },
  "results": [...]
}
```

Each finding in `results` includes `cwe` (e.g., `"CWE-78"`) and `confidence` (e.g., `"HIGH"`) fields.

### HTML Report

Self-contained dark-themed HTML with dashboard, severity breakdown, and detailed findings. Generate with:

```bash
mcpaudit /path/to/code --format html --output report.html
```

### SARIF (Static Analysis Results Interchange Format)

SARIF 2.1.0 output for integration with industry-standard tooling:

- **GitHub Code Scanning** -- upload via `github/codeql-action/upload-sarif`
- **VS Code SARIF Viewer** -- view findings inline in your editor
- **Azure DevOps** -- native SARIF support in pipelines
- **Any SARIF-compatible viewer** -- standard OASIS format

```bash
mcpaudit /path/to/code --format sarif -o results.sarif
```

Each SARIF result includes `security-severity` scores (0--10 scale), CWE tags, confidence-based precision levels, and inline fix suggestions.

#### Upload to GitHub Code Scanning

```yaml
# In your GitHub Actions workflow
- run: mcpaudit . --format sarif -o results.sarif
- uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: results.sarif
```

## Grading System

| Grade | Weighted Score | Meaning |
|-------|---------------|---------|
| A+ | 0 | No findings |
| A | 1-2 | Minor issues only |
| B | 3-5 | Some medium issues |
| C | 6-10 | High severity findings |
| D | 11-20 | Multiple high severity |
| F | 21+ | Critical issues present |

## Exit Codes

| Code | Meaning |
|------|---------|
| `0` | No HIGH or CRITICAL findings |
| `1` | HIGH findings present |
| `2` | CRITICAL findings present |

## CI/CD Integration

### GitHub Actions

```yaml
# .github/workflows/security.yml
name: Security Scan
on: [push, pull_request]

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install -e path/to/mcpaudit
      - run: mcpaudit . --format json --output results.json
      - run: mcpaudit . --format html --output report.html
      - run: mcpaudit . --format sarif --output results.sarif
      - uses: actions/upload-artifact@v4
        with:
          name: security-report
          path: |
            report.html
            results.json
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: results.sarif
```

A reusable workflow is included at `.github/workflows/security-scan.yml`.

### Pre-commit Hook

```bash
cp hooks/pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
```

Blocks commits with HIGH or CRITICAL findings.

### Baseline (Suppress Known Findings)

Accept existing findings to only fail on **new** security issues:

```bash
# Step 1: Create baseline from current state
mcpaudit . --create-baseline .mcpaudit-baseline.json

# Step 2: In CI/CD, scan with baseline — only NEW findings trigger failure
mcpaudit . --baseline .mcpaudit-baseline.json
```

Fingerprints are stable across line number changes — a finding is only un-suppressed if the code itself changes.

## Architecture

```
mcpaudit/
|
|-- __init__.py          # Package exports (MCPSecurityScanner, models)
|-- __main__.py          # CLI entry point (mcpaudit command)
|-- scanner.py           # Core scanning engine
|-- models.py            # Data classes (Finding, ScanResult, Severity, etc.)
|-- formatters.py        # Text and JSON output formatters
|-- config.py            # Configuration file loader (.mcpaudit.yml)
|
|-- rules/               # Modular security rules (20 rules)
|   |-- __init__.py      # Rule base class + get_all_rules()
|   |-- path_traversal.py
|   |-- command_injection.py
|   |-- hardcoded_secrets.py
|   |-- absolute_paths.py
|   |-- input_validation.py
|   |-- sql_injection.py
|   |-- unsafe_deser.py
|   |-- info_disclosure.py
|   |-- missing_auth.py
|   |-- cors_misconfig.py
|   |-- unsafe_temp.py
|   |-- excessive_perms.py
|   |-- redos.py
|   |-- rate_limiting.py
|   |-- ssrf.py           # SSRF detection
|   |-- unsafe_file_write.py # Dynamic path write detection
|   |-- error_handling.py  # Missing try/except in tool handlers
|   |-- sensitive_logging.py # Secrets in log statements
|   |-- resource_exhaustion.py # Unbounded reads, missing timeouts
|
|-- reporters/           # Report generators
|   |-- html_reporter.py # Self-contained HTML report (dark theme)
|   |-- sarif_reporter.py # SARIF 2.1.0 output (GitHub, VS Code, etc.)
|
|
|-- examples/           # Demo files
|   |-- vulnerable_server.py  # Intentionally vulnerable MCP server (26 findings)
|
tests/
|   |-- test_scanner.py    # Core scanner tests
|   |-- test_sarif.py      # SARIF output validation
|   |-- test_config.py     # Config file tests
|   |-- test_new_rules.py  # Tests for SSRF, FILE, ERR, LOG, RES rules
|
hooks/
|   |-- pre-commit       # Git pre-commit hook
|
.github/workflows/
    |-- security-scan.yml  # Reusable GitHub Actions workflow
```

## CWE Reference Table

All rule CWE mappings at a glance:

| Rule ID | CWE ID | CWE Name |
|---------|--------|----------|
| `CMD-001`, `CMD-002` | CWE-78 | OS Command Injection |
| `SQL-001` | CWE-89 | SQL Injection |
| `AUTH-001` | CWE-306 | Missing Authentication for Critical Function |
| `PATH-001` | CWE-22 | Path Traversal |
| `DESER-001` | CWE-502 | Deserialization of Untrusted Data |
| `CORS-001` | CWE-942 | Overly Permissive Cross-domain Whitelist |
| `RATE-001` | CWE-770 | Allocation of Resources Without Limits |
| `PERM-001` | CWE-250 | Execution with Unnecessary Privileges |
| `TEMP-001` | CWE-377 | Insecure Temporary File |
| `SEC-001` | CWE-798 | Use of Hard-coded Credentials |
| `SSRF-001` | CWE-918 | Server-Side Request Forgery |
| `FILE-001` | CWE-73 | External Control of File Name or Path |
| `ERR-001` | CWE-755 | Improper Handling of Exceptional Conditions |
| `LOG-001` | CWE-532 | Insertion of Sensitive Information into Log File |
| `RES-001` | CWE-400 | Uncontrolled Resource Consumption |

## Adding Custom Rules

```python
from mcpaudit.rules import Rule
from mcpaudit.models import Finding, Severity

class MyCustomRule(Rule):
    @property
    def rule_id(self) -> str:
        return "CUSTOM-001"

    @property
    def name(self) -> str:
        return "My Custom Check"

    @property
    def severity(self) -> Severity:
        return Severity.HIGH

    @property
    def description(self) -> str:
        return "Detects my custom pattern."

    def check(self, tree, source) -> list[Finding]:
        findings = []
        # Your detection logic here
        return findings

# Use it
from mcpaudit.scanner import MCPSecurityScanner
scanner = MCPSecurityScanner(rules=[MyCustomRule()])
```

## License

MIT License. Created by Carlos Miret.
