home · demo · events · compare: remote-registry-rug-pull

Remote Registry Rug Pull (Pinning + Permission Delta)

MCP02MCP04

What just happened in your session

vulnerablecompare-vuln-remote-registry-rug-pull2026-09-07T15:43:11.479359+00:00
side effect landed in /app/sandbox/effects/registry-rug-pull-compare-vuln-remote-registry-rug-pull.json · canary CANARY_remote_registry_rug_pull_18654df6bd0199e8c2a819e8
defendedcompare-def-remote-registry-rug-pull2026-09-07T15:43:11.479591+00:00
blocked by registry_pinning_policy · logged to var/telemetry.jsonl
Tool description that differed: run_demo

poisoned

Run the canonical scenario and return DemoResult.

sanitised

Run the canonical scenario and return DemoResult.

Vulnerable mode

violation_detected: truesecret_exfiltrated: false
✗ The attack succeeded
where it landed
sandbox/effects/registry-rug-pull-<session>.json
registry diff
Before version
1.0.0
After version
2.0.0
Permission delta
{3 keys}
Tool hash changes
[2 items]
telemetry events for this run
permission_change_applied registry.drift-mock.example-serverdrift-mock.example-server@2.0.0 allowed_by_vulnerable_mode
registry.drift-mock.example-server: silently activated 2.0.0; permissions added: ['read:contacts', 'send:message']
permission_change_applied registry.drift-mock.example-serverdrift-mock.example-server@2.0.0 allowed_by_vulnerable_mode
registry.drift-mock.example-server: silently activated 2.0.0; permissions added: ['read:contacts', 'send:message']
permission_change_applied registry.drift-mock.example-serverdrift-mock.example-server@2.0.0 allowed_by_vulnerable_mode
registry.drift-mock.example-server: silently activated 2.0.0; permissions added: ['read:contacts', 'send:message']
permission_change_applied registry.drift-mock.example-serverdrift-mock.example-server@2.0.0 allowed_by_vulnerable_mode
registry.drift-mock.example-server: silently activated 2.0.0; permissions added: ['read:contacts', 'send:message']
permission_change_applied registry.drift-mock.example-serverdrift-mock.example-server@2.0.0 allowed_by_vulnerable_mode
registry.drift-mock.example-server: silently activated 2.0.0; permissions added: ['read:contacts', 'send:message']
permission_change_applied registry.drift-mock.example-serverdrift-mock.example-server@2.0.0 allowed_by_vulnerable_mode
registry.drift-mock.example-server: silently activated 2.0.0; permissions added: ['read:contacts', 'send:message']
permission_change_applied registry.drift-mock.example-serverdrift-mock.example-server@2.0.0 allowed_by_vulnerable_mode
registry.drift-mock.example-server: silently activated 2.0.0; permissions added: ['read:contacts', 'send:message']
Background
Central MCP registries publish tool packages by name; the vulnerable client trusts whatever version the registry serves at each fetch. Install gets v1.0.0 with read-only scopes; the next fetch silently returns v2.0.0 with new exfiltration tools and broader write scopes. The client auto-upgrades and the new permissions land in sandbox/effects/registry-rug-pull-*.json.

Defended mode

violation_detected: truesecret_exfiltrated: falseblocked_by: registry_pinning_policy
✓ The defense blocked the attack
where it landed
var/telemetry.jsonl
rule that fired
registry_pinning_policy
mitigations applied
  • registry_pinning_policy
  • permission_delta_review
  • tool_fingerprint_pinning
