ROOTVIELROOTVIEL
← Knowledge Base

Agentic architecture

Partial11 min

MCP Rug Pull (Tool Drift)

An MCP tool is benign when an operator reviews and approves it, then changes afterwards — its description, schema, or behaviour drifts to something malicious on a later fetch. The approval was for a version that no longer runs; trust granted once is silently inherited by a different tool.

Classification

Domain
Agentic architecture
ROOTVIEL support
Partial — Core is built; parts are not yet wired end-to-end.
Analysis family
FAM-GENAI-MCP
CWE
CWE-494
OWASP
LLM03

Definition

An MCP rug pull is a time-of-check / time-of-use attack on tool definitions. An operator (or an automated policy) inspects an MCP server’s tool, finds it acceptable, and approves it. The server later serves a different definition for the same tool name — a rewritten description carrying injected instructions, a widened parameter schema, or an entirely changed contract — and the agent, holding a standing approval keyed only to the tool’s name, uses the new version as if it were the one that was vetted.

The name "rug pull" captures the trust pattern: the approval is pulled out from under the operator after the fact. The defect is not that any single definition is malicious when read, but that the identity the agent trusts (a tool name on a configured server) is mutable and the agent has no pinned reference to compare against.

Context

MCP servers can update their tool catalogs; agents commonly re-fetch tools on reconnect, on a schedule, or when a server signals a change. Wherever approval is a one-time event and the tool definition can change between approvals, a rug pull is possible. Remote and third-party servers are the obvious risk, but a compromised dependency inside an otherwise-trusted server produces the same drift.

  • ▸Agents that cache a one-time human approval of a tool and reuse it across reconnects.
  • ▸Marketplace or community MCP servers that can push catalog updates after installation.
  • ▸Supply-chain compromise of a trusted server, where the operator’s original review no longer describes what ships.

Technical fundamentals

The structural flaw is a missing integrity binding between "the tool I approved" and "the tool I am about to use". In classic software supply chains this is solved by pinning: you record a hash or signature of the approved artifact and refuse anything that does not match. MCP tool definitions are rarely pinned this way, so the agent cannot tell an approved definition from a drifted one — both arrive as plain JSON over the same channel.

Detecting drift therefore requires a baseline. Given an operator-supplied record of the approved tool — a hash over its canonicalised definition plus a set of expected markers — a verifier can compare the live tool against it and report a mismatch. Without that baseline there is nothing to compare to, and the honest verdict is not "safe" but "cannot tell".

Approved manifest
An operator-supplied record of each tool as it was vetted — a pinned hash over the canonical definition and expected markers.
Schema hash
A digest over the tool’s canonicalised name, description, and input schema; equality means "byte-identical to what was approved".
Drift
Any difference between the live tool definition and its approved manifest entry.

Root cause

The defect is that trust is bound to a mutable identity (a tool name) rather than to an immutable artifact (a pinned definition), and approval is treated as permanent rather than as applying to a specific version. The agent re-fetches and re-uses tool definitions without re-checking them against what was actually reviewed. The remote server controls the bytes; the agent supplies the standing trust; nothing in between verifies that the two still agree.

How the attack works, step by step

  1. 1An attacker ships a benign tool and gets it approved by an operator or an automated reviewer.
  2. 2The agent records the approval keyed to the tool name and begins using the tool.
  3. 3Later — on a reconnect, a scheduled refresh, or a pushed catalog update — the server serves a changed definition for the same name.
  4. 4The new definition carries the attack: a poisoned description, a widened schema that accepts a dangerous argument, or a changed contract.
  5. 5The agent, holding only a name-keyed approval and no pinned baseline, uses the drifted tool as trusted — executing the attacker’s change under the original approval.

Vulnerable example

A synthetic, illustrative host that approves a tool once by name and never re-checks the definition it later fetches:

synthetic — do not use; illustrates the flaw
# UNSAFE: approval is keyed to the tool NAME and never re-verified. approved = set() def ensure_approved(tool): if tool.name not in approved: if human_review(tool): # vetted ONCE, at this moment approved.add(tool.name) # ... only the name is remembered return tool.name in approved tools = mcp_client.list_tools(server) # re-fetched; definition may have drifted for t in tools: if ensure_approved(t): # passes forever, even if t changed register(t) # the drifted tool is now trusted

Corrected example

Bind approval to a pinned artifact. The operator supplies an approved manifest; on every fetch the host canonicalises the live tool, hashes it, and compares the hash and expected markers against the manifest. Any mismatch — or any tool with no manifest entry — is withheld and surfaced, not silently used.

