Supply Chain Security

1 findingMCP08MCP10ASI04CoSAI-T6CoSAI-T8CoSAI-T11MAESTRO-L4EU-AI-Act-Art-9AML.T0017#

Malicious & Typosquat Packages

1 finding
D3

Typosquatting Risk in Dependencies

HighMCP10-supply-chain

Server depends on 'expresss' (triple s) with Levenshtein distance 1 from 'express'

  1. 1Open the manifest and confirm the dependency `chai@4` is present. The scanner's similarity pipeline matched this name against the curated target `chalk` via the levenshtein-near classifier. If this dependency is an intentional internal fork or re-export, add it to `legitimate-forks.ts` so the finding will no longer fire.npm:chai@4Expect: Dependency npm:chai@4 is declared; it is NOT in the legitimate-fork allowlist at scan time.
  2. 2Recompute the Damerau-Levenshtein distance and Jaro-Winkler similarity between `chai` and `chalk` using the same primitives as the scanner. Concretely, the rule expects Damerau-Levenshtein ≤ 2 and Jaro-Winkler ≥ 0.80 (except for advisory-registry matches which skip the floor). Observed values: distance 2, Jaro-Winkler 0.848.npm:chai@4Expect: Damerau-Levenshtein distance between "chai" and "chalk" is 2. Jaro-Winkler is 0.848. The numbers agree with what the rule recorded.
  3. 3Open the package manifest at this RFC 6901 pointer and read the line. Confirm the package name recorded in the manifest is literally `chai` (not a spelling the build tool fuzzed to) and that no post-resolution rewrite turns this entry into the legitimate `chalk`.package.json/dependencies/chaiExpect: The manifest entry at package.json/dependencies/chai resolves to chai@4 — the exact name the scanner flagged.
  4. 4Open the npm page for `chai` and compare against the legitimate `chalk`. Check: publisher identity, publish date, weekly download count, repository link, postinstall script presence. A typosquat typically presents as: recently published, low download count, no repository link, optionally carrying a postinstall hook that executes code at install time.npm:chai@4Expect: Either the candidate is a legitimate publisher-authored alternative (in which case add to `legitimate-forks.ts`) or its metadata confirms the typosquat hypothesis (recent, unknown publisher, low downloads, suspicious scripts).
sourceexternal-content
npm:chai@4
Dependency npm:chai@4 is within Damerau-Levenshtein distance 2 of chalk (threshold 2).

Dependency names are external content resolved from public package registries. A near-miss to a popular canonical name is a supply-chain anomaly under ISO 27001 A.5.21 — the package manager installs whichever spelling is declared, with no built-in guard against lexically similar substitutions.

propagationdirect-pass
package.json/dependencies/chai
The manifest entry at /dependencies/chai directs the package manager to resolve and install chai@4. Resolution is purely string-matched against the registry — a typosquatted name installs whatever code the squatter published.
sinkcommand-execution
npm:chai@4
Malicious package `chai` executes attacker code in the build environment or at import time. Attack classifier: levenshtein-near. Target shadowed: `chalk`.
mitigationinput-validationabsent
package.json/dependencies/chai

Lockfiles pin versions but do not pin the spelling of the dependency name. The static analyser cannot confirm whether a typosquat-aware package firewall (Socket.dev, Snyk Advisor) is in the CI chain; the auditor must verify.

impactremote-code-execution
server-host

A developer installs `chai` by typo, copy-paste, or autocomplete. The package's postinstall hook runs during installation with the developer's or CI runner's credentials, or the payload executes on first import when the MCP server starts. An MCP server compromised this way delegates full tool authority to attacker code on every downstream agent interaction.

trivial
Confidence88%ISO-27001-A.5.21ISO/IEC 27001:2022 Annex A Control 5.21 — ICT Supply Chain Security
4 confidence factors
  • +0.1input-validation absentNo input-validation found — Lockfiles pin versions but do not pin the spelling of the dependency name. The static analyser cannot confirm whether a typosquat-aware package firewall (Socket.dev, Snyk Advisor) is in the CI chain; the auditor must verify.
  • +0.1target_distance_under_thresholdDamerau-Levenshtein distance 2 between `chai` and `chalk` is within the target's declared ceiling of 2. Combined with the Jaro-Winkler agreement check, this is the distance-only classifier — the most common class.
  • +0.02algorithm_agreement_moderateJaro-Winkler similarity 0.848 clears the agreement floor (0.80) but is below the high-agreement band — the finding stands but reviewer confirmation is advised.
  • -0.04legitimate_fork_allowlist_consultedThe candidate was not in legitimate-forks.ts at scan time. The rule records this explicitly so the finding can be dismissed by adding to the allowlist, with audit trail, if the reviewer confirms the dependency is a sanctioned variant.