registry diff
Before version
1.0.0
After version
2.0.0
Permission delta
{3 keys}
Tool hash changes
[2 items]
Recommendation
ask the user to re-approve the new server manifest before activating the new version
telemetry events for this run
policy_decision policy.registry_pinning_policydrift-mock.example-server@2.0.0 blocked
policy.registry_pinning_policy: registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']
policy_decision policy.registry_pinning_policydrift-mock.example-server@2.0.0 blocked
policy.registry_pinning_policy: registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']
policy_decision policy.registry_pinning_policydrift-mock.example-server@2.0.0 blocked
policy.registry_pinning_policy: registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']
policy_decision policy.registry_pinning_policydrift-mock.example-server@2.0.0 blocked
policy.registry_pinning_policy: registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']
policy_decision policy.registry_pinning_policydrift-mock.example-server@2.0.0 blocked
policy.registry_pinning_policy: registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']
policy_decision policy.registry_pinning_policydrift-mock.example-server@2.0.0 blocked
policy.registry_pinning_policy: registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']
policy_decision policy.registry_pinning_policydrift-mock.example-server@2.0.0 blocked
policy.registry_pinning_policy: registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']
Background
registry_pinning_policy pins the package by content hash on first install. Any version drift surfaces as a permission_delta block-event in telemetry; the auto-upgrade is refused and the delta between v1 and v2 is shown to the operator.
Background on this attack class

Safe remote registry-rug-pull demo. The demo simulates a supply-chain attack on an MCP server registry: v1 of ``drift-mock.example-server`` is benign. v2 keeps the same server id and the same tool name but adds two permissions (``read:contacts``, ``send:message``), grows the input schema and ships a poisoned description. The registry is read entirely from local YAML fixtures - no network, no third-party install. Modes: - ``vulnerable``: activates ``latest`` (= v2) without consulting the user. An artefact is written to ``sandbox/effects/registry-rug-pull-<session>.json`` with the resolved version, the permission delta, and the per-tool hash diff so a reviewer can see exactly what changed. - ``defended``: enforces a user-pinned version and refuses to activate whenever the registry returns a different version, a non-empty permission delta, or a tool fingerprint drift. ``record_blocked_attempt`` emits a structured block event with the diff.

