scanner changelog

Static Analyzer Version History

The scanner reads every server's published source — never running it — to infer what it can do and surface security signals for review. This is what each version detects, and when it changed. A bump re-checks the whole catalogue with the new version, so older servers benefit without waiting for a new release.

current version v28
updated Jul 29, 2026
released 26 versions
method static · no-run

Every signal is an inferred review prompt, not a verdict — the analyzer reads published source without ever running it. How those findings feed capabilities and risk grades is on the methodology page; live analysis coverage and the per-source poll cadence are on the feeds page. Browse what the current version found across the catalogue on the code analysis page.

What it detects today

v28

The detector families running at the current version. Each raises findings tagged by confidence (attested / reported / inferred) and severity, which combine into a composite risk grade — see methodology.

  • capabilities

    Infers a server's permission surface — filesystem, shell, network, secrets, database, untrusted-input — from imports, call-sites, tool names, and input schemas. Six bits feed every signal below.

  • tool_poisoning

    Hidden instructions, zero-width / bidi unicode, ANSI terminal escapes (line-jumping), pseudo-tags, and base64-smuggled directives in tool names, descriptions, and schema fields.

  • exfil_combo

    A single tool — or a server's tools together — that both reads data (files, secrets, a database) and reaches the network. Severity follows what is read.

  • toxic_flow

    The lethal trifecta: untrusted-content ingestion + private-data access + network exfil reachable in one session. Checked per-tool, per-server, and across directly-connected servers.

  • tool_shadow

    A tool impersonating one exposed by a verified server (including homoglyph / look-alike names), and cross-server steering that points the agent at another named tool.

  • dangerous_code

    Committed secrets, dynamic exec, secret-to-network and secret-to-shell taint, suspicious endpoints, logged credentials, over-broad OAuth scopes, base64-decode-to-exec obfuscation, telemetry beacons, and clipboard sinks.

  • prompt_injection

    Hidden-channel instructions in the README, package description, and bundled skill prose an assistant reads before calling the server (indirect / second-order injection).

  • supply_chain

    Install-time hooks, bundled .vsix / .zip archives and editor extensions (opened and folded into the scan), dynamic require of a variable, transport posture (0.0.0.0 bind with no auth), and IDN-homograph hosts.

  • impersonation

    Typosquat (edit-distance) and homoglyph radar against popular and official names — a name one keystroke or one look-alike character away from a trusted package.

  • rug_pull

    Tool definitions tracked over time: an alert when a tool quietly gains a dangerous capability or a hidden instruction after you installed it.

Version history