synthetic — approval pinned to a hashed baseline
# SAFER: compare each live tool to an operator-supplied approved manifest. manifest = load_approved_manifest() # {name: {hash, markers}}, operator-pinned def verify(tool): entry = manifest.get(tool.name) if entry is None: return "UNKNOWN", "no baseline for this tool" # never "safe" live = sha256(canonicalize(tool)) # name + description + input schema if live != entry["hash"] or not markers_match(tool, entry["markers"]): return "DRIFT", "live definition differs from approved" return "OK", None for t in mcp_client.list_tools(server): verdict, why = verify(t) if verdict == "OK": register(t) else: withhold(t, verdict, why) # DRIFT -> block + alert; UNKNOWN -> require review
Note

Limit of the fix: the schema is hashed, not semantically parsed. The hash proves THAT the definition changed, not every dangerous way it could change — two different schemas simply produce two different hashes. It gives you a reliable "this is not what was approved" signal; it does not rank how harmful the change is, and with no manifest entry the only honest verdict is UNKNOWN.

Impact

  • ▸Integrity: a vetted tool is replaced by a malicious one under the same trusted name, defeating the review that was performed.
  • ▸Confidentiality: a drifted description or widened schema can steer the agent into leaking data (rug pull as a delivery vehicle for tool poisoning).
  • ▸Authorization: the agent acts on the changed contract with its standing privileges, having never re-authorized the new behaviour.
  • ▸Persistence: because the drift rides on an existing approval, it can survive audits that only re-check tools lacking approval.

Detection

Detection is a diff against a baseline. With an operator-supplied approved manifest, canonicalise and hash each live tool and compare; flag any hash mismatch or missing/extra marker as drift. This is deterministic and reliable at answering "did this change from what was approved?" — but it is only as good as the baseline. With no manifest entry for a tool there is nothing to compare to, so the result is UNKNOWN, never a pass. The hash catches that a change happened; judging whether a specific change is dangerous is a separate, harder step.

How ROOTVIEL analyzes it

Support level
Partial — the execution engine compares a live tool’s schema hash and markers against an operator-supplied approved manifest, but has no baseline of its own to fall back on.
Family
FAM-GENAI-MCP — the Model Context Protocol tool-definition surface.
Declared invariants
"tool must not drift from approved" and "schema must match pinned hash."

The engine canonicalises each live tool definition, hashes it, and checks that hash and the tool’s markers against the approved manifest the operator supplies. A mismatch is reported as drift against the pinned baseline.

Partial

Honest limits: with no approved manifest there is no baseline, so the verdict is UNKNOWN rather than "secure" — the engine will not pretend an unpinned tool is safe. And the schema is hashed, not semantically parsed: the check detects THAT a definition changed, not every dangerous way it could change. It answers "does this still match what was approved?", not "is this change harmful?".

Mitigation

Pin trust to artifacts and re-verify on every use.

  • ▸Preventive: maintain an approved manifest of pinned tool hashes; bind approval to the hash, not the tool name; prefer servers that sign their catalogs.
  • ▸Preventive (change control): require a fresh review when a tool’s hash changes, treating a drifted tool as a new tool.
  • ▸Detective: hash-compare every live tool against the manifest on connect and on every refresh; alert on drift and on any tool lacking a baseline.
  • ▸Corrective: withhold a drifted or unbaselined tool until re-reviewed; keep deterministic authorization on consequential calls so a drifted contract cannot act unchecked.

Verifying the mitigation

Verify by approving a synthetic tool, recording its manifest entry, then serving a mutated definition for the same name and confirming the host flags drift and withholds the tool. Serve a tool with no manifest entry and confirm the verdict is UNKNOWN (withheld for review), not a pass. Confirm an unchanged tool still verifies OK. Report results as "drift was detected against the pinned baseline within the tested scope".

Note

A manifest you do not maintain is the silent failure mode: if the baseline is stale or missing, the honest output is UNKNOWN, and treating UNKNOWN as "safe" reintroduces the whole vulnerability.

Common mistakes

  • ▸Keying approval to a tool name instead of a pinned hash, so a changed tool inherits old trust.
  • ▸Treating an UNKNOWN (no baseline) as a pass instead of as "must review".
  • ▸Reviewing tools once at install and never re-checking on refresh or reconnect.
  • ▸Assuming a matching hash means the tool is safe — it only means it is unchanged from what was approved, which is a different claim.

References

  1. [1]OWASP Top 10 for LLM Applications — LLM03 Supply Chain
  2. [2]OWASP Agentic Security Initiative
  3. [3]Model Context Protocol — Specification
  4. [4]MITRE ATLAS
  5. [5]MITRE CWE-494 (Download of Code Without Integrity Check)

References point to public, well-known technical sources for further study.

Related articles