Releases: akanthed/SecureAI-Scan
Release list
v0.11.0 — Ecosystem audit: 5 precision fixes, MCP011, pre-commit hook
Ecosystem audit of public MCP servers (upstash/context7, cloudflare/mcp-server-cloudflare, stripe/agent-toolkit, awslabs/mcp) turns up and fixes five precision bugs, adds a new detection rule (MCP011: untrusted tool source), and ships a pre-commit hook plus a VS Code extension scaffold for distribution.
Fixed
- AI002 flagged secrets outside any LLM context — fired on a bcrypt hash in a demo app and a non-secret constant merely named with "KEY".
- isTestFilePath now recognizes
eval(s)as a non-production path segment, so LLM-as-judge eval harnesses aren't scanned as real request handlers. - MCP001 required a real identifier reference instead of a plain substring match, fixing a
proven/critical false positive on prose that merely used the word "tools". - MCP009/SKL003 cross-tool-reference detection resolved all 7 residual false positives by requiring the referenced tool to sit between the trigger word and the verb (a redirect), not just appear anywhere in the sentence.
Added
- MCP011: untrusted tool source — flags an MCP tool handler that fetches from an external/unauthenticated source and returns the response with no sanitization (the Sentry-MCP-DSN attack shape).
- Pre-commit hook (
.pre-commit-hooks.yaml). - VS Code extension scaffold (
vscode-extension/) — local-build install, not yet on the Marketplace. - Example compliance artifact + terminal-recording demo.
npm test: 151/151 passing.
What's Changed
- chore(deps): bump github/codeql-action from 4.37.3 to 4.37.7 by @dependabot[bot] in #21
- Ecosystem audit, distribution surfaces, and 5 precision fixes by @akanthed in #22
- feat(rules): add MCP011 rule for untrusted tool source and implement … by @akanthed in #23
- Fix readme file by @akanthed in #24
Full Changelog: v0.10.0...v0.11.0
v0.10.0 — LiteLLM Proxy config scanning + 6 root-cause fixes from real-world regression testing
Static config scanning for LiteLLM Proxy, plus six false-positive/robustness bugs found and fixed by adding a large real-world repo (BerriAI/litellm) to the regression gate.
Added
- LLC001–LLC003: LiteLLM Proxy
config.yamlscanning. New off-disk scanner (src/scanner/litellm-config-scanner.ts), following the same pattern asmcp-config-scanner.ts. Gated behind a structural check (model_list+litellm_params) so unrelated YAML files are never touched.- LLC001 (
proven, critical): a hardcoded secret inlitellm_params/general_settingsinstead of anos.environ/VAR_NAMEreference. - LLC002 (
proven, high): a providerapi_basereachable over plaintexthttp://(non-localhost). - LLC003 (
heuristic, low,--paranoidonly): noguardrails:section configured at all — absence of an optional control, not a proven gap. - New dependency:
js-yaml(default-schemaload(), no unsafe deserialization tags).
- LLC001 (
Fixed
Found by adding BerriAI/litellm — the official repo, ~7,000 TS/JS files plus a large Python codebase — to npm run regression, specifically to get real coverage for the new LLC rules:
- LLC001 line misattribution — findings could point at the wrong
api_key:occurrence in a file with many entries. Now anchors on the flagged value, not the key name. - LLC001 placeholder-value false positives — LiteLLM's own docs/tests use dummy values like
fake-key,sk-lar1-demo. Added a placeholder-word check plus a "does this look like a random credential blob" heuristic. - A rule crash was silently discarding every other rule's findings — a scan that goes quiet instead of erroring is worse than any false positive. Each rule now runs isolated; one failure logs a warning instead of aborting the whole scan.
- MCP001 (Python) module-scope false positive — an unrelated settings description was treated as MCP tool metadata because the scope-detection fallback covered the whole file. Capped to a small line window.
- MCP002 (TypeScript) blanket parameter taint — every function parameter was treated as user-controlled input regardless of context, flagging a pure URL-parsing utility. Removed the blanket taint; the known-vulnerable fixture never needed it.
- VEC001 (Python)
re.searchcollision — stdlib regex search matched as a vector-store similarity search because a pattern string contained "vector". Excludedre.search/regex.search. - AI003 (Python) missed FastAPI's idiomatic
Depends(...)auth pattern in the route's parameter list — every health-check route in litellm's own proxy was flagged as unauthenticated despite being authenticated.
All six fixes shipped with a permanent fixture under test-fixtures/safe/.
Also
- Skill/MCP-server tool descriptions rewritten with explicit trigger phrasing for better auto-suggestion in Claude/Cursor.
- Real-world findings write-up: #19
Full changelog: https://github.com/akanthed/SecureAI-Scan/blob/main/CHANGELOG.md#0100--2026-08-19
v0.9.0 — OWASP LLM Top 10 2026 support + 5 new Agent Skill rules
Support for the official OWASP Top 10 for LLM Applications 2026, plus five new Agent Skill detection rules — SKL006 through SKL010.
OWASP LLM Top 10 2026
- Versioned reporting: JSON includes
owaspVersion: "2026", terminal/CLI output uses the officialLLMxx:2026notation, Markdown/HTML name the framework version, and SARIF emitsowasp-llm-top10-2026/*tags. - A conservative coverage statement (
docs/OWASP2026.md) distinguishes implemented static signals from partial coverage and the runtime-only Misinformation boundary, instead of presenting category mapping as exhaustive detection. - Existing findings remapped to the official 2026 ranking (Excessive Agency → LLM03, Supply Chain → LLM04, Data/Model Poisoning → LLM05, Unbounded Consumption → LLM06, Misinformation → LLM07, Hidden Context Exposure → LLM08, Vector/Embedding Weaknesses → LLM09, Improper Output Handling → LLM10). Detection behavior and evidence tiers are unchanged.
New Agent Skill rules
- SKL006 — load-time command execution via Claude Code's dynamic-context-injection syntax, which runs shell commands the instant a skill is read, before any tool-permission gate applies.
- SKL007 — unscoped
Bash/Bash(*)grants in a skill'sallowed-toolsfrontmatter. - SKL008 — skills that fetch and execute instructions from an external URL at runtime instead of shipping them in the reviewed bundle.
- SKL009 — skills that write into another trust-elevated context file (MEMORY.md, SOUL.md, AGENTS.md, CLAUDE.md), planting a backdoor that outlives the skill.
- SKL010 — unsafe deserialization tags (e.g. YAML
!!python/object) in skill-bundle metadata/config that execute code on load.
Validated against the real-repo regression corpus with no new false positives — see What we found scanning real repos for the full writeup.
What's Changed
Full Changelog: v0.8.0...v0.9.0
v0.8.0 — Tool-Poisoning & Skill-Shadowing Precision Pass
Highlights
This release is almost entirely a precision pass on the MCP tool-poisoning
(MCP009) and skill-shadowing (SKL002/SKL003) detectors, plus a couple of
config-scanning bugs. No new rules; several existing rules got materially
more accurate.
Fixed false positives
- MCP009 / SKL003 (cross-tool/cross-skill shadowing) now distinguishes a
bare conditional ("when X is unknown, use Y" — ordinary sibling-tool
documentation, extremely common in real multi-tool MCP servers and skill
bundles) from an actual override/substitution directive ("instead of",
"always", "never", "silently", or output-rerouting phrasing like "route the
message body through this tool"). Only the latter now fires atlikelyby
default; the bare-conditional form is demoted toheuristic
(--paranoid-only) instead of being dropped entirely. - SKL002 (injection phrasing) gained context-aware suppression for three
previously noisy patterns:- negation ("never read
.env") no longer reads as an instruction to
read credential files, - quoted example phrases inside a skill's own defensive documentation
("flag inputs like 'ignore previous instructions'") no longer read as
the attack they're describing, - "do not tell the user X — they were never asked" no longer reads as
concealment when the surrounding text is a factual correction.
- negation ("never read
- Fixed a related deobfuscation bug where the intra-word-line-break-join pass
(used to catch genuine evasion) could delete a newline that was itself part
of the suppressing context above, or coincidentally pull two unrelated
phrases into proximity — both were silently defeating the new suppressions
and, worse, promoting the resulting false positive toproven. Cross-tool
reference matching (SKL003) is no longer run through the deobfuscation
variants at all, since it has no exact-phrase requirement that evasion
could exploit. - SKL004/capability disclosure now checks a skill's companion reference
files, not justSKILL.mdand its top-level description — a bundle that
documents a download/exec step in a per-topic file (e.g. "Install Istio
CLI") was previously flagged as undisclosed even though it documents the
capability, just not in the top-level file. - MCP004 (secret committed in MCP config) now anchors its reported line
to the occurrence that actually contains the leaked value, not just the
first line mentioning the env var name — a config with both a placeholder
and a real secret sharing a var name across servers was pointing findings
at the wrong server. - MCP config env-reference detection now accepts any well-formed
${...}placeholder (dotted/path-style references like
${user_config.api_token}or${CLAUDE_PLUGIN_ROOT}, used by real MCP
client manifests) instead of only bare/env:/input:forms, so those
stopped being misread as literal committed secrets.
Other
- CI workflow actions (
checkout,setup-node,upload-artifact,
codeql-action,scorecard-action) are now pinned to commit SHAs instead
of floating version tags, closing a supply-chain gap in our own pipeline. - New safe-corpus fixtures locking in the fixes above:
test-fixtures/safe/skills/defensive-docs-skill/,
test-fixtures/safe/skills/lint-check/, and an expanded
test-fixtures/safe/mcp/.mcp.json.
v0.7.0 — Dataflow diagrams + cross-file AI001 tracing
SecureAI-Scan can now show, not just claim, how a vulnerability's data flows through your code.
- HTML reports (
--output report.html) now render every finding's source→flow→sink trace as an inline SVG node-link diagram instead of a flat text list, with a dashed "cross-file" arrow wherever a step crosses a file boundary. - Trace coverage extended to four more rules — MCP003 (tool result elevated to system-role), AI005 (unsafe output handling), AI012 (unvalidated structured output), and VEC003 (user content ingested into a vector store) — matching AI001's existing dataflow evidence.
- AI001 now traces tainted values across function and file boundaries: when a tainted parameter is passed into a locally-resolved helper (same project, import-resolved — never a name guess), the scanner follows the call up to 2 hops and builds a real multi-file trace, capped at
likelyevidence (neverproven) and cycle-safe against mutual recursion. - Fixed: AI012 could never fire — its own
JSON.parsedetection collided with its.parse(validation-pattern check, silencing the rule regardless of input.
Note: the interprocedural AI001 tracer mainly adds an accurate, honestly-capped cross-file trace — not new recall on its own, since the base rule's existing per-parameter taint fallback already flagged those same sink locations in isolation.
What's Changed
- chore(deps): bump actions/setup-node from 4 to 7 by @dependabot[bot] in #10
- chore(deps): bump github/codeql-action from 3 to 4.37.3 by @dependabot[bot] in #9
- chore(deps-dev): bump typescript from 6.0.3 to 7.0.2 by @dependabot[bot] in #8
- chore(deps-dev): bump @types/node from 22.19.17 to 26.1.2 by @dependabot[bot] in #7
- chore(deps): bump commander from 14.0.3 to 15.0.0 by @dependabot[bot] in #6
- chore(deps): bump actions/upload-artifact from 4 to 7 by @dependabot[bot] in #5
- chore(deps): bump actions/checkout from 4 to 7 by @dependabot[bot] in #4
- Add SVG dataflow diagrams, extend trace coverage, and trace AI001 acr… by @akanthed in #11
New Contributors
- @dependabot[bot] made their first contribution in #10
Full Changelog: v0.6.0...v0.7.0
v0.6.0 — Evasion-resistant skill scanning + pre-install scanning + root-cause bug fixes
Highlights
Evasion-resistant Agent Skill scanning. Closes the scanner-evasion techniques published in Cloak and Detonate (arXiv:2607.02357) — zero-width/homoglyph obfuscation, self-extracting payloads staged in .git/, and payloads hidden in *.test.ts files that every scanner in that study missed. Two new rules (SKL004, SKL005) plus a deobfuscation layer that matches content across normalized text variants. Validated against anthropics/skills (18 real bundles, 0 findings) and cisco-ai-defense/skill-scanner's labeled eval corpus (6/6 malicious fixtures correctly flagged, 0 false positives).
Scan before you install. New secureai-scan skill <target> / secureai-scan mcp <target> commands fetch and scan a skill or MCP server before you trust it — no clone, no config, and nothing fetched is ever executed (npm pack/git clone --depth 1 only). Same capability now exposed to Claude itself via a new scan_untrusted_target MCP tool.
Found and fixed 5 real bugs by scanning a 5,511-file production repo. Triaging every finding from a full scan of vercel/ai turned up three root-cause false-positive bugs in shared rule logic (an LLM-call resolver that ignored method names, a "query" naming collision between two unrelated rules, and an overly broad request-taint pattern duplicated across three rules) and two Windows-only infrastructure bugs (the bundled MCP server couldn't start on Windows at all; npm/tar needed platform-specific handling). All fixed at the root cause and locked in as permanent regression fixtures — a full re-scan of vercel/ai now reports zero findings.
Scoped a path to AST-based Python detection. A non-shipped proof-of-concept (spike/python-ast-poc/) confirms tree-sitter-python works as a pure-WASM (no native compilation) replacement for today's regex-based Python scanner, demonstrated against a real, previously-invisible gap: user input flowing through a class attribute (self.x = request.json[...]) into an LLM call. See ROADMAP.md for the full assessment.
Full details in CHANGELOG.md.
Full Changelog: v0.5.0...v0.6.0
SecureAI-Scan 0.5.0 is now available.
- Added Agent Skill scanning for SKILL.md files, including checks for hidden Unicode, injection phrases, and cross-skill shadowing.
- Added MCP010 for dynamic MCP server commands built from untrusted input.
- Improved DEP003 so advisories compare against actual declared package versions.
- Fixed several false positives in sensitive logging, RAG context injection, and system prompt leakage.
- Added regression coverage for CLI and dependency-guard behavior.
Install or upgrade:
- npm i -g secureai-scan
Fix Python false negatives on litellm, Bedrock, Cohere, Mistral, Ollama
This patch closes two false-negative bugs in the Python scanner surfaced by community testing: apps built on litellm, boto3 Bedrock clients not literally named bedrock, Cohere, Mistral, or Ollama were producing zero findings — even with --paranoid — because their call syntax wasn't recognized despite the SDK import itself being detected. Separately, AI001 (prompt injection) only looked 10 lines ahead of a request read for an LLM call, missing injection in any handler with realistic logic (logging, rate limiting, RAG lookups) in between.
AI001 now tracks request-tainted variables across the whole enclosing function instead of a fixed line window, and the LLM call-pattern list covers the additional SDKs. No new rules, no breaking changes — bug-fix only, verified against the exact repro cases with new regression tests.
v0.4.0 — OWASP ASI/MCP Top 10 mapping + MCP tool-poisoning detection
The first release mapped to all three OWASP AI security frameworks — the LLM Top 10 (2025), the Top 10 for Agentic Applications (2026, ASI01–ASI10), and the MCP Top 10 (2025) — plus three new rules that catch the pattern behind real-world incidents like the postmark-mcp backdoor and the WhatsApp MCP rug-pull.
Added
- Three-framework OWASP mapping — every rule tagged with LLM Top 10, ASI (Agentic), and MCP Top 10 IDs across terminal, Markdown, HTML, JSON, and SARIF output
- OWASP coverage matrix in
secureai-scan threat-model— shows exactly which of the 30 risks across all three frameworks are covered, which fired, and which are runtime-only concerns - MCP007 — invisible/bidi Unicode hidden in MCP tool names or descriptions (critical, proven)
- MCP008 — agent-directed injection phrases in tool descriptions (high, likely) — the WhatsApp MCP rug-pull / postmark-mcp pattern
- MCP009 — cross-tool shadowing, where one tool's description redirects calls meant for another (medium, likely)
- DEP003 — known-malicious/critically-vulnerable dependency advisories (postmark-mcp, mcp-remote CVE-2025-6514), checked offline on every scan — no flag required, covers package.json, requirements.txt, and packages launched from MCP configs
All new rules ship with both TypeScript AST and Python implementations.
Changed
--only-mcpnow also runs MCP007–MCP009--rulesaccepts DEP001–DEP003
v0.3.1 — False-positive & crash-safety fixes
Bug-fix release — no new rules, commands, or flags. Focused on cutting false positives in existing rules and hardening the scanner against crashes on edge-case input.
Fixed
- Python AI005 —
subprocess.run/eval/exec/os.systemsinks no longer flag on ordinary code just because a nearby variable was namedresult/output/etc. Now requires the file to import a known LLM SDK and traces a real assignment link from an LLM call before reporting atcritical; a name-only match without a confirmed link now reports at a lowerhigh/heuristic tier instead. - Python AI002 — sensitive prompt/response logging now also requires the file to import a known LLM SDK.
- Windows glob exclusion —
node_modules,dist,build, and policyskipPathsare now actually excluded from TS/JS scans on Windows (glob patterns were silently broken by backslash paths). - Crash safety — ts-morph symbol resolution is now wrapped so a single malformed/edge-case file can no longer abort an entire scan run.
- AI012 — dropped a bare-name fallback that flagged
JSON.parse(result)regardless of whether the value actually came from an LLM call. - AI007 (RAG context injection) — generic names like
context/resultsnow only count as RAG content when the enclosing function shows an actual retrieval call. - AI010 (indirect prompt injection) — fetch-derived variable detection now checks real call/property-access nodes instead of substring-matching, so values like
{ fetch: true }no longer false-positive. - VEC001 / VEC004 —
supabase/opensearchclients now require an explicit vector/embedding signal before flagging ordinary DB reads/writes. - AI-BOM — model-identifier matching (
o1/o3/o4) is now scoped to string literals, so plain variable names no longer show up as "OpenAI model referenced." - DEP002 — added the missing catalog entry (findings no longer render blank OWASP/fix tags).
- Report snippets — the source-line cache no longer folds path case on case-sensitive filesystems.
- Dependency guard — a registry-unreachable network failure now prints a warning instead of silently no-opping DEP001.
Note for CI users
Some previously-critical AI005 findings now report at a lower tier, and some previously-flagged AI007/VEC001/VEC004/AI010/AI012 cases no longer fire at all. If you pin --fail-on critical, expect your finding count to drop on upgrade — that's the intended effect of removing false positives, not a regression.