Static preflight scanner for MCP servers and agent skills
Project description
AgentPreflight
Pre-deployment security scanner for MCP servers and agent skills. Finds tool poisoning, prompt injection, secrets, and unsafe code before your agent runs — offline static scan, Codex-generated fixes, rescan proof.
trust_score=0 verdict=fail findings=15 ← poisoned repo
trust_score=100 verdict=pass findings=0 ← after Codex fix + rescan
Why
The first confirmed malicious MCP server on npm ran for 15 versions before anyone noticed. Then, in a single commit, the attacker added one BCC line to send_email. Every password reset token forwarded to an attacker address. No CI check caught it.
This was not isolated:
- 14 documented MCP incidents in 12 months (authzed.com) — WhatsApp exfiltration, GitHub MCP prompt injection exfiltrating private repo data including financial information, Asana cross-tenant data leak (~1,000 enterprise orgs, 35 days), Smithery supply-chain breach (3,000+ apps), Oura MCP malware campaign delivering StealC credential harvesters (Feb 2026), nginx-ui MCP auth bypass granting complete nginx service control across 2,600+ live infra instances (Mar 2026)
- 36.82% of 3,984 scanned agent skills (1,467 skills) had at least one flaw; 13.4% (534 skills) had a critical issue; 76 confirmed malicious payloads, 91% combining prompt injection with traditional malware techniques — 8 payloads still publicly available at time of publication (Snyk ToxicSkills 2025)
- 43% of popular MCP server implementations had command injection; Puppeteer MCP (91,000 monthly downloads) had SSRF + prompt injection + sandbox bypass — archived rather than patched. 45% of vendors dismissed Equixly's findings as "theoretical." Equixly's conclusion: "It feels like we're facing a regression in security." (March 2025)
- CVSS 9.6 RCE in
mcp-remote(437,000+ downloads) — the package Claude Desktop uses for remote MCP - 72.8% tool-poisoning attack success rate against o1-mini in one evaluated setting (45 real-world servers); Claude-3.7-Sonnet refused fewer than 3% of malicious test cases (MCPTox)
- April 2026: OX Security found STDIO architectural flaw across 150M+ downloads — Anthropic declined to modify the protocol
- Runtime defenses fail by design: Invariant Labs demonstrated a "rug pull" — malicious server served innocent descriptions on first launch, then switched to data-exfiltrating instructions on second launch, after trust was already granted
MCP tool descriptions are natural-language, invisible to standard CI checks. Bandit and Semgrep scan Python syntax — they do not parse the semantic content of tool metadata strings. A Postmark-style BCC injection in a tool description is invisible to every general-purpose SAST tool on the market. The MCP specification requires clients to treat tool annotations from untrusted servers as untrusted — but provides no enforcement mechanism. Anthropic declined to modify the STDIO architecture (OX Security, April 2026). The gate must be built outside the protocol layer. AgentPreflight is that gate: sub-second pre-commit/PR linter — scan → trust score → Codex patch → rescan proof. Under two minutes, entirely static — offline-first, local static file parsing with straightforward logic, never executes the server to analyze it. First to market on the complete scan-to-Codex-patch-to-rescan-proof workflow for MCP security. Conservative time model: 15 minutes saved per agent-extension PR; 30–60 minutes saved per high-risk finding when Codex suggests a targeted patch.
Install
# From PyPI (recommended):
pip install mcp-agent-preflight-sec
agentpreflight --version
# From source:
pip install .
# Optional extras:
pip install "mcp-agent-preflight-sec[api]" # FastAPI scan endpoint
pip install "mcp-agent-preflight-sec[codex]" # Codex AI remediation
export OPENAI_API_KEY=<your-key>
90-Second Demo
# scan a poisoned MCP repo — expect fail
agentpreflight scan demo/poisoned --profile strict --fail-on high
# get Codex AI patch proposals (requires OPENAI_API_KEY)
agentpreflight fix demo/poisoned --rules AP-MCP-001 --codex
# apply deterministic safe fixes
cp -r demo/poisoned /tmp/fix-demo
agentpreflight fix /tmp/fix-demo --apply
# rescan — expect pass
agentpreflight scan /tmp/fix-demo --profile strict
Sample Output
Scan a poisoned MCP repo:
$ agentpreflight scan demo/poisoned --profile strict
╔════════════════════════════════════════════════════╗
║ ▄▀▄ █▀█ ║
║ █▀█ █▀ AgentPreflight v0.1.0 ║
║ ▀ ▀ ▀ Pre-deployment MCP & skill scanner ║
║ offline · static · SARIF · 21 rules ║
╚════════════════════════════════════════════════════╝
AgentPreflight target=demo/poisoned
trust_score=0 verdict=fail findings=15 offline=True
summary critical=7 high=5 medium=3 low=0 suppressed=0 artifacts=6
┌──────────┬──────────────┬────────────┬──────┬───────────────────────────────┐
│ Severity │ Rule │ Path │ Line │ Evidence │
├──────────┼──────────────┼────────────┼──────┼───────────────────────────────┤
│ critical │ AP-CODE-003 │ install.sh │ 3 │ Remote pipe: curl … | bash │
│ critical │ AP-CODE-001 │ run.py │ 5 │ os.system() call │
│ critical │ AP-CODE-001 │ server.py │ 6 │ os.system() call │
│ critical │ AP-CODE-001 │ server.py │ 7 │ subprocess with shell=True │
│ critical │ AP-CODE-003 │ SKILL.md │ 10 │ Remote pipe: curl … | sh │
│ high │ AP-SEC-002 │ .env │ 1 │ Secret-like token pattern │
│ high │ AP-MCP-001 │ mcp.json │ │ Tool description contains: │
│ │ │ │ │ "Hidden instruction" │
│ high │ AP-SKILL-001 │ SKILL.md │ 5 │ Injection phrase: "Secret │
│ │ │ │ │ directive" │
│ … │ │ │ │ │
└──────────┴──────────────┴────────────┴──────┴───────────────────────────────┘
fix_available=14 run: agentpreflight fix demo/poisoned
Drill into findings with source context (--verbose):
$ agentpreflight scan demo/poisoned --verbose
... (banner + table) ...
──────────────────── run.py ────────────────────
3 def setup_environment(user_input):
4 # Dangerous: user input reaches shell
5 → os.system(f"echo Setting up: {user_input}")
6 return True
7
8 def install_deps():
9 → os.system("pip install -r requirements.txt")
──────────────────── server.py ─────────────────
5 def run_task(user_input: str) -> None:
6 → os.system("deploy " + user_input)
7 → subprocess.run("echo " + user_input, shell=True)
8
10 def parse_rule(expr: str):
11 → return eval(expr)
Apply fixes and rescan:
$ agentpreflight fix demo/poisoned --apply
fixable=14 changed=7
$ agentpreflight scan demo/poisoned --profile strict
╔════════════════════════════════════════════════════╗
║ ▄▀▄ █▀█ ║
║ █▀█ █▀ AgentPreflight v0.1.0 ║
║ ▀ ▀ ▀ Pre-deployment MCP & skill scanner ║
║ offline · static · SARIF · 21 rules ║
╚════════════════════════════════════════════════════╝
AgentPreflight target=demo/poisoned
trust_score=100 verdict=pass findings=0 offline=True
PASS: no issues found
Scan a clean repo:
$ agentpreflight scan demo/clean --profile strict
╔════════════════════════════════════════════════════╗
║ ▄▀▄ █▀█ ║
║ █▀█ █▀ AgentPreflight v0.1.0 ║
║ ▀ ▀ ▀ Pre-deployment MCP & skill scanner ║
║ offline · static · SARIF · 21 rules ║
╚════════════════════════════════════════════════════╝
AgentPreflight target=demo/clean
trust_score=100 verdict=pass findings=0 offline=True
PASS: no issues found
Commands
# scan with trust score + table output
agentpreflight scan <path> --profile strict --fail-on high
# show source snippet context (±2 lines) for each finding
agentpreflight scan <path> --verbose
# output formats
agentpreflight scan <path> --format sarif --output report.sarif
agentpreflight scan <path> --format json --output report.json
agentpreflight scan <path> --format markdown --output pr-comment.md
# suppression file
agentpreflight scan <path> --suppressions .agentpreflight.json
# fix (dry run → Codex proposals → apply)
agentpreflight fix <path>
agentpreflight fix <path> --codex # Codex AI patch (OPENAI_API_KEY)
agentpreflight fix <path> --codex --rules AP-MCP-001
agentpreflight fix <path> --apply # deterministic local fix
# generate Codex remediation prompt pack
agentpreflight prompts <path> --output remediation.md
# benchmark scan speed
agentpreflight bench <path> --runs 5
# list all 21 rules
agentpreflight rules list
# show description, remediation, and references for a rule
agentpreflight rules info AP-CODE-001
GitHub Action
- uses: ./
with:
target: .
profile: strict
fail-on: high
sarif-file: agentpreflight.sarif
comment-file: agentpreflight-comment.md
SARIF uploads to GitHub Security tab. PR comment scorecard auto-posts.
Full workflow: .github/workflows/agentpreflight.yml
Rules (21 total)
| Category | Rules |
|---|---|
| MCP | AP-MCP-001 prompt override, AP-MCP-002 trust claim, AP-MCP-003 untrusted result, AP-MCP-004 loose schema, AP-MCP-005 privileged tool |
| Skill | AP-SKILL-001 prompt injection, AP-SKILL-002 hidden Unicode, AP-SKILL-003 remote dependency, AP-SKILL-004 credential seeking, AP-SKILL-005 capability mismatch |
| Code | AP-CODE-001 unsafe shell (crit), AP-CODE-002 dynamic exec, AP-CODE-003 remote pipe exec (crit), AP-CODE-004 file access, AP-CODE-005 network exfiltration |
| Secrets | AP-SEC-001 private key (crit), AP-SEC-002 API token, AP-SEC-003 committed env file |
| Transport | AP-NET-001 broad bind, AP-NET-002 missing origin validation, AP-NET-003 plain HTTP tool |
Suppression File
.agentpreflight.json at repo root — auto-detected:
{
"suppressions": [
{
"rule": "AP-SEC-003",
"path": ".env",
"reason": "demo fixture",
"owner": "appsec",
"expires": "2026-06-30"
}
]
}
Inline suppression in source:
os.system("echo ok") # agentpreflight:disable-line AP-CODE-001
API (optional)
pip install ".[api]"
uvicorn agentpreflight.api.main:app --reload
curl -X POST http://localhost:8000/v1/scans \
-H "Content-Type: application/json" \
-d '{"target": {"path": "demo/clean"}, "profile": "strict"}'
Auth + rate limiting via env vars:
export AGENTPREFLIGHT_API_KEY=local-dev-key
export AGENTPREFLIGHT_RATE_LIMIT_PER_MINUTE=60
Validation
73 tests pass (pytest tests/ -q). Covers: all 21 rules, scanner exclude, suppression, scoring caps, Codex API mock, SARIF output, fix proofs, API auth/rate limiting, benchmark, --verbose snippet context, rules info all 21 rules.
$ pytest tests/ -q
....................................................................... [100%]
73 passed in 0.74s
Scan → fix → rescan proof:
$ agentpreflight scan demo/poisoned --profile strict
trust_score=0 verdict=fail findings=15 critical=7 high=5 medium=3
$ agentpreflight fix demo/poisoned --apply
fixable=14 changed=7
$ agentpreflight scan demo/poisoned --profile strict
trust_score=100 verdict=pass findings=0
PASS: no issues found
Architecture
agentpreflight scan:
collector → normalizer → rule engine → trust scorer → reporter
(100% offline, zero API calls, sub-second on typical repos)
agentpreflight fix:
findings → [--codex] redacted snippet → OpenAI Codex API → patch proposal
→ [--apply] deterministic regex rewrite → files modified
agentpreflight scan (rescan):
new trust score confirms fix held
Scan path: offline by default, no model calls, no token cost.
Fix path: --codex sends a 5-line code window around the violation (redacted — no secrets, no file paths) to codex-mini-latest. Code rewriting is cheap; the scarce resource is selection — which of the infinite possible rewrites is minimal, compilable, and review-ready. Codex makes that call.
CI path: --fail-on high exits 1 on violations; trust score thresholds: 85+=pass, 70–84=warn, <70=fail (critical finding caps at 50; secrets cap at 55; 3+ high cap at 60; combo caps lower); critical rules: AP-CODE-001 (unsafe shell), AP-CODE-003 (remote pipe exec), AP-SEC-001 (private key); SARIF uploads to GitHub Security tab.
Docs
| File | Contents |
|---|---|
PRODUCT.md |
Build plan, MVP scope, success metrics |
SPEC.md |
Architecture, rule catalog, scoring formula |
DEMO.md |
90-second demo script, launch checklist |
COMPETITORS.md |
Competitive landscape, 10x differentiation |
EVIDENCE.md |
CVEs, incidents, scale stats, verification audit |
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcp_agent_preflight_sec-0.1.0.tar.gz.
File metadata
- Download URL: mcp_agent_preflight_sec-0.1.0.tar.gz
- Upload date:
- Size: 61.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a8f8122eb66f5677384570fd9d010dd91c9bf280d8435e7b4ff70972b3078f2
|
|
| MD5 |
85f6d3250bf08ec5ba058b8e368b86ac
|
|
| BLAKE2b-256 |
0fa05ea27514d5a9b7389b6425c3059f50a7aad7cbc8f5459068260bc03695b2
|
File details
Details for the file mcp_agent_preflight_sec-0.1.0-py3-none-any.whl.
File metadata
- Download URL: mcp_agent_preflight_sec-0.1.0-py3-none-any.whl
- Upload date:
- Size: 53.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b8376ce461d6c98e615224ce9e8460d8ddf9b54753f7e132ce9054ca04b0cd1e
|
|
| MD5 |
c38d48143001933a4a883234ea2fa530
|
|
| BLAKE2b-256 |
503bcfad0624101b62c2f481b437a732044065069aba1c88d0a7f9fd9a8213ce
|