Skip to main content

agent-cve-scanners

Free, offline scanners that check an AI-agent codebase for the code and dependency patterns behind 68 published CVEs (plus 4 GitHub-only advisories) in nine agent frameworks and in MCP client and server code, and a small tool that pins MCP tool definitions so you notice when a server changes a tool after you approved it.

Scanner Framework CVEs covered GitHub-only Tracked, not covered
autogpt_cve_scanner AutoGPT Platform 19 4 8 + 4
crewai_cve_scanner CrewAI 5 0 1
flowise_cve_scanner Flowise 4 0 3
google_adk_cve_scanner Google Agent Development Kit 2 0 1
langchain_cve_scanner LangChain 7 0 0
langgraph_cve_scanner LangGraph 7 0 2
llamaindex_cve_scanner LlamaIndex 7 0 2
mcp_client_cve_scanner MCP client / host applications 12 0 1
mcp_server_cve_scanner MCP servers over HTTP (Python, TypeScript) 3 0 0
n8n_cve_scanner n8n 2 0 0
semantic_kernel_cve_scanner Microsoft Semantic Kernel 2 0 0
mcp_tool_pinning any MCP server - - -

The columns add up to more than 68 because two CVEs are covered by more than one scanner (68 is the number of distinct CVEs). "Tracked, not covered" are real advisories a scanner's docstring names and deliberately does not turn into a check - because the flaw is a missing limit rather than a code shape, the technical detail is not public, or another tool already covers it. They are listed rather than hidden: scanners/data/coverage.json has the exact ids per scanner and is rebuilt by tools/build_coverage.py.

