Observed since 20 Oct, 21:47 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Offline scanners for the code patterns behind 68 CVEs in 9 AI-agent frameworks, plus MCP tool pinning. No dependencies, no telemetry.
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.
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 from PyPI: pipx install agent-cve-scanners (or pip install agent-cve-scanners), which gives you the
agent-cve-scan and mcp-tool-pin commands. pipx run --spec agent-cve-scanners agent-cve-scan . runs it once without
installing. From a checkout, pip install . does the same.
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.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..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.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.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.
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.
# .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.
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.
python -m pytest -q tests
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).
decoy-kit-free (MIT): one complete honeytoken, a genuinely valid SSH key whose comment carries a
beacon URL, plus a receiver you run yourself. pipx install decoy-kit-free. It catches secret scanners and attackers who find the key after a
filesystem or repository compromise; this repository checks your code before that happens.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.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.5.2 | 6 Oct 2026 | 0 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 21:47 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:c5f5b64f8f8cc39d11c814e1bbb4d1d19fb5e25a1744216eac0b724b937db513