Deadbugz Killed Approve-Once: The Runtime Controls MCP Needs Now

Deadbugz hid malicious MCP metadata behind ordinary calls. Build a runtime loop with pinned manifests, re-approval, bounded egress, and call-time evidence.

Three ordinary MCP tool calls unlock hostile metadata on a later refresh; a proposed manifest check blocks the changed definition
The incident's trigger and the proposed control are distinct: inspect changing definitions before presenting them to the model.

The move

Replace an MCP server’s permanent trust flag with an approved catalog, a comparison on each refresh, and a block on unknown or changed definitions. Keep enough evidence to identify the exact metadata used in a run. Add separate restrictions on filesystem access, credentials, and outbound traffic, because an unchanged manifest does not prove safe behavior.

The news, once: what Deadbugz actually demonstrated

In its August 12, 2026 disclosure, Pillar Security described a campaign distributing a productivity-suite MCP configuration through GitHub pull requests. Three tools/call requests advanced a per-client counter; subsequent tools/list and prompts/get responses exposed instructions seeking SSH keys, AWS credentials, shell history, and Kubernetes configuration.

Pillar observed the delayed metadata using harmless requests and compared it with public source. The malicious logic was already present in the code; it was withheld during early interactions. The 23 reviewed pull requests were unmerged at the time of review. That establishes a delivery campaign and a reproduced trigger, not proof that those repositories were compromised or that a thorough source audit had passed the server.

The operational lesson is narrow and useful: testing a few successful calls does not establish that later metadata will remain benign.

Why acting agents changed the requirement

A tool definition influences how a model chooses and uses tools. It is untrusted server content, not an instruction entitled to the same authority as the operator. Nevertheless, a model can follow a malicious instruction if the surrounding system fails to preserve that boundary.

The dangerous transition occurs when that influence reaches another capability: a shell, a credential-bearing API, or an outbound request. A manifest check should therefore block changed context before presentation where possible, while independent action permissions limit what the model can do afterward. The existing MCP hardening checklist covers installation; this runbook adds a lifecycle around approved metadata.

The runbook: six runtime controls

Start with one internal test server and synthetic data. Do not connect to attack infrastructure to test these controls. Introduce a harmless description change locally, add a tool, alter a schema, and simulate unavailable metadata. These fixtures give you repeatable pass/fail evidence without handling live malware.

An initial inspection misses delayed metadata changes; the runtime loop checks a refreshed catalog, retains the approved version, and blocks changed definitions for review Verify the catalog presented to the model. A second connection that happens to receive clean metadata is not an equivalent check.

Step 1: record exactly what was approved

Capture all pages of tools/list, under the authorization context the agent will use. Retain the complete tool objects and their hashes, plus the server’s source, executable or image digest, endpoint, transport, owner, and approval. MCP’s tool specification includes more than names and input schemas: output schemas, annotations, titles, and other metadata can also matter to a host.

{
  "lockfileVersion": 1,
  "server": "notes-test",
  "sourceCommit": "<reviewed-commit>",
  "artifactDigest": "sha256:<verified-artifact-digest>",
  "transport": "stdio",
  "command": ["/opt/company/notes-test/server"],
  "authorizationContext": "test-reader-policy-v3",
  "catalogHash": "sha256:<complete-catalog-hash>",
  "tools": {
    "summarize": {
      "definitionHash": "sha256:<complete-tool-object-hash>",
      "approvedBy": "platform-reviewer",
      "approvedAt": "2026-09-04T09:00:00Z"
    }
  }
}

This is an illustrative organizational format, not a standard MCP lockfile. Never place bearer tokens in it. A remote server’s reported version is evidence to retain, not attestation that its executable stayed unchanged.

For a small single-language implementation, this Python fingerprint ignores JSON object-key order but preserves all supplied fields and string contents:

import hashlib
import json

def tool_hash(tool: dict) -> str:
    if not isinstance(tool.get("name"), str):
        raise ValueError("tool name must be a string")
    canonical = json.dumps(
        tool, sort_keys=True, separators=(",", ":"),
        ensure_ascii=False, allow_nan=False,
    )
    return "sha256:" + hashlib.sha256(canonical.encode("utf-8")).hexdigest()

Use one implementation everywhere. This is deterministic Python serialization, not a claim of RFC 8785 canonicalization across languages. Reject duplicate JSON object keys during parsing, reject duplicate tool names across pages, and hash the complete catalog in tool-name order. Do not trim descriptions or normalize Unicode silently: the approved string should be the string the host receives.