Every id was checked against its primary source (NVD for CVEs; GitHub's advisory database, including repository-level advisories, for GHSAs): see docs/ADVISORY_VERIFICATION.md. Every covered CVE id was found in NVD or GitHub's advisory database at the last check; the ids that were not (a ZDI-published CVE, and Bisheng's CVE-2026-33224) are the "tracked, not covered" ones.

Use it

Python 3.10+, no dependencies.

python scan.py path/to/your/project                    # readable report
python scan.py path/to/your/project --json             # for CI; exit code 1 when something is flagged
python scan.py path/to/your/project --sarif out.sarif  # SARIF 2.1.0, for GitHub code scanning

Pin MCP tool definitions, then check them later:

python scanners/mcp_tool_pinning.py --help

Or install it as a package from a checkout (pip install .), which gives you the agent-cve-scan and mcp-tool-pin commands.

What it checks

  1. Code patterns - nine scanners, one per framework. Calls wrapped over several lines, comments, docstrings, import aliases (import pickle as pk, from x import load_prompt as lp) and nested arguments are handled, so load_prompt(\n user_path\n) is seen and # never use pickle.loads(x) is not.
  2. Dependency versions - requirements*.txt, pyproject.toml, setup.py/setup.cfg, Pipfile.lock, poetry.lock, uv.lock, package.json, package-lock.json and yarn.lock are compared with the affected version ranges in scanners/data/affected.json (taken from OSV, rebuilt by tools/update_affected.py). An exact pin or lockfile entry inside an affected range is a finding. A range is a finding only when its lowest allowed version is affected; >=1.5.0 with no ceiling is not, because picking a version is the resolver's job. Advisories OSV has no PyPI/npm range for (for example AutoGPT's repository advisories) are not in that data and are never guessed. Turn this off with --no-deps.
  3. MCP client configs - .mcp.json, mcp.json, claude_desktop_config.json, mcp_config.json, .claude.json: when a server is launched with npx, bunx, pnpm dlx, uvx or pipx run and names a version (pkg@1.2.3, pkg==1.2.3), that package and version are checked against the same advisory data (for example the MCP Python SDK, LiteLLM, n8n-mcp, dbt-mcp, Flowise). To check your own machine, python scan.py --home-configs reads only the well-known per-user config files (Claude Desktop, Claude Code, Cursor, Windsurf, VS Code), runs only the version check on them, shows paths as ~/..., and says how many servers it could and could not judge: a server launched as plain npx pkg resolves to "latest" at run time and a static check cannot evaluate it. In a sample of 362 public .mcp.json files only about 9% of package launches pinned a version and none pinned one with a published advisory (census, a retrieved sample, not a prevalence estimate), so for MCP configs this check will usually find nothing to say; it matters more on lockfiles and requirements files.
  4. MCP client checks (mcp_client_cve_scanner) - application code and project config that launch or trust MCP servers, for the shapes behind the MCP stdio command-injection CVE family and a Claude Code project-trust bypass. Python and TypeScript source for the first two, project-scoped files for the last two:
    • stdio_command_non_literal - StdioServerParameters(...) / new StdioClientTransport({...}) whose command is not a literal, whose args is a variable or has a spread, or whose env is a variable, spreads a user dict, or sets a dangerous key (NODE_OPTIONS, LD_PRELOAD, PATH, ...) to a variable. Not flagged: command=sys.executable, list literals with a variable element, {"API_KEY": key}.
    • command_allowlist_args_unvalidated - an args=[...] list containing -c, -e, --eval or --eval-string in MCP client code, which defeats a command-name allowlist. Not reported in committed JSON configs, where bash -c wrappers are common.
    • config_auto_approve_trust_bypass - enableAllProjectMcpServers: true or a non-empty enabledMcpjsonServers in a committed .claude/settings.json / .mcp.json (CVE-2026-21852).
    • vendor_api_base_url_override - ANTHROPIC_BASE_URL, OPENAI_BASE_URL and similar set to a literal in a project-scoped file. Official api.anthropic.com / api.openai.com hosts, $VAR references and .env.example-style files are not reported. Advisories behind these include CVE-2025-65720, CVE-2026-30623, CVE-2026-40933 and CVE-2026-21852.
  5. MCP server checks (mcp_server_cve_scanner) - Python and TypeScript code that serves MCP over HTTP:
    • mcp_dns_rebinding_protection_disabled (high confidence) - protection switched off or a * in the allowed hosts/origins (CVE-2025-66416 Python SDK < 1.23.0, CVE-2025-66414 TypeScript SDK < 1.24.0).
    • mcp_dns_rebinding_protection_missing (review) - a low-level transport built in a file that binds to localhost with no protection configured; FastMCP() and createMcpExpressApp() protect by default and are not flagged, and neither is a file that checks the Host/Origin header or keeps an allowed-hosts list itself. It cannot see authentication or other files, so behind real auth it is a false positive.
    • mcp_transport_shared_across_clients (review) - a TypeScript StreamableHTTPServerTransport created at module scope, so one transport serves every client (CVE-2026-25536, fixed in 1.26.0). Fine for a single-client tool.

Not what this is: a general MCP config linter. Plaintext secrets in configs, http:// transports, curl | sh launch commands, over-broad filesystem roots and unpinned @latest references are already covered by other free tools - for example mcp-config-audit, mcp-config-lint and mcp-drift-check - and are deliberately not repeated here. This tool's lane is the question those do not ask: does a server version you pinned have a published advisory.

Keeping the noise down

python scan.py . --no-tests                  # drop findings under tests/, examples/, docs/ (they are tagged either way)
python scan.py . --ignore 'vendor/*'         # drop findings by path glob (repeatable)
python scan.py . --write-baseline base.json  # record today's findings (exit 0)
python scan.py . --baseline base.json        # report only findings NOT in base.json - fail CI on new problems

Suppress one finding in the source it points at (same line, or a comment line directly above):

data = pickle.loads(blob)  # agent-cve-scan: ignore[langgrinch_insecure_deserialization]
# agent-cve-scan: ignore            <- a bare marker covers every category

A baseline entry is keyed by rule + file + the normalised line text, not by line number, so unrelated edits above a finding do not bring it back.

CI and pre-commit

# .github/workflows/agent-cve-scan.yml (steps)
- uses: actions/checkout@v4
- uses: Ech333/agent-cve-scanners@v0.5.1
  with:
    baseline: .agent-cve-baseline.json   # optional: fail only on new findings
- uses: github/codeql-action/upload-sarif@v3
  if: always()
  with: { sarif_file: agent-cve-scan.sarif }

The action is a composite action that uses the runner's python3: no pip install, no network, no telemetry. For pre-commit, add the hook agent-cve-scan from this repository to .pre-commit-config.yaml. Inputs, outputs, permissions and a complete workflow are in docs/usage-ci.md.

Policy (dry run first)

agent-cve-scan . --policy policy.json checks which MCP servers a project launches against a policy: package allow/deny globs, a "must be pinned" rule, and optionally "no launches I cannot identify". It is a dry run by default (violations are printed, the exit code is unchanged); --enforce makes them fail the run. The same file carries a tools section (allowed_tools / blocked_tools) in the field names MCP Gateway's ToolPolicy takes, so a team can start in dry run here and move to runtime enforcement of tool calls later. An invalid policy is an error (exit 2), never a permissive default. Details and an example: docs/policy.md.

What it is and isn't

  • Reads files only. It never runs your code, never installs anything and never sends anything anywhere. No telemetry, no account. The advisory data is bundled; nothing is fetched when you scan.
  • Pattern matching, not proof. A finding means "this looks like the shape behind CVE-X, go look", not a confirmed exploit. A clean result does not mean you are safe. Keep your frameworks updated.
  • Known limits. It is a text scanner, not a data-flow analyser: it does not know whether a value reaching a call is attacker-controlled, only whether it is a literal. It does not resolve transitive dependencies that no lockfile in the tree records. Python statements are joined using the standard tokenizer; JavaScript/TypeScript use a simpler bracket-matching join, so unusual JS (regex literals containing brackets, template-literal tricks) can be missed or mis-joined. Code quoted inside a string that is assigned (a prompt template, a test fixture) is still scanned; bare docstrings are not. Scanning the source of a scanner (including these) reports its own finding messages.
  • Findings describe the vulnerable pattern and the fixed version; no exploit code is included.

Tests

python -m pytest -q tests

Maintaining the data (not needed to use the scanners)

python tools/update_affected.py        # affected version ranges, from OSV
python tools/build_coverage.py         # covered vs tracked-not-covered, per scanner
python tools/verify_advisories.py > docs/ADVISORY_VERIFICATION.md   # NVD + GitHub existence check (slow: NVD rate limit)

tools/tracked_advisories.txt lists advisories followed for the version check even though no code pattern exists for them (mostly MCP-server and agent-platform advisories from 2026).

Licence

GNU Affero General Public License v3.0 only (see LICENSE). If you change it and offer it to others, including as a hosted service, you publish your changes under the same licence.

Maintained by Erlend Christoffer Hagen Hårsaker (Norway). Corrections and new advisories welcome as issues or pull requests.

Metadata

Release files for agent-cve-scanners 0.5.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for agent-cve-scanners 0.5.1
File Size Uploaded
agent_cve_scanners-0.5.1.tar.gz 132.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for agent-cve-scanners 0.5.1
File Interpreter ABI Platform
agent_cve_scanners-0.5.1-py3-none-any.whl Python 3 none any Details

Total release size: 249.6 kB

Release files / agent_cve_scanners-0.5.1.tar.gz

Download URL agent_cve_scanners-0.5.1.tar.gz
Size 132.3 kB
Tags Source
SHA-256 checksum
How to use checksums
c83b09e855aef3d243188963f76e25162e8ae798b290cadcbe3181c557ba896e
BLAKE2b-256 checksum
How to use checksums
397262ef8e8383b0e0e9791446d703b0e06bc9a076ac234ff5a0279010dbe85e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release files / agent_cve_scanners-0.5.1-py3-none-any.whl

Download URL agent_cve_scanners-0.5.1-py3-none-any.whl
Size 117.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
506dd46f37f23c51851c922d62d5478a60c6614b8133f35a330e8671d82048c8
BLAKE2b-256 checksum
How to use checksums
5fc9ffb96dba3fd08d79b1e7054c9bae03b676e9d6a8a409599042ecb46b94c6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page