Methodology5 tests · 4 frameworks
Technique
similarity
Tests (5)
  1. legitimate-fork-allowlist
  2. visual-confusable-replay
  3. scope-squat-detection
  4. numeric-version-suffix-strip
  5. algorithm-agreement-gate
Lethal edge cases (7)
  • Legitimate namespace fork — `lodash-es` is a real package within Damerau-Levenshtein distance 3 of `lodash`. A detector that fires purely on edit distance misclassifies it as a typosquat. The rule suppresses candidates listed in `legitimate-forks.ts` and down-weights candidates whose only extra content is a structural suffix like `-es`, `-fork`, `-pro`.
  • Visual-confusable graphemes in ASCII — `rnistral` differs from `mistral` by substituting `rn` for `m`. Pure Damerau-Levenshtein scores distance 2 but doesn't flag this as "near" `mistral` with high confidence. The rule re-evaluates every <=2-distance candidate through `visuallyConfusableVariants` to catch the RN/M, CL/D, VV/W cohort.
  • Scope-squat under a different scope — `@mcp/sdk` shadows the official `@modelcontextprotocol/sdk` via scope replacement rather than substring edits. Character-level Levenshtein would consider these far apart. The rule runs a scope-squat check on any dependency whose UNSCOPED tail matches the tail of a `scoped_official` target but whose scope differs (including no-scope).
  • Version-suffixed package — `react-18`, `webpack-5`, `python-3.12`. These are legitimate publisher-versioned aliases and must not be flagged. The rule treats numeric suffixes separated by `-` or `.` as non-material for the similarity comparison — the suffix is stripped before Damerau-Levenshtein evaluation.
  • Deprecated-official official rename — `request` is deprecated in favour of `got`, yet `request` remains a published package and many legacy servers still depend on it. The rule must NOT flag `request` as a typosquat of `got` (distance-wise these are far apart anyway, but the rule nonetheless documents this class to acknowledge the failure mode).
  • Author-internal name coinciding with a public near-miss — an org's private package `@acme/requestss` is three edits from public `requests`. The rule cannot distinguish private from public registries statically; it emits the finding with a `no_confirmed_malicious_record` factor (negative adjustment) so the reviewer sees that the finding is distance-only and can apply organisational context to dismiss.
  • Short-name collisions — `axios` has length 5. A Damerau-Levenshtein distance of 2 against `axios` produces many legitimate 3-5 character unrelated names (e.g. a greenfield utility called `axles`). The rule uses the target's declared `max_distance` (2 for short names, 3 for longer) and additionally requires a Jaro-Winkler similarity ≥ 0.80 before firing — agreement between two complementary algorithms is the filter against single-algorithm noise.
Confidence cap
unbounded
Frameworks (4)
  • EU AI ActArt.9Risk Management System
  • OWASP MCPMCP08Dependency Vulnerabilities
  • OWASP MCPMCP10Supply Chain Compromise
  • OWASP ASIASI04Agentic Supply Chain
Backing
  • Precision:
  • Recall:
  • Red-team fixtures: 4
  • CVE replays: none
  • Last validated:

Code Vulnerabilities

1 findingMCP03MCP05MCP07ASI02ASI05CoSAI-T3MAESTRO-L3EU-AI-Act-Art-15AML.T0054#

Server-Hardening Failures

1 finding
E1

No Authentication Required

MediumMCP07-insecure-config

MCP server accepts initialize handshake without any authentication token or API key

  1. 1Connect to the MCP server transport (streamable-http) without providing any credentials (no Authorization header, no API key query parameter, no mTLS certificate). Issue the `initialize` request followed by `tools/list`. If both succeed, authentication is genuinely absent.capability:toolsExpect: Server responds 200/OK to `initialize` and returns the tool list without a 401/403 or any WWW-Authenticate challenge.
  2. 2If the MCP server is fronted by a reverse proxy (nginx / envoy / Traefik / IAP), inspect the proxy configuration and confirm whether auth is terminated at that layer. If yes, document the proxy's auth strategy in an audit note; the finding can then be dismissed with provenance. If no, the server is the auth boundary and E1 stands.nginx.conf/server/locationExpect: Either the proxy enforces auth (dismiss with audit trail) or no proxy exists (E1 stands).
  3. 3Verify the server's bind address. A 127.0.0.1 bind is not a substitute for authentication — DNS rebinding (Jackson/Bortz/Boneh 2007) makes localhost reachable from any web page the user visits. Unauthenticated localhost MCP servers have been demonstrated-exploited in the wild.capability:toolsExpect: Bind address is 0.0.0.0 / a routable IP (direct network exposure) OR 127.0.0.1 (still exposed via DNS rebinding from a malicious web page).
sourceenvironment
capability:tools
Live connection to the MCP server over streamable-http succeeded with no credentials. `initialize` + `tools/list` returned without any WWW-Authenticate challenge.

An MCP server that answers tool enumeration without authentication trusts the network. Under modern threat models (CCS 2007 DNS rebinding, open cloud networking) no network is trustworthy.

sinkprivilege-grant
capability:tools
Full tool authority exposed without identity verification. Any client that reaches the transport can enumerate and (on systems that expose invocation) call every tool.
mitigationauth-checkabsent
capability:tools

No authentication mechanism present at the MCP server layer. Reverse-proxy-terminated auth may exist at a layer the scanner cannot observe; reviewer must confirm via the deployment diagram.

impactprivilege-escalation
server-host

Any network-reachable client can connect and invoke tools with the server's delegated authority. For localhost-bound servers, a malicious web page can still reach the server via DNS rebinding (Jackson/Bortz/Boneh 2007), making localhost no better than 0.0.0.0.

trivial
Confidence75%MCP-Authorization-2025MCP Authorization Specification (mid-2025 adoption)
2 confidence factors
  • +0.1auth-check absentNo auth-check found — No authentication mechanism present at the MCP server layer. Reverse-proxy-terminated auth may exist at a layer the scanner cannot observe; reviewer must confirm via the deployment diagram.
  • +0.2no_auth_confirmed_runtimeLive runtime observation: connection over streamable-http succeeded without credentials. This is not a heuristic — the scanner demonstrated the unauth posture directly.
Methodology3 tests · 6 frameworks
Technique
structural
Tests (3)
  1. null-connection-skip
  2. localhost-does-not-count
  3. proxy-layer-reviewer-note
Lethal edge cases (5)
  • Localhost-only binding is NOT a substitute for auth. Many MCP servers bind to 127.0.0.1 and assume that is sufficient. DNS rebinding makes localhost reachable from any tab in the user's browser. The rule fires on auth_required=false regardless of transport or bind address; the localhost assumption is called out in the impact narrative.
  • stdio transport. An MCP server running over stdio (the process launches the server and pipes to it) inherits the parent process's security boundary. For stdio-launched servers E1 is arguably not material — the parent process is the authentication. The connection metadata populated by the scanner only reaches E1 when a live network connection was made; for stdio-only servers E1 skips silently (connection_metadata=null).
  • "auth_required: false" but auth happens at a higher layer. Some deployments front the MCP server with a reverse proxy that terminates OAuth before the request reaches the server. The scanner cannot see the proxy; a false positive is possible. The verification step explicitly instructs the reviewer to confirm proxy-layer auth before dismissing.
  • connection_metadata is null. When no live connection was made, the rule cannot assert anything about the runtime auth posture. It MUST skip silently (AnalysisCoverage records the gap).
  • auth_required=true but auth is trivially bypassable. The scanner observes whether the server rejects unauthenticated connections, not whether the auth itself is strong. This rule does NOT cover weak-auth cases — that is outside E1's surface (H1 covers OAuth specifically; K6/K7/K8 cover token lifecycle).
Confidence cap
unbounded
Frameworks (6)
  • EU AI ActArt.15Accuracy, Robustness, and Cybersecurity
  • ISO 27001A.5.15Access Control
  • OWASP MCPMCP07Insecure Configuration
  • OWASP ASIASI03Identity & Privilege Abuse
  • CoSAI MCPCoSAI-T1Identity & Authentication Abuse
  • MITRE ATLASAML.T0055Unsecured Credentials
Backing
  • Precision:
  • Recall:
  • Red-team fixtures: 3
  • CVE replays: none
  • Last validated:

Tested cleanly

  • Prompt Injection24 rules tested cleanly
  • Tool Poisoning17 rules tested cleanly
  • Data Exfiltration15 rules tested cleanly
  • Authentication & Identity9 rules tested cleanly
  • Human Oversight6 rules tested cleanly
  • Audit & Logging5 rules tested cleanly
  • Multi-Agent Security1 rule tested cleanly
  • Protocol & Transport15 rules tested cleanly
  • Denial of Service7 rules tested cleanly
  • Container & Runtime10 rules tested cleanly
  • Model Manipulation8 rules tested cleanly
Cloaked Agent security findings — MCP Sentinel