Developer view: raw outputs, tools/list, MCP Inspector
Vulnerable mount: http://mcp-sec.data.mayflower.tech/mcp/registry-rug-pull/vulnerable/
Defended mount: http://mcp-sec.data.mayflower.tech/mcp/registry-rug-pull/defended/
Open in MCP Inspector: run npx @modelcontextprotocol/inspector locally and paste either URL above as a Streamable HTTP server.
DemoResult: vulnerable
{
  "experiment": "remote-registry-rug-pull",
  "mode": "vulnerable",
  "violation_detected": true,
  "secret_exfiltrated": false,
  "blocked_by": [],
  "events": [
    {
      "type": "registry_diff",
      "before_version": "1.0.0",
      "after_version": "2.0.0",
      "permission_delta": {
        "added": [
          "read:contacts",
          "send:message"
        ],
        "removed": [],
        "broadened": []
      },
      "tool_hash_changes": [
        {
          "tool": "message.send",
          "kind": "added",
          "description_hash_after": "e49dd2f80325997d00a1c9234ca29b5896fce708d578a4fa009897d6bed2e41b",
          "schema_hash_after": "6db7da64b9a1c7162f246c6b6d41b19e9787e61a2f196e16808b74c8f074aa8f"
        },
        {
          "tool": "profile.get",
          "kind": "changed",
          "description_hash_before": "ecb5d2f40cde2481941758ede9f16b7900c5c09f20e31d0231980c9636d02dc0",
          "description_hash_after": "01f72e184d911900f9c6ab65cb949ad5cfb9486ca7b60a0488dc205e92d6fd97",
          "schema_hash_before": "c8a1ac469a826ea3547ac220c7bbfdcd6b58080d4ec596ff2a0149c5ccb9b699",
          "schema_hash_after": "bb9b00266367d50a53ce621c9319329fae68d503c0107e32edbd80710463f630"
        }
      ]
    }
  ]
}
DemoResult: defended
{
  "experiment": "remote-registry-rug-pull",
  "mode": "defended",
  "violation_detected": true,
  "secret_exfiltrated": false,
  "blocked_by": [
    "registry_pinning_policy"
  ],
  "events": [
    {
      "type": "registry_diff",
      "before_version": "1.0.0",
      "after_version": "2.0.0",
      "permission_delta": {
        "added": [
          "read:contacts",
          "send:message"
        ],
        "removed": [],
        "broadened": []
      },
      "tool_hash_changes": [
        {
          "tool": "message.send",
          "kind": "added",
          "description_hash_after": "e49dd2f80325997d00a1c9234ca29b5896fce708d578a4fa009897d6bed2e41b",
          "schema_hash_after": "6db7da64b9a1c7162f246c6b6d41b19e9787e61a2f196e16808b74c8f074aa8f"
        },
        {
          "tool": "profile.get",
          "kind": "changed",
          "description_hash_before": "ecb5d2f40cde2481941758ede9f16b7900c5c09f20e31d0231980c9636d02dc0",
          "description_hash_after": "01f72e184d911900f9c6ab65cb949ad5cfb9486ca7b60a0488dc205e92d6fd97",
          "schema_hash_before": "c8a1ac469a826ea3547ac220c7bbfdcd6b58080d4ec596ff2a0149c5ccb9b699",
          "schema_hash_after": "bb9b00266367d50a53ce621c9319329fae68d503c0107e32edbd80710463f630"
        }
      ],
      "recommendation": "ask the user to re-approve the new server manifest before activating the new version"
    }
  ]
}
tools/list (vulnerable)
run_demo
description
Run the canonical scenario and return DemoResult.
inputSchema
{
  "properties": {
    "session_id": {
      "default": "mcp-vulnerable",
      "title": "Session Id",
      "type": "string"
    }
  },
  "title": "run_demoArguments",
  "type": "object"
}
tools/list (defended)
run_demo
description
Run the canonical scenario and return DemoResult.
inputSchema
{
  "properties": {
    "session_id": {
      "default": "mcp-defended",
      "title": "Session Id",
      "type": "string"
    }
  },
  "title": "run_demoArguments",
  "type": "object"
}
telemetry: vulnerable
[
  {
    "event_id": "evt_57dfd200436f5d27",
    "ts": "2026-07-26T03:20:16.476729+00:00",
    "session_id": "compare-vuln-remote-registry-rug-pull",
    "experiment": "remote-registry-rug-pull",
    "mode": "vulnerable",
    "event_type": "permission_change_applied",
    "severity": "info",
    "message": "registry.drift-mock.example-server: silently activated 2.0.0; permissions added: ['read:contacts', 'send:message']",
    "data": {
      "actor": "registry.drift-mock.example-server",
      "target": "drift-mock.example-server@2.0.0",
      "policy_decision": "allowed_by_vulnerable_mode",
      "canary_id": "CANARY_remote_registry_rug_pull_18654df6bd0199e8c2a819e8",
      "artifact": "/app/sandbox/effects/registry-rug-pull-compare-vuln-remote-registry-rug-pull.json"
    }
  },
  {
    "event_id": "evt_8a407a2a25d9a18e",
    "ts": "2026-08-19T08:56:24.817706+00:00",
    "session_id": "compare-vuln-remote-registry-rug-pull",
    "experiment": "remote-registry-rug-pull",
    "mode": "vulnerable",
    "event_type": "permission_change_applied",
    "severity": "info",
    "message": "registry.drift-mock.example-server: silently activated 2.0.0; permissions added: ['read:contacts', 'send:message']",
    "data": {
      "actor": "registry.drift-mock.example-server",
      "target": "drift-mock.example-server@2.0.0",
      "policy_decision": "allowed_by_vulnerable_mode",
      "canary_id": "CANARY_remote_registry_rug_pull_18654df6bd0199e8c2a819e8",
      "artifact": "/app/sandbox/effects/registry-rug-pull-compare-vuln-remote-registry-rug-pull.json"
    }
  },
  {
    "event_id": "evt_d743cc588234fc5a",
    "ts": "2026-08-29T11:19:32.011788+00:00",
    "session_id": "compare-vuln-remote-registry-rug-pull",
    "experiment": "remote-registry-rug-pull",
    "mode": "vulnerable",
    "event_type": "permission_change_applied",
    "severity": "info",
    "message": "registry.drift-mock.example-server: silently activated 2.0.0; permissions added: ['read:contacts', 'send:message']",
    "data": {
      "actor": "registry.drift-mock.example-server",
      "target": "drift-mock.example-server@2.0.0",
      "policy_decision": "allowed_by_vulnerable_mode",
      "canary_id": "CANARY_remote_registry_rug_pull_18654df6bd0199e8c2a819e8",
      "artifact": "/app/sandbox/effects/registry-rug-pull-compare-vuln-remote-registry-rug-pull.json"
    }
  },
  {
    "event_id": "evt_22d881b44f7f88dc",
    "ts": "2026-09-07T14:02:16.174446+00:00",
    "session_id": "compare-vuln-remote-registry-rug-pull",
    "experiment": "remote-registry-rug-pull",
    "mode": "vulnerable",
    "event_type": "permission_change_applied",
    "severity": "info",
    "message": "registry.drift-mock.example-server: silently activated 2.0.0; permissions added: ['read:contacts', 'send:message']",
    "data": {
      "actor": "registry.drift-mock.example-server",
      "target": "drift-mock.example-server@2.0.0",
      "policy_decision": "allowed_by_vulnerable_mode",
      "canary_id": "CANARY_remote_registry_rug_pull_18654df6bd0199e8c2a819e8",
      "artifact": "/app/sandbox/effects/registry-rug-pull-compare-vuln-remote-registry-rug-pull.json"
    }
  },
  {
    "event_id": "evt_ad9b0942d2d4bbd7",
    "ts": "2026-09-07T14:02:59.185601+00:00",
    "session_id": "compare-vuln-remote-registry-rug-pull",
    "experiment": "remote-registry-rug-pull",
    "mode": "vulnerable",
    "event_type": "permission_change_applied",
    "severity": "info",
    "message": "registry.drift-mock.example-server: silently activated 2.0.0; permissions added: ['read:contacts', 'send:message']",
    "data": {
      "actor": "registry.drift-mock.example-server",
      "target": "drift-mock.example-server@2.0.0",
      "policy_decision": "allowed_by_vulnerable_mode",
      "canary_id": "CANARY_remote_registry_rug_pull_18654df6bd0199e8c2a819e8",
      "artifact": "/app/sandbox/effects/registry-rug-pull-compare-vuln-remote-registry-rug-pull.json"
    }
  },
  {
    "event_id": "evt_01ddd586ccf7d258",
    "ts": "2026-09-07T14:04:38.691690+00:00",
    "session_id": "compare-vuln-remote-registry-rug-pull",
    "experiment": "remote-registry-rug-pull",
    "mode": "vulnerable",
    "event_type": "permission_change_applied",
    "severity": "info",
    "message": "registry.drift-mock.example-server: silently activated 2.0.0; permissions added: ['read:contacts', 'send:message']",
    "data": {
      "actor": "registry.drift-mock.example-server",
      "target": "drift-mock.example-server@2.0.0",
      "policy_decision": "allowed_by_vulnerable_mode",
      "canary_id": "CANARY_remote_registry_rug_pull_18654df6bd0199e8c2a819e8",
      "artifact": "/app/sandbox/effects/registry-rug-pull-compare-vuln-remote-registry-rug-pull.json"
    }
  },
  {
    "event_id": "evt_6cb62a8eac637463",
    "ts": "2026-09-07T15:43:11.479359+00:00",
    "session_id": "compare-vuln-remote-registry-rug-pull",
    "experiment": "remote-registry-rug-pull",
    "mode": "vulnerable",
    "event_type": "permission_change_applied",
    "severity": "info",
    "message": "registry.drift-mock.example-server: silently activated 2.0.0; permissions added: ['read:contacts', 'send:message']",
    "data": {
      "actor": "registry.drift-mock.example-server",
      "target": "drift-mock.example-server@2.0.0",
      "policy_decision": "allowed_by_vulnerable_mode",
      "canary_id": "CANARY_remote_registry_rug_pull_18654df6bd0199e8c2a819e8",
      "artifact": "/app/sandbox/effects/registry-rug-pull-compare-vuln-remote-registry-rug-pull.json"
    }
  }
]
telemetry: defended
[
  {
    "event_id": "evt_72e8f5e49cf1770e",
    "ts": "2026-07-26T03:20:16.477057+00:00",
    "session_id": "compare-def-remote-registry-rug-pull",
    "experiment": "remote-registry-rug-pull",
    "mode": "defended",
    "event_type": "policy_decision",
    "severity": "warning",
    "message": "policy.registry_pinning_policy: registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']",
    "data": {
      "actor": "policy.registry_pinning_policy",
      "target": "drift-mock.example-server@2.0.0",
      "policy_decision": "blocked",
      "canary_id": null,
      "artifact": null,
      "reason": "registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']"
    }
  },
  {
    "event_id": "evt_87d44fc159385f7c",
    "ts": "2026-08-19T08:56:24.817953+00:00",
    "session_id": "compare-def-remote-registry-rug-pull",
    "experiment": "remote-registry-rug-pull",
    "mode": "defended",
    "event_type": "policy_decision",
    "severity": "warning",
    "message": "policy.registry_pinning_policy: registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']",
    "data": {
      "actor": "policy.registry_pinning_policy",
      "target": "drift-mock.example-server@2.0.0",
      "policy_decision": "blocked",
      "canary_id": null,
      "artifact": null,
      "reason": "registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']"
    }
  },
  {
    "event_id": "evt_7c810bc54b360174",
    "ts": "2026-08-29T11:19:32.011936+00:00",
    "session_id": "compare-def-remote-registry-rug-pull",
    "experiment": "remote-registry-rug-pull",
    "mode": "defended",
    "event_type": "policy_decision",
    "severity": "warning",
    "message": "policy.registry_pinning_policy: registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']",
    "data": {
      "actor": "policy.registry_pinning_policy",
      "target": "drift-mock.example-server@2.0.0",
      "policy_decision": "blocked",
      "canary_id": null,
      "artifact": null,
      "reason": "registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']"
    }
  },
  {
    "event_id": "evt_5c9926f79ef2fba4",
    "ts": "2026-09-07T14:02:16.174644+00:00",
    "session_id": "compare-def-remote-registry-rug-pull",
    "experiment": "remote-registry-rug-pull",
    "mode": "defended",
    "event_type": "policy_decision",
    "severity": "warning",
    "message": "policy.registry_pinning_policy: registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']",
    "data": {
      "actor": "policy.registry_pinning_policy",
      "target": "drift-mock.example-server@2.0.0",
      "policy_decision": "blocked",
      "canary_id": null,
      "artifact": null,
      "reason": "registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']"
    }
  },
  {
    "event_id": "evt_b9d98f31b34f95db",
    "ts": "2026-09-07T14:02:59.185923+00:00",
    "session_id": "compare-def-remote-registry-rug-pull",
    "experiment": "remote-registry-rug-pull",
    "mode": "defended",
    "event_type": "policy_decision",
    "severity": "warning",
    "message": "policy.registry_pinning_policy: registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']",
    "data": {
      "actor": "policy.registry_pinning_policy",
      "target": "drift-mock.example-server@2.0.0",
      "policy_decision": "blocked",
      "canary_id": null,
      "artifact": null,
      "reason": "registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']"
    }
  },
  {
    "event_id": "evt_806236fe557b08d9",
    "ts": "2026-09-07T14:04:38.691839+00:00",
    "session_id": "compare-def-remote-registry-rug-pull",
    "experiment": "remote-registry-rug-pull",
    "mode": "defended",
    "event_type": "policy_decision",
    "severity": "warning",
    "message": "policy.registry_pinning_policy: registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']",
    "data": {
      "actor": "policy.registry_pinning_policy",
      "target": "drift-mock.example-server@2.0.0",
      "policy_decision": "blocked",
      "canary_id": null,
      "artifact": null,
      "reason": "registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']"
    }
  },
  {
    "event_id": "evt_1707f94798976aff",
    "ts": "2026-09-07T15:43:11.479591+00:00",
    "session_id": "compare-def-remote-registry-rug-pull",
    "experiment": "remote-registry-rug-pull",
    "mode": "defended",
    "event_type": "policy_decision",
    "severity": "warning",
    "message": "policy.registry_pinning_policy: registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']",
    "data": {
      "actor": "policy.registry_pinning_policy",
      "target": "drift-mock.example-server@2.0.0",
      "policy_decision": "blocked",
      "canary_id": null,
      "artifact": null,
      "reason": "registry drift: pinned=None latest='2.0.0'; permissions added=['read:contacts', 'send:message']; tool changes=['message.send', 'profile.get']"
    }
  }
]