Pass: changing any retained field changes its hash; reordering object keys does not. A missing page or parse error fails closed instead of producing a partial approved list.

MCP Inspector v0.18.0 showing a connected server, the read_wikipedia_article tool and its input schema.
Upstream illustration from Goose: MCP Inspector displays a server's advertised tool and input schema. These fields remain untrusted server content; this is not a Deadbugz capture or proof of runtime security. Source: Goose project · License and attribution.

Step 2: gate metadata before the model sees it

Place the comparison in the MCP client adapter or a mediation layer that handles the same responses the host will consume. Compare at initial discovery and every subsequent refresh. Invalidate on supported list-change notifications, but do not rely on a hostile server announcing a change.

In the 2026-07-28 protocol, ttlMs is a freshness hint, not a guarantee of unchanged content or a mandatory polling interval. Refresh on use when stale. If policy requires periodic checks, add bounded polling with jitter and backoff, and document the maximum observation gap.

Pinning tool definitions does not inspect prompts/get results or ordinary tool output. Keep fetched prompts disabled until explicitly needed, review prompt templates separately, and treat dynamic returned content as untrusted data. Action permissions must still prevent it from authorizing sensitive operations.

A hook before execution can add a second check. Claude Code documents PreToolUse matching for MCP tools, with exit code 2 blocking a call. The configuration below requires your own verifier; it is not a built-in manifest-pinning feature:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__.*",
        "hooks": [
          {
            "type": "command",
            "command": "/opt/company/mcp-policy/pretool-gate.sh",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

The verifier must map the hook’s stdin tool name to the mediated catalog generation and reject missing, stale, unknown, or changed state. Checking a fresh connection separately can miss a per-client trigger. A pre-call hook also cannot undo poisoned metadata already shown to the model.

Claude Code command-hook startup errors and timeouts can be non-blocking. Keep verifier work bounded below the hook timeout, convert verifier errors to exit 2, and test the missing-file and timeout cases in the installed client. Use enforced transport and tool permissions when a local hook cannot provide the required failure behavior.

Step 3: restrict both the server and the agent’s other tools

A contained stdio process should receive only the files, environment variables, and network routes it needs. A remote server does not automatically read your laptop; the cross-tool risk is that its returned instructions persuade the agent to use a local capability on its behalf. Restrict that capability too.

# Illustrative sandbox requirements for a test summarizer; not vendor syntax.
filesystem:
  readable: [/workspace/test-notes]
  writable: [/workspace/test-output]
  home_mount: false
environment:
  inherit: false
network:
  default: deny
  allow: []

For a server that legitimately needs an API, add a specific approved destination and scoped credentials. Verify redirects, DNS behavior, direct IP access, and alternate proxy variables. A domain allowlist still permits data to leave for that domain; it does not distinguish an authorized repository write from an unauthorized upload.

Keep known indicators from the primary disclosure in your security tooling, with a reviewed date. They supplement the boundary; they are not its definition. Use synthetic destination and credential markers when testing whether denied access appears in logs.

Pass: the test server cannot read a synthetic file outside its mount, and the agent cannot use a separate shell or network tool to perform the same prohibited action.

Step 4: quarantine the delta and triage the exposure

Every unexpected definition change should block the affected catalog entry and create a review event. It is not automatically a confirmed compromise: a planned upstream release and an attacker payload can both change a hash. Escalate based on the content, provenance, and observed actions.

  • Pause new use. Disable the server entry at the mediation point and affected local clients; identify running processes before stopping them.
  • Preserve evidence. Save the approved and received objects, timestamps, catalog generation, related calls, and relevant network records with restricted access.
  • Determine reach. Establish which identities, hosts, mounts, and credentials were accessible during the uncertain period.
  • Contain consequences. Revoke affected access, cancel outstanding work where supported, and rotate credentials that were exposed or plausibly reachable under the observed behavior.
  • Review recovery. Restore a reviewed artifact and metadata version, retest, and require an independent approver for reactivation.

Do not paste hostile descriptions into an assistant as instructions while asking it to investigate. Preserve them as untrusted evidence. Session transcripts may omit tool definitions or be compacted, so save the actual mediated catalog rather than assuming the transcript contains everything.

Step 5: connect admission evidence to execution results

An admission log should identify the catalog generation actually presented, the approved hash, the observed hash, the authorization context, and the policy decision. Record the catalog observation time: a hash of yesterday’s clean response is not a current observation.

{
  "event": "tool_admission",
  "request_id": "test-request-004",
  "session_id": "test-session-a",
  "server": "notes-test",
  "tool": "summarize",
  "catalog_generation": 7,
  "catalog_observed_at": "2026-09-04T09:14:30Z",
  "approved_hash": "sha256:<approved>",
  "observed_hash": "sha256:<changed>",
  "decision": "deny",
  "reason": "definition_changed"
}

This is synthetic evidence. Emit completion records separately, joined by request ID, for calls that were allowed. A PreToolUse hook cannot record an outcome that has not happened; obtain it from the execution adapter or post-call hook. Network byte counts require separate instrumentation and correlation, not a guessed field in a tool log.

Retain redacted arguments where useful, avoid logging credentials or unrestricted prompt content, and protect access to retained snapshots. Reconstruct the local fixture run using only these records. You should be able to distinguish a rejected call from a call that ran and returned an error.

Step 6: enforce a complete deny rule

Unknown definitions, changed definitions, unavailable metadata, and mismatched authorization contexts all block admission. A matching hash passes only the metadata check; resource permissions, approval, budgets, and egress constraints still apply.

# Illustrative decision logic in the mediator; values are not client assertions.
admit_when:
  - server_is_approved
  - observed_catalog_is_complete
  - catalog_matches_current_authorization_context
  - tool_exists_in_approved_catalog
  - presented_definition_hash_equals_approved_hash
  - observation_is_within_policy_age
  - action_permissions_and_required_approvals_pass
otherwise: deny

For HTTP traffic, MCP’s mirrored headers can identify the requested tool, provided the upstream validates header/body agreement. They carry no definition hash. A gateway needs its own trusted catalog and version checks; accepting a client-supplied liveHash would defeat the control.

Re-run the fixtures through every supported harness path. Include a call that bypasses the usual UI, an unknown tool name, and a server that changes metadata after several ordinary requests. A policy exercised only by one happy-path client is incomplete.

What breaks, and how you’ll know

The checker observes a different server view. Different credentials or a separate connection return a different catalog. Signal: the recorded hash cannot be tied to the metadata presented to the model. Fix the mediation path; more polling from an unrelated client will not establish that relationship.

Approvals become automatic. Repeated legitimate changes create a rubber-stamp queue. Move internal manifest updates into reviewed pull requests and batch releases. Never exempt description changes or all version-string changes merely to quiet alerts. Keep new catalog entries blocked while review is pending.

Definitions stay unchanged while behavior changes. A remote backend can change execution or return a malicious result without changing tools/list. Pinning does not detect that. Limit reach, inspect results as untrusted content, and investigate unusual arguments or destinations. The boundaries in securing AI agents remain necessary.

A local server bypasses the mediator. Compare the approved catalog with host configuration and process discovery. Shadow MCP can include a legitimate server deployed without the controls this runbook assumes.

The operating-layer frame

The gateway or client adapter owns enforcement. The operator needs local visibility into the affected sessions and a way to recover their context. Agents as privileged users explains the identity boundary; fleet replay explains the session side of incident reconstruction.

Automater’s local Library and fleet views help operators locate supported session records. They are not a complete capture of model context, a forensic chain of custody, or a gateway manifest gate. Preserve server definitions and security events independently. Explore Automater Lite and Pro for the local operating layer.

FAQ: MCP manifest pinning

What did the Deadbugz research establish?

Pillar documented a GitHub configuration-delivery campaign and reproduced malicious metadata appearing after three tool calls. The reviewed pull requests had not been merged at the time of review. The research demonstrates delayed exposure of malicious instructions; it does not establish that every targeted repository installed the server.

What does manifest pinning protect?

It detects differences between approved metadata and the definitions actually presented to a client, provided comparison happens on the same mediated path. It does not attest to a remote server’s executable, make tool results trustworthy, or prevent sensitive actions by itself. Those require separate permissions and runtime boundaries.

Does a pre-tool hook solve the problem?

A hook can block an impending call after consulting trusted catalog state. It may run after the model has already consumed malicious metadata, and some clients continue after hook errors or timeouts. Validate those failure cases and combine the hook with transport mediation and enforced action permissions.

Sources