▣ Defensible Posture
No single layer is sufficient — and the cryptographic layer (signing) is the weakest link precisely where people assume it's strongest.[31] The posture isn't a product; it's a discipline.
snapshot the contract
pin to content, not the reference
re-hash on every update
never trust an install-time approval to hold
01
Two threat models, two test rigs
one word, two failure classes
◧ Functional regression
Tool schema or behaviour drifts → the agent calls it wrong and nothing throws. Triggered by your refactor, an SDK bump, or a Claude Code version.[6]
caught by · snapshot · conformance · behavioural tests
◨ Rug-pull / supply-chain
A definition or bundled code silently changes after you approved it — trust accrues across versions, malice ships in one.[17] MCP has no native re-approval on tool-definition change.[12]
caught by · hash-diff · pinning · provenance · scanners
The two intersect: a rug-pull is a malicious functional regression. The same tools/list snapshot that catches your accidental param rename also catches an upstream description mutation — the single highest-leverage control here.[24]
02
The board — attack class → defense
left: vector · right: 2026 control
CRITICALPost-approval mutation (rug-pull)
A server ships benign tools to earn one approval, then silently changes a tool's description, params, or behaviour.[14][15]
native MCP support:
✗ none — no re-approval on change[12]
→
Tool-description hashing + diff on every update
Hash the whole tool object and track hashes over time; alert on any change.[51] Scan-mode detects — only a gateway/proxy prevents in real time.
CRITICALTool poisoning
Malicious instructions hidden in a tool description (often <IMPORTANT> tags) — visible to the model, not the user; demoed making an add tool exfiltrate SSH keys.[12]
→
Snapshot the whole tool object — descriptions included
The description is part of the prompt the model reads, so a description change is a behavioural change even when the JSON Schema is byte-identical. OWASP folds this under Tool Poisoning (MCP01–10).[24]
HIGHTool shadowing
A poisoned tool rewrites the agent's behaviour toward another trusted server — e.g. forcing an email tool to BCC the attacker.[12]
cross-server contamination
→
Scanner across the whole config + least privilege
mcp-scan inspects installed tools across Claude Code, Cursor, Gemini CLI, Windsurf.[51] Pair with scoped tokens so one tool can't reach another's blast radius.[43]
MEDIUMSchema / description drift (your own server)
"A parameter renamed, a type modified, a tool removed — AI agents break silently."[6] The contract is whatever tools/list + resources/list + prompts/list return.
native MCP support:
✓ tools.listChanged — a hint, not a gate[10]
→
Golden snapshot of the manifest, diffed in CI
Connect, call tools/list, assert the exact schema against a stored fixture; fail the build on any add/remove/type/description change. Update only via a reviewed baseline bump.[9]
MEDIUMBehaviour drift — "the tool now returns wrong data"
Schema tests catch shape, not behaviour. Chat-window "vibe-testing" proves nothing reproducible.[5]
shape ≠ correctness
→
Behavioural tests through an in-memory client
Exercise the real MCP serialization/dispatch layer with Client(mcp) — atomic, contract-focused, no network flake.[5] Plus the official conformance gate.[3]
HIGHSurprise upstream update · tag re-push
A version moves under you, or a tag is re-pushed to point at different bytes. Pinning the reference doesn't pin the content.[40]
version ≠ content
→
Digest pinning (image@sha256:…) + lockfile hashes
Immutable: content changes → hash changes. Pin every bundled dependency; add a SessionStart manifest-diff hook for plugins.[41]
MEDIUMName-squatting / impersonation
A look-alike server name harvests installs intended for the real one.
trust the name?
→
Registry namespace verification
Reverse-DNS names (io.github.user/server) tied to verified GitHub / DNS / HTTP challenge.[35] Verifies ownership of the name, not intent of the code.
CRITICALHook RCE via project files
Malicious project files define hooks / settings.json that auto-run when Claude loads an untrusted repo.[45]
CVE-2025-59536 CVE-2026-21852
→
Directory-trust boundary — and validate before publish
The directory-trust dialog is the boundary; Claude Code does not security-audit plugins or MCP servers.[43] Gate your own with claude plugin validate --strict.[41]
HIGHPrompt-injection exfil via legitimate tools
A malicious GitHub issue hijacks the agent and drains private repos through the legitimate GitHub MCP tools — broad PATs were the root cause.[20][21]
the lethal trifecta
→
Least privilege + runtime isolation
Scoped tokens, one-repo-per-session, dev containers — so a hijack can't turn a legit tool into an exfil channel.[43]
scoped PAT · session isolation
03
Incident & CVE timeline
this is not theoretical
May 2025
GitHub MCP prompt-injection heist HIGH
A malicious issue hijacks the agent and exfiltrates private-repo data via legitimate GitHub MCP tools.
[20][21]
Jul 2025
mcp-remote OAuth proxy CRITICAL
OS command injection via a crafted
authorization_endpoint; 437,000+ downloads affected.
[23]
CVE-2025-6514
Sep 2025
postmark-mcp — the canonical rug-pull CRITICAL
Benign for 15 versions (1.0.0–1.0.15), then v1.0.16 BCC'd every email to the attacker; ~1,500 weekly installs. Trust accrues across versions, malice ships in one.
[17][18][19]
Oct 2025
Smithery hosting path-traversal HIGH
Leaked the builder's Docker config + Fly.io token across 3,000+ apps.
[23]
CVE-2025-53967
Feb 2026
ToxicSkills / ClawHub audit CRITICAL
3,984 agent skills audited: 36% had a vuln, 534 critical, 76 confirmed malicious payloads — 100% of malicious skills combined a code layer with a natural-language instruction layer.
[26]
Feb 2026
Claude Code hook RCE CRITICAL
Malicious project files define hooks /
settings.json that auto-run on repo open.
[45]
CVE-2025-59536 CVE-2026-21852
2026
Hookify — official-marketplace plugin MEDIUM
Reads repo rule files into the hook system's
trusted system-message channel; 5/5 payloads leaked env vars against Opus 4.6. Anthropic closed it "working as designed" — the directory-trust dialog is the boundary.
[44]
May 2026
Sigstore provenance bypass CRITICAL
633 malicious npm versions passed Sigstore verification using valid certs from a compromised maintainer account. Signing proves build provenance, not publish authorization.
[31] npm tightened trusted publishing in response.
[32]
04
Defense-in-depth — the four-layer floor
no single layer is sufficient
01
Pin to digest
image@sha256:… and lockfile hashes for every bundled dependency — content changes → hash changes.[40]
02
Hash-diff on update
Run mcp-scan against your config in CI and, ideally, as a runtime gateway.[13]
03
Snapshot the contract
Golden tools/list fixture, descriptions included, diffed in CI; update via reviewed baseline only.[4]
04
Prefer signed sources
Registry-namespaced + signed images — but assume signing can be bypassed and keep the hash-diff regardless.[31]
Genuinely missing defenses
⚠ where the 2026 tooling runs out
No re-approval on tool mutation
Neither the MCP spec nor Claude Code forces re-consent when a tool's definition changes after approval — detection is bolt-on, not built-in.[12]
No cross-version test-matrix runner
There is no public "test this plugin against three Claude Code versions" runner — you DIY it with a GitHub Actions matrix invoking claude plugin validate.[41]
Signing ≠ authorization
Sigstore/provenance was bypassed at scale in May 2026 — it proves where a build came from, not that the publish was authorized.[31]
Detection tools are young / thin
Bellwether is days old (★ 1); mcp-scan's hashing internals are under-documented; the skills supply chain is an active-attack frontier with no mature gatekeeper.[6][26]
05
Source intel
primary feeds · 49 cited