newest first
  1. v28

    Detector evasions closed, two scan-stalling inputs bounded, and several false alarms narrowed

    performance fewer false alarms

    Two crafted inputs could make the scanner spend minutes on a single file instead of milliseconds, stalling every other scan behind it. A source file packed onto one very long line could take almost two minutes to permission-scan, and a malformed private-key block — one where the closing marker appears before the opening one — could take several minutes to reject. Both are now bounded. As part of the first fix, minified and bundled one-line files are no longer scanned for permissions, matching how every other detector already treats them: their contents are machine-generated and unquotable, so a permission inferred from one was never usable evidence. The permission surface of a server that ships only minified code may therefore be smaller than before; its danger, secret, and prompt scans are unchanged.

    Technical detail
    • The permission pattern loop now skips minified/bundled lines (the same lineLooksMinified guard the danger, manifest, transport-posture and exec-sink scanners already applied, and which this file's own aliased-import pass applied 65 lines earlier). Two 'untrusted' body-read patterns paired an unbounded argument run with an unbounded trailing run, which is quadratic when the trailing context never matches: measured 17.8s on a 400KB single line and 115.8s at the 1 MiB per-file read cap. Those patterns are additionally bounded to 200 characters per run — a real response-body read chain is short.
    • The PEM private-key block is now matched in three separate linear steps (locate the opening marker, search a bounded window after it for the closing marker, then require key material between them) instead of one expression pairing two lazy runs around the body class. The single-expression form left a roughly cubic search space: with the closing marker placed BEFORE the opening one the match can never complete, so the engine explored all of it — measured 4.8s at 2KB and 40.9s at 4KB of body, extrapolating to ~325s at the previous 8KB bound. Detection semantics are unchanged: a bare header mention is still not a finding, and a genuine committed key still is.
    • Several detectors could be silently disabled by attacker-chosen text. A shared guard treated the first `//` anywhere on a line as the start of a comment, so an ordinary URL string earlier on the same line suppressed every later match — across eight detectors, and real malware naturally puts its call-home URL and its decode-and-run sink in one statement. That guard is now aware of quoting, and `#` only counts as a comment in languages that have it (it was previously silencing JavaScript lines containing a CSS colour or a URL fragment). Findings in files whose PATH looks like a test or example are now shown at low confidence rather than dropped entirely, since a file's location is chosen by whoever publishes it and the file still runs when installed. A bundled skill script is no longer excused merely for sitting in a directory whose name resembles a threat-hunting toolkit, and a command wrapped in quotes inside a shell script is recognised as a command rather than as text.
    • Fewer false alarms: a tool description saying ‘use MCP tools to…’ — close to boilerplate in this ecosystem — no longer reads as an instruction to call a different named tool, and a list of long API operation names is no longer mistaken for an encoded payload. A network address containing a JavaScript spread (`{...opts}`) is checked normally again; previously that one token disabled the whole address check on the line, including the AWS instance-metadata address used for credential theft. Placeholder-credential recognition was extended from 5 of the 14 supported vendor prefixes to all of them, so an obvious demo token (npm, GitLab, HuggingFace, SendGrid, Stripe and others) is no longer reported as a committed secret. Tool names beginning show/ship/start/make/author/token no longer suppress the purpose-mismatch check by accident.
    • A deeply nested tool schema from a third-party server can no longer exhaust the stack while permissions are inferred; that walk is now depth- and size-bounded like its two siblings, and the surrounding fallback covers the whole derivation rather than only the JSON parse.
  2. v27

    Dependency parsing reaches beyond npm and PyPI, a code-file count, and a hidden-character scan gap closed

    capability model new detection

    The dependency graph and supply-chain surface previously came only from a server's npm or PyPI manifest, so a server written in Go, Rust, C#, Java, Kotlin, Swift, PHP, or Ruby — or a Python project declaring its dependencies in setup.py rather than requirements.txt/pyproject.toml — showed up as if it had no dependencies at all. The analyzer now reads each ecosystem's own manifest format and folds the declared dependencies into the same graph. Every analyzed server also now carries a language-agnostic count of source code files, independent of any one ecosystem's manifest. Separately, the check for invisible characters smuggled into a server's tool descriptions caught a wider set of blank, zero-advance code points than the matching check on READMEs, skill files, and agent-rule files an assistant reads before using a server; both checks now share a single character list so they can no longer drift apart, and the file-side scan flags those additional code points too.

    Technical detail
    • Dependency manifests parsed: Go (go.mod), Rust (Cargo.toml), C# (.csproj PackageReference), Java/Kotlin (pom.xml, build.gradle / build.gradle.kts), Swift (Package.swift), PHP (composer.json), Ruby (Gemfile), and Python setup.py (in addition to the existing requirements.txt / pyproject.toml parsing). Each parser extracts declared package names and feeds them into the same dependency graph and supply-chain checks as the npm/PyPI path.
    • Added a language-agnostic code-file count to the analysis output, derived from the scanned tree rather than any single ecosystem's manifest, so file-count-based signals aren't blind to non-JS/Python codebases.
    • Hidden-unicode parity: the character class is now defined once (HIDDEN_UNICODE_CLASS in src/analysis/text.ts) and consumed by BOTH the prompt/README/skill-file scanner and the tool-description/schema scanner, so the two can never diverge again. The file-side scan gained the code points it previously omitted: Mongolian vowel separator (U+180E), Hangul fillers (U+115F, U+1160, U+3164, U+FFA0), and interlinear-annotation marks (U+FFF9–FFFB). The existing context-benign exemptions and the same-character-scatter suppression are unchanged, layered on top of the shared class.
  3. v26

    Fewer false alarms on self-directed endpoints and clipboard; precision on purpose-corroborated services

    fewer false alarms

    Stopped flagging endpoint and OAuth-scope requests to a server's own declared vendor as dangerous — a Telegram bot hitting Telegram's API, or a Google Drive helper reading Drive, is doing its job, not leaking data — and suppressed the browser `navigator.clipboard` signal from webview and plugin UI contexts where clipboarding is user-initiated, not covert. Semgrep evidence paths are now stored repo-relative so re-scans from a different scratch location surface the same source locations.

    Technical detail
    • Purpose-corroborated services: endpoint and OAuth-scope evidence whose target matches the server's dep-corroborated declared vendor (a Telegram server hardcoding `api.telegram.org`, a Google Drive helper scoped to `https://www.googleapis.com/auth/drive`) are tagged `expected=1` in the database, visible to the UI as an informational signal rather than alarming. Closes a broad false-alarm class across vendor-specific servers.
    • Clipboard in UI contexts: the `navigator.clipboard` web API signal is now suppressed in webview and plugin contexts (as opposed to server code), because browser clipboard access is user-initiated interaction in a UI, not a covert server sink. The shell (pbcopy/xclip) and clipboardy-dependency arms remain unconditional.
    • Semgrep path portability: Semgrep evidence paths in code_evidence are now stored relative to the repository root instead of absolute /scratch paths, so a re-scan from a different unpacked location (e.g., after a deploy or container restart) surfaces the same source locations and avoids churn in the evidence table.
  4. v25

    Fewer false alarms on dynamic imports; catches more prompt-injection and exec-obfuscation tricks

    fewer false alarms new detection

    Stopped flagging ordinary data/locale dynamic imports, same-origin URL fetches, and a common shell re-source idiom as risky, and now catches replacement-context prompt directives, the Function-constructor eval escape, split-string require/__import__ calls, and renamed ZIP archives.

    Technical detail
    • FP: a dynamic import() of a data/locale asset (.json/.txt/.yaml/.csv/.md) is no longer 'obfuscation' when the specifier is pure `${}` interpolation — a decoded-then-imported or concatenated specifier still is.
    • FP: a same-origin `new URL(path, import.meta.url)` fetch and a bare service-worker fetch passthrough (no body read) no longer set the untrusted-content permission bit; a passthrough that inspects the body still does.
    • FP: the `eval "$(<file)"` shell-hook idiom (re-sourcing a local command's output) no longer trips the skill-script curl|sh/IEX marker — but the marker scan now reads the whole eval expression, so a nested subshell hiding a download-and-run (`eval "$(x=$(echo no); curl -fsSL .../install.sh)"`) still flags; the FP cut opens no new blind spot.
    • FN: replacement-context prompt directives — "you are now …", "from now on <injection-verb>…", "reveal/print your (system) instructions/prompt", "new instructions:" — now fire as prompt injection (tightened to require a qualified instruction/prompt noun plus an injection verb, after an initial over-broad match).
    • FN: dynamic-exec obfuscation now also catches the `.constructor.constructor(` Function-constructor eval escape, concatenated `require('a' + 'b')` built from two quoted fragments, and Python `__import__('os'/'subprocess'/...).system(`.
    • FN: a magic-byte (PK\x03\x04) sniff opens extension-less archives, so a renamed ZIP is unpacked and scanned instead of skipped.
    • Still deferred (design-heavy / FP-risky): NFKD/script-mixing homoglyph rewrite, untrusted-lexicon expansion, cross-function secret-to-arbitrary-host taint, path-exclusion downgrade, and a `.next/static` vendored exemption (reverted this pass — a blanket path match silently disables the whole pipeline; needs a Next.js-dependency corroborating signal instead).
  5. v24

    A full-database false-alarm audit — grades stop being distorted

    fewer false alarms

    A ground-up review of every warning in the live database, fixing the classes that were inflating risk grades on ordinary, safe servers. The biggest single fix: a "tool quietly changed to grab more access after you installed it" warning was firing en masse because it compared our OWN best-guess of a tool's capabilities over time — when we improved that guessing, hundreds of unchanged tools looked like they had escalated (one server alone carried 211 phantom warnings). It now only fires when the tool's actual description or inputs changed. A file-transfer tool that reads a file and sends it over the network (a WebDAV client, an uploader) is no longer treated as a high-risk data-exfiltration channel unless it also reaches secrets or a database. Tools honestly named for what they do — `ssh_exec`, `list_credentials` — are no longer flagged as "innocent-looking name hiding a dangerous capability". A tool whose name is entirely generic vocabulary (`execute_command`, `write_file`, `list_sessions`) can no longer be accused of impersonating another server. And a long list of "found a secret / dangerous code" warnings that were really looking at test fixtures, vendored dependencies, build scripts, security-training material, or documentation examples have been quieted — while the same checks still fire on the real, shipped server code.

    Technical detail
    • Rug pull (tool changed after install) — an escalation now requires the tool DEFINITION TEXT (name + description + input schema) to have changed between versions. A permission-guess delta with identical text is our classifier drifting, not the author acting: one vocabulary change had minted 211 phantom "gained secrets" findings on a single Smithery catalog. More than 10 tools "escalating" in the same window on one server now collapse into a single catalog-wide review prompt instead of flooding the feed.
    • Exfiltration combo — a single tool that reads the filesystem and reaches the network is now MEDIUM on its own (the ordinary file-transfer shape — WebDAV/upload/get_file — was 344 of 433 high findings); HIGH now requires a second read/ingest leg (secrets, a database, or untrusted-content ingestion) that widens the take beyond the files the tool was pointed at.
    • Capability-vs-purpose — the shell-capability announce test now flattens snake_case first, so `ssh_exec` / `send_command` / `get_command_history` (which honestly name what they do) are no longer "benign-looking", and a tool whose name DECLARES credential handling (`list_credentials`, `get_credential`) is no longer flagged for carrying the secrets bit. Separately, the word "command" next to a chat platform (Discord/Telegram/Midjourney) is read as a slash/bot command, not a shell spawn — which had put a false shell capability across whole image-generation catalogs.
    • Tool shadowing — a tool whose name is made ENTIRELY of universal vocabulary (`execute_command`, `write_file`, `list_sessions`, `all`, `terminal`) can no longer be flagged as impersonating a "verified" server; that shared dialect isn't an impersonation tell. The distinctive-name, sensitive-name, and look-alike-character (homoglyph) paths are unchanged.
    • Dangerous code — `new Function(code)` is now a MEDIUM review note, the same tier as `eval(code)` (229 servers' only high finding was a bare Function constructor — expression evaluators, schema compilers); the malware "decode a blob then run it" shape still reaches high separately. The webpack `new Function('id','return require(id)')` bundler shim is recognised as benign glue. A credential-in-a-log-line embedded inside a string literal (code carried as data) or in a build `scripts/` file no longer flags.
    • Committed secrets — key material inside vendored dependency trees (`vendor/`, `third_party/`, Perl `t/`), sample/example certificates, and scanner-output reports (CodeQL/SARIF/CVE dumps) is no longer reported as a shipped secret; and a committed `.env` no longer flags on stock default values (`postgres`/`admin`), localhost connection strings, or when it sits under a sample/example directory (including the common `.env.exampe` typo).
    • Bundled scripts — a `curl | sh` or PowerShell `IEX` string echoed inside a print statement (an install instruction) or a code comment no longer flags, and a skill that IS threat-detection content (a YARA / Sigma / Splunk rule set, a malware field guide) is recognised as documenting attack shapes rather than performing them.
    • Hidden text in prompt/skill files — a run of the SAME zero-width character (a docs-generator heading anchor, a copy-paste artifact) or a cluster of mis-encoded bytes (mojibake) is no longer read as smuggled text; a genuine hidden channel needs at least two DISTINCT invisible characters, or a text-reordering control. Hidden-content warnings in test / fixture / benchmark / vendored files are downgraded to low (an evaluation corpus legitimately contains sample payloads). And the "instructs the model to send a secret somewhere" check is suppressed when the surrounding prose is defensive ("do not send…", "this prevents the token from leaking") or the whole file profiles as security guidance (a pentest playbook, a malware analysis guide).
    • Environment-secret-to-network (Semgrep) — when this finding is corroborated by another in-house danger signal it now escalates to MEDIUM rather than high: reading an API key from the environment and calling the vendor's own endpoint is what every authenticated API client does.
    • Permissions — repo `scripts/` directories and build-hook config files (electron afterSign, forge/vite/rollup/webpack config) no longer contribute to a server's capability profile: their `child_process` and network calls run at build or publish time on the maintainer's machine, not in the served runtime. And the untrusted-content-ingestion bit no longer fires on a same-origin relative `fetch('/api/…')` — a bundled dashboard talking to its own backend, not third-party ingestion.
  6. v23

    Precision + reach: quieter clipboard/docstring noise, new eval sinks, code-aware trifecta

    fewer false alarms new detection

    A pass that both quiets three false-alarm classes and catches more real risk. The biggest noise cut: a copy-to-clipboard button in a bundled web dashboard or desktop app is no longer mistaken for a server secretly reading your clipboard — that check now understands it's looking at browser/UI code, not the server. Example "exec()" / "eval()" mentions inside Python documentation text no longer read as running code, and a "listen on all interfaces" setting inside a build or dev-server config file (Vite, webpack, docker-compose) is no longer treated as the server exposing itself without a password. On the detection side: the classic global-scope eval trick `(0, eval)(…)`, running code via `setTimeout("…string…")`, and a module name glued together to hide it (`require('child_' + 'process')`) are now caught; a harmless bundler helper (`new Function('return this')`) is no longer flagged. Finally, the "lethal trifecta" check (a tool that can read private data, take in outside content, and reach the network) now also considers what the published source code actually does, not just the tool descriptions — closing a blind spot across roughly 700 servers whose risky combination was visible only in their code.

    Technical detail
    • Clipboard — the browser `navigator.clipboard` signal is suppressed in a browser-context file (a .jsx/.tsx file, a path under a frontend directory like web/ frontend/ renderer/ dashboard/, or a file that touches the DOM / imports a UI framework). `navigator.clipboard` does not exist in a Node server, so its presence is browser UI, not a covert server sink — this was the single largest evidence class (~6.3k rows). The genuine server-side arms (pbcopy/xclip shell calls, the clipboardy dependency) are unchanged.
    • Dynamic exec — eval()/exec() sitting inside a Python triple-quoted docstring (multi-line documentation prose, including reStructuredText ``exec()`` inline literals) is no longer flagged; the single-line string guard could not see a multi-line docstring. A real exec() call outside a docstring still flags.
    • Transport posture — a 0.0.0.0 bind inside a build / dev-server / container config (vite/webpack/rollup/next/… .config, docker-compose, Dockerfile) is no longer reported as "network-listening, no auth": that is dev tooling, not the MCP server's own listen.
    • New sinks — indirect eval `(0, eval)(x)` (runs in global scope, dodges a bare-eval grep) and `setTimeout`/`setInterval` called with a STRING body (eval-equivalent) are now flagged; a concat-obscured `require('child_' + 'process')` is caught by the obfuscation constructed-specifier detector; and the `new Function('return this')` global-this bundler shim is suppressed as benign glue.
    • Lethal trifecta — detectToxicFlow now folds in the CODE-derived permission surface (what the analyzed source actually imports/calls) alongside the tool-description surface, so a server whose third leg (a private-data read, or ingesting a fetched response body) is proven only in source still trips the union. Scoped to the trifecta only, server-level only, and stays a medium review prompt tagged as source-derived — a pure recall gain, ~737 servers (see ADR 0006).
    • Refactor — the danger analyzers were decomposed from a single 1100-line module into per-detector modules under src/lib/analyzers/dangers/ (secrets / execSinks / network / credLog / clipboard / transportPosture / skillScripts) with the per-file dispatch and shared file-context in dangers/index.ts. No behavior change from the split itself.
  7. v22

    Three more false-alarm fixes — a live-data precision pass

    fewer false alarms capability model

    A sweep of the live findings to quiet three warnings that were firing on ordinary, safe code. A Python multi-line import written across several lines — `from pydantic import (` and the like — is no longer mistaken for a hidden dynamic import; that one pattern was over 90% of the "obfuscated dynamic import" notes. A server reading its own configured API key from the environment to call its own service (the universal shape of every authenticated API wrapper) no longer reads as the high-severity "reads a secret and sends it out" combo — only an arbitrary filesystem read flowing to the network does. And keys committed inside test data and fixture folders whose names the scanner didn't previously recognise (testdata/, test_assets/, test-fixtures/, unittests/, and bare test.js / tests.rs files) are now treated as fixtures rather than shipped credentials.

    Technical detail
    • Obfuscation — the dynamic require()/import() detector now runs on JavaScript/TypeScript only and fires only when the imported path is CONSTRUCTED (string concatenation, a template interpolation, or a decode call such as atob). A Python `from x import (…)` multi-line static import is not an import() call and no longer flags; a bare `await import(distEntry)` of a plain variable is ordinary code-splitting and no longer flags. (~55k of ~60k findings cleared.)
    • Capabilities — a NAMED credential environment variable (OPENAI_API_KEY, GITHUB_TOKEN, …) is the credential the operator handed the server to authenticate; reading it no longer counts as a sensitive read for the exfiltration combo, so "network + secret" drops from high to a low review note (~550 high findings reclassified). Credential-FILE harvesting (~/.aws, ~/.ssh, other apps' .env) rides the filesystem bit and stays high. The secret-capability pattern also now requires the credential word to be the trailing token of an UPPER_SNAKE name, so config like MAX_TOKENS / TOKEN_OPTIMIZER_MODEL and PUBLIC/PUBLISHABLE keys no longer trip it.
    • Dangerous code — the test/example/fixture path filter now recognises compound and bare-basename test directories it previously missed (testdata/, testing/, unittests/, test_assets/, test-fixtures/, picky-test-data/, and bare test.js / tests.rs / test.pem), so dummy keys and fixtures in those trees are no longer reported as committed secrets. Bounded so real source like src/testRunner.ts, latest/ and attestation/ stays analyzed.
  8. v21

    Fewer false alarms — a live-data accuracy pass

    fewer false alarms capability model

    A sweep of the live findings to quiet warnings that were firing on ordinary, safe code. Tools whose job is to run a database query or some code ("execute SQL", "run plugin code") or to "process" data are no longer mistaken for running shell commands — that one homonym was the single biggest source of the "a harmless-looking tool can reach a shell" warning. A tool that downloads or fetches a file to disk is no longer read as if it were freely reading your filesystem and sending it out, which was the biggest "reads and sends your files" false alarm. A committed .env file is now only called out when it actually holds a credential (a value under a key like PASSWORD or API_KEY) — shipping plain configuration like a port number no longer trips it. And the Function-constructor / sandbox-eval warning is reserved for the genuinely risky case of running a variable; a fixed, self-contained snippet is now a gentler review note. Finally, these accuracy fixes now take effect across every already-scanned server immediately, instead of waiting for each one to be re-fetched.

    Technical detail
    • Capabilities — retired the `exec`/`execute`/`process` shell keywords (run-a-query/code/workflow homonyms: `execute_sql`, `dynamodb_batch_execute`, `figma_execute`, "process the data", "the onboarding process"). They drove the dominant `purpose_mismatch` benign-name-carries-shell false positive (~388 of 759 live findings cleared). A genuine shell tool still earns the bit from command/cmd/shell/terminal/bash/spawn/subprocess, and a real `child_process` call is still caught in source.
    • Capabilities — an INBOUND transfer (`download_file`/`download_audio`→destination_dir/`fetch_artifact`→output directory) now sheds its `fs` bit: pulling a remote resource and writing it to disk is a write target, not the read leg of an exfiltration channel. Cleared the dominant `exfil_combo` (filesystem + network) false positive (~265 single-tool findings removed); an OUTBOUND `upload_directory`/`backup_files` keeps `fs` because reading many local files and shipping them IS the exfil shape.
    • Propagation — tool-safety detection re-derives each tool's inferred capability mask from the current classifier at detection time, rather than trusting a mask captured before a fix shipped. Every classifier correction (this release's and prior ones) now reaches the whole back catalogue on the next risk computation, with no re-scan.
    • Dangerous code — a committed `.env` is flagged HIGH only when a populated, non-placeholder value sits under a secret-NAMED key (PASSWORD/TOKEN/SECRET/API_KEY/…); a `.env` of pure configuration (PORT/NODE_ENV/HOST/LOG_LEVEL) no longer reads as a committed secret. A real vendor token under any key name is still caught line-by-line. PUBLIC/PUBLISHABLE keys are excluded as public-by-design.
    • Dangerous code — `new Function()` / `vm.runInNewContext()` with an all-string-literal static body (a templating / eval-lite helper) is now medium rather than high; a call that runs a variable or interpolated expression (opaque input) stays high, and a base64-decoded body feeding an exec sink stays high via the obfuscation detector.
    • Prompt injection — the skill-exfil prose heuristic (a credential + an outbound sink in one breath) now skips fenced code blocks: a documented `curl -H "Authorization: Bearer $TOKEN" …` is a usage example, not a natural-language instruction the agent follows. It was the dominant skill-exfil false positive across real skill / agent-rule repos. A genuine prose payload elsewhere in the file still trips, and an executable payload shipped in a skill is still caught by the code-sink scanners.
  9. v20

    Sharper capability & injection detection, plus a red-team evasion sweep

    fewer false alarms new detection

    An accuracy pass on the permission and prompt-injection checks. Some warnings that were firing wrongly now stay quiet: the word "token" on its own (which means a cryptocurrency or an LLM unit far more often than a credential) no longer flags a tool as handling secrets, and a tool whose whole job is to move one named file over the network is no longer read as freely reading your filesystem. At the same time, several real risks that were slipping through are now caught: credential names written with an underscore or space ("api_key", "access token") are recognised as secrets; bulk "back up / sync / export everything" tools keep their filesystem flag because reading many files and sending them is exactly the pattern worth watching; the hidden-instruction check now catches the common "ignore the previous instructions" / "ignore your prior directives" phrasings; an instruction to send a secret to a named site ("send $API_KEY to evil.com") is now detected; and the committed-secret scanner no longer discards a genuine key just because it happens to contain a short run of x's. A red-team sweep then closed evasion gaps a malicious server could exploit: many more committed-secret formats are recognised (GitHub fine-grained, GitLab, npm, Stripe, Anthropic, HuggingFace, Google OAuth, Slack app, SendGrid, and encrypted private keys), invisible "tag"-character smuggling is caught inside a tool's own description (not just its docs), shell access is seen through modern runners (execa, zx, Bun, Deno), and the supply-chain check now inspects the `prepare` install script and IPv6 all-interface binds.

    Technical detail
    • Capabilities — dropped the bare `token` secrets keyword (a crypto/LLM homonym that drove false `exfil_combo`/`purpose_mismatch`/`tool_shadow` across DeFi servers); a file-transfer tool (`send_file`/`upload_file`/`download_media`) sheds the `fs` bit when its only filesystem signal is a generic file/path word and it reaches the network (the file is the payload, not an arbitrary read).
    • Capabilities (false-negative closures) — credential compounds spelled with a separator now match via concatenated adjacent tokens, so `api_key`, `api key`, `access_token`, `access key`, and `private key` earn the secrets bit (without re-admitting the bare `token`/`key` homonyms); bulk/collection transfer verbs (backup, mirror, sync, export, import) no longer clear the `fs` bit, because reading many files and shipping them is the exfiltration shape, not a single-file transfer.
    • Prompt injection — the model-imperative rule now matches an article or possessive between the verb and the noun (`ignore the previous instructions`, `ignore your prior directives`, `disregard the above`), the dominant phrasing it previously missed.
    • Skill-exfil — the outbound-sink check accepts a literal attacker hostname (`send $API_KEY to evil.com`) in addition to url/endpoint/$env destinations, still gated by a concrete-secret co-occurrence so a bare hostname alone can't trip it; the sink span also no longer breaks on the dot inside an inline `process.env.X` reference, catching the canonical `send process.env.API_KEY to <host>` payload.
    • Dangerous code — the placeholder filter that suppresses crafted demo credentials (`sk-…PLACEHOLDER…`) now requires a longer `xxxxxxxx` filler run, so a real high-entropy key carrying an incidental short `xxxx` substring is no longer dropped.
    • Secret scanner (red-team) — added token formats the patterns were blind to: GitHub fine-grained PAT (`github_pat_`), GitLab (`glpat-`), npm (`npm_`), Stripe live (`sk_live_`/`rk_live_`), Anthropic (`sk-ant-`) and OpenAI service-account (`sk-svcacct-`) keys whose hyphenated bodies the old `sk-` pattern stopped at, HuggingFace (`hf_`), Google OAuth client secret (`GOCSPX-`), Slack app token (`xapp-`), SendGrid (`SG.`); plus encrypted PKCS#8 PEM private-key blocks.
    • Tool poisoning (red-team) — the Unicode Tags block (U+E0000–E007F) used to smuggle an invisible directive is now flagged in tool names/descriptions and schema fields (previously README/skill prose only); the hidden-character set also gained the Mongolian vowel separator, Hangul fillers, and interlinear-annotation marks.
    • Capabilities (red-team) — the shell bit is derived from modern process runners (execa, zx's tagged template, `Bun.spawn`, `Deno.Command`), and the secrets bit from `os.getenv(...)` and destructured `const { API_KEY } = process.env`. The separator-credential matching was also tightened so a `private key-value store` / `keyspace` data-structure name is not misread as a secret.
    • Supply chain / transport (red-team) — install-hook scanning now covers `prepare`, `prepublishOnly`, and `prepack` (npm runs `prepare` on a plain install — the modern `postinstall` substitute), and the all-interfaces bind check now matches the IPv6 wildcard (`::` / `0:0:0:0:0:0:0:0`).
    • Confused deputy (red-team) — the capability-vs-purpose check now flags a bare-noun-named tool that carries a shell capability (`weather`, `invoice` — names that announce nothing and slipped the benign-verb gate), while still ignoring honestly-named action tools (`run_command`, `deploy_service`).
    • Loose schema (red-team) — command/code-injection params are matched on the separator-stripped name (so `shellCommand` / `cmd_line` / `bash_script` no longer evade by renaming), and the schema walk now descends `additionalProperties`, `anyOf`/`oneOf`/`allOf`, and `$defs` to find a param hidden by nesting.
    • More evasions closed — a base64 payload wrapped across whitespace (chunked under the contiguous-run threshold) is now flagged; cross-server steering catches `route`/`delegate`/`forward … to/through` phrasing, not just call/use; and the bundled-archive opener handles the wider ZIP family (`.jar`, `.crx`, `.xpi`, `.nupkg`, `.whl`).
  10. v19

    Fewer false alarms, plus several new checks

    fewer false alarms new detection performance

    The largest update so far, in three parts. First, fewer mistaken warnings: the deep scan's most common false alarm — a server reading an API key from its environment and calling the very service it exists to talk to, which is normal — no longer counts as a serious finding unless something else genuinely backs it up, and two related checks stopped flagging ordinary tools that simply use a secret. Second, new checks: hidden terminal control codes in tool text, look-alike names that impersonate a popular tool using foreign letters shaped like ordinary ones, tools that take in outside content and can also send data out, risky commands hidden behind renamed imports, network servers that listen on every network with no sign of a login check, analytics trackers and clipboard access, disguised code that decodes and runs itself, and instructions smuggled into a tool's hidden default values. Third, speed: the heavy deep scan no longer repeats when a server's code hasn't changed, so the whole catalogue is re-checked faster after an analyzer update.

    Technical detail
    • Demoted the Semgrep env-secret-to-network finding from high to a low review prompt by default — reading an API key from the environment and calling the vendor's own HTTPS endpoint is the ordinary shape of an API-wrapper server. It re-escalates to high only when a real in-house danger corroborates it (a committed secret, dynamic exec, lethal-trifecta, or tool-poisoning signal on the same server).
    • Stopped treating a benign-named tool that merely reads an environment secret as high-signal: purpose-mismatch and tool-shadowing now reserve the high tier for an actual shell capability.
    • New detections — ANSI / terminal-escape sequences hidden in tool text (line-jumping); look-alike names built from Cyrillic, Greek, or full-width letters, folded to ASCII before the shadowing and typosquat checks; tools that read a network response body (the lethal trifecta's untrusted-input leg); shell access hidden behind renamed imports; network servers that bind every interface (0.0.0.0) with no detected authentication; analytics/telemetry beacons and clipboard access; base64-decode-then-execute obfuscation and dynamic require of a variable; instructions smuggled into a tool's schema default; and a rug-pull alert when a tool newly gains network plus untrusted-input capability.
    • Performance — the heavy taint pass (Semgrep) is skipped on unchanged source via a separate rules-version key, and the in-house scanners share a single file-tokenisation pass, so a version bump re-checks the whole catalogue faster and with less load on the worker.
  11. v18

    Deep-scan now ignores tests and build scripts

    fewer false alarms

    The optional deep code scan flags when a secret read from the environment flows into a network call. It was reporting these everywhere, including test files, examples, and build or release scripts — where using a token to reach an API is the script's own job, not the server's behaviour. It now skips the same non-shipped files the main analyzer already ignores, removing the largest source of mistaken warnings from this check.

    Technical detail
    • The Semgrep env-secret-to-network taint check now applies the same path exclusions as the in-house baseline — test/example/fixture/doc files, vendored or generated code, and build scripts/ — because a token reaching an API inside a smoke test or seed script is the script's job, not the server's runtime behaviour. Was the single largest Semgrep false-positive class.
  12. v17

    Fewer false alarms for logged secrets

    fewer false alarms

    The analyzer warns when code looks like it writes a secret into a log, where it could leak. This release stops most of the mistaken warnings. It now considers only what is actually being logged, and it understands the common harmless cases, such as recording whether a key is present rather than the key itself, or printing a masked or shortened version.

    Technical detail
    • The credential-in-log check now scopes to the log call's own arguments — a credential in a preceding guard such as if (token) log('msg') is no longer treated as logged.
    • It recognises non-value shapes as safe: presence tests (Boolean(token), !!token), predicate branches (token.startsWith('sk-') ? …), derived members (apiKey.keyId, accessTokenExpiry), and masking/truncation (api_key[:8]).
  13. v16

    Smarter .env and private-key checks

    fewer false alarms

    Two checks for committed secrets were firing too easily. An .env file is now flagged only when it actually holds real values, because an example file full of blanks or placeholders gives nothing away. A private-key warning now requires a complete key to be present, not just a line that mentions one, which security tools and tests routinely include on purpose.

    Technical detail
    • A committed .env is a leak only when an assignment carries a populated value — blank or placeholder-only assignments are de-facto templates.
    • A private-key finding now requires a full PEM block with real base64 material, not a bare -----BEGIN PRIVATE KEY----- header, which appears in detector patterns, redaction fixtures, and key-shape tests on purpose.
  14. v15

    Allowing a server's own service

    fewer false alarms

    Many servers exist to talk to one particular service, so a Telegram bot contacting Telegram, or a Discord bot posting to Discord, is simply doing its job. The analyzer now recognises when a built-in address matches what the server is for, and no longer treats it as suspicious.

    Technical detail
    • Purpose-aware endpoint filtering: a hardcoded call-home host that IS the server's declared vendor (a Telegram server hitting api.telegram.org, a Discord server posting to its own webhook API) is the package's purpose, not a covert sink. Filtered at evidence-persist time so it never reaches the grade.
  15. v14

    Fewer false alarms after a full review

    fewer false alarms

    A broad review of earlier findings removed several recurring false alarms. The analyzer now ignores code that merely ships inside a project but was written by others, such as bundled dependencies and test fixtures, and it does a better job of telling a genuinely risky pattern apart from ordinary code that happens to resemble one.

    Technical detail
    • Permission and tool scans now skip vendored (node_modules) and test/example files — a committed dependency tree and test harnesses were inflating the permission mask.
    • JS exec( / eval( require a bare call (RegExp.prototype.exec was about a third of all shell evidence; $$eval and .eval() are library APIs); dynamic-exec skips string literals, JSDoc, the dynamic-import shim, and literal __import__('x').
    • Secret scan skips harness / playground / fixture-generator files, SAST rulesets, throwaway-stem tokens, and .env.test / .env.ci; hidden-unicode exempts emoji-sequence ZWJ and RTL marks.
  16. v13

    Ignoring keys that are meant to be there

    fewer false alarms

    Stopped reporting keys that appear where they are expected and harmless, such as documentation, example files, and the rule files that other security scanners ship. It also stopped treating a public Firebase web key as a secret, since that kind of key is designed to be shared.

    Technical detail
    • Secret scanning now skips generated .d.ts / .map files, benches/ and corpus/ dirs, documentation prose, and secret-pattern libraries (gitleaks rulesets, redaction / PII-recognizer modules), and drops public Firebase web API keys (project ids, not credentials).
  17. v12

    First big noise cleanup

    fewer false alarms

    The first major pass at reducing noise. The analyzer began skipping machine-generated and bundled code, ignoring well-known example and placeholder keys, and reporting risky shell or eval patterns only when they genuinely do something dangerous rather than something routine.

    Technical detail
    • Began skipping machine-generated and bundled code (node_modules, *.min.js, *.bundle.js), ignoring AWS-docs example keys and placeholder secret lines, and gating shell / eval findings on a genuinely dangerous payload rather than a routine call.
  18. v11

    Looking inside bundled archives

    capability model

    Some packages ship a zip or editor-extension archive inside them. The analyzer now opens those archives and examines what they contain, so anything tucked away in a bundled file gets the same scrutiny as the rest of the source.

    Technical detail
    • Bundled .vsix / .zip archives in the tree are opened (pure-JS fflate) and their entry source is folded into analysis — env-to-exfil, secrets, dynamic-exec, and extension manifests inside the archive get the same scrutiny as the outer source, not just the outer manifest.
  19. v10

    Tracking untrusted input

    capability model new detection

    Added a new dimension to how a server's permissions are described: whether it takes in untrusted input. This lets the analyzer recognise the dangerous combination of reading untrusted data, holding secrets, and reaching the network, which is the classic recipe for data theft.

    Technical detail
    • Added the sixth permission bit, untrusted (the injection-entry axis), so the lethal-trifecta detector can see all three legs at once: untrusted-content ingestion + private-data access + network reach.
  20. v9

    Scanning bundled editor extensions

    new detection

    Detects editor-extension files, such as VS Code manifests and recommended-extension lists, that are bundled into a package, so their behaviour is reviewed rather than overlooked.

    Technical detail
    • Detects bundled editor-extension files — VS Code manifests and .vscode/extensions.json recommendation lists — so their behaviour is reviewed rather than overlooked.
  21. v8

    Reading the listing text for hidden instructions

    new detection

    An assistant often reads a project's README or description before using it, which makes that text a place to hide instructions aimed at the assistant. This release scans the listing text for those hidden prompts.

    Technical detail
    • Scans the README and package.json description an assistant reads before using a server for hidden-channel injection markers — indirect prompt injection in the listing text rather than the tool schema.
  22. v7

    Flagging risky bundled scripts

    new detection

    Flags an executable script bundled alongside a skill when it does something risky, such as downloading and running code from the internet, since an assistant could be instructed to run it.

    Technical detail
    • Flags a suspicious executable bundled inside a skill directory — remote-exec or pipe-to-shell patterns an assistant could be instructed to run.
  23. v6

    Logged secrets and broad permissions

    new detection

    Added two checks: one for code that writes a secret into a log, and one for requests for unusually broad access, such as full account or full drive permissions.

    Technical detail
    • Added the credential-in-log check (a secret value written into a log or print sink) and the over-broad OAuth scope check (a full-account or full-drive scope request).
  24. v5

    Spotting data exfiltration in skills

    new detection

    Flags a skill whose instructions both reference a secret and point to somewhere data could be sent, the pattern behind skills designed to quietly leak information.

    Technical detail
    • Flags a skill whose prose both references a credential and names an outbound sink — the ToxicSkills exfil pattern in plain skill-file text.
  25. v4

    Smarter call-home detection

    fewer false alarms new detection

    Improved the check for code that contacts an outside address. It now ignores local and private addresses that cannot reach the wider internet, and pays special attention to cloud metadata addresses, a known target for credential theft. It also stopped scanning test and example files, which are full of harmless sample addresses.

    Technical detail
    • Endpoint detection now drops non-routable addresses (loopback / private / reserved / TEST-NET), promotes cloud-metadata addresses (a known credential-theft target), and stops scanning test / example / fixture / doc files full of sample addresses; the hidden-prompt scan ignores a leading BOM.
  26. v1 to v3

    Foundational analyzers

    capability model new detection

    The first versions of the scanner. They read a server's published source to work out what it can do (touch the filesystem, run shell commands, reach the network, use secrets or databases), list its tools and dependencies, work out how it communicates, and raise the first danger signals, including committed secrets, dynamically executed code, and suspicious outbound addresses.

    Technical detail
    • The first analyzers read a server's published source — never executing it — to infer capabilities (filesystem, shell, network, secrets, database), enumerate tools and dependencies, classify transport (stdio / http / SSE / streamable-http), and raise the first danger signals: committed secrets, dynamically executed code, and suspicious outbound addresses.