Case study
MCP Enterprise Security Architecture Reference architecture — authored, not a client engagement
/

Securing the Model Context Protocol as an enterprise control plane

Once a language model can discover and invoke tools across an enterprise, the thing you have built is not an integration layer. It is a control plane with a non-deterministic caller — and it should be governed like one.

Zero token passthrough Audience-bound delegation Deterministic policy gate Sandboxed execution Independent audit custody Deny by default Recoverable without the plane

What this is. An authored reference architecture and threat model — research and technical writing, not a description of a delivered client engagement. No client, no engagement outcome and no deployment result is claimed anywhere on this page. Where a control is described, it is a design position I am prepared to defend, not a report of one I operated.

Currency. Written against MCP specification revision 2026-07-28, the OWASP Top 10 for LLM Applications 2026 edition, NIST SP 800-63-4, NIST CSF 2.0, CIS Controls v8.1 and SLSA v1.2, as of 8 August 2026. The MCP specification and the OWASP LLM identifiers both changed within the last twelve months, and both changes invalidate widely circulated guidance. Source discipline records what was checked, what was corrected and what could not be verified.

01 The two MCPs, and why they are the same problem

The acronym collision is not a nuisance to be disambiguated in a footnote. It is the argument.

A Model Context Protocol

An open protocol that lets an AI application connect to external tools, data and workflows over JSON-RPC 2.0, through a host, a client and one or more servers. It standardizes what used to be bespoke per-integration glue.

B Managed control plane

A centralized management authority that authenticates principals, evaluates policy, mints credentials, distributes configuration and drives change across downstream infrastructure, clusters, pipelines, cloud accounts and SaaS estates.

Deploy the first at enterprise scale and you have built the second. An MCP estate connected to a ticketing system, a cloud account, a source repository, a database and a deployment pipeline can open accounts, read secrets, change configuration, move money and ship code. That is the definition of a control plane, and it inherits the control plane's governing property:

A control plane converts identity and policy into authority at scale. A compromise that would affect one host in an ordinary application affects the estate when the compromised component can create identities, alter policy, mint credentials, change builds or reconfigure networks. The security question is therefore not only “what data can an attacker read?” but “what decisions can an attacker cause the enterprise to trust?”

What MCP adds to that inheritance is a caller that cannot be reasoned about deterministically. Traditional control-plane security assumes an authenticated human operator or a program whose behaviour is fixed at build time. Here the caller is a language model that composes its next action from whatever text has entered its context — including text supplied by an attacker through a support ticket, a web page or a tool description. The perimeter assumption that the client is on your side does not survive that.

1 Authority without a human in the loop

Agentic workflows exist precisely to remove the operator from each step. Every consent prompt you add is friction the deployment was built to eliminate, which is why consent fatigue is a security control failure and not a UX complaint.

2 Instructions and data share a channel

Retrieved content and tool metadata land in the same context window as the operator's intent. There is no reliable in-band delimiter between a request and a payload pretending to be one.

3 Composition amplifies

A host connected to five servers has not five attack surfaces but the cross product: content read from one server can drive a mutating call against another, and neither server can see the other half of the chain.

The design position this reference takes. Prompt injection is not solved and should not be planned around as though it will be. Treat the model as an untrusted, occasionally hostile caller that holds a legitimate credential, and put the enforceable controls where determinism still exists: identity and audience binding, a policy engine that evaluates structure rather than intent, an execution sandbox, and an audit path the plane itself cannot rewrite. Every guardrail that depends on correctly interpreting natural language is a mitigation, not a control.

02 Reference architecture

Vendor-neutral, expressed as planes and boundaries rather than products. The rule that matters is that no tier inherently holds the authority of another.

MANAGEMENT PLANE Operators & platform owners phishing-resistant MFA · JIT elevation Server registry & catalogue signed, pinned, allowlisted Policy authoring & review policy-as-code · four-eyes on dangerous rules HOST — TRUST ZONE 0 LLM orchestrator non-deterministic · untrusted caller MCP client runtime namespace isolation · consent surface TB-1 BROKER — ENFORCEMENT PLANE Transport & edge TLS · Origin validation · rate & size limits Identity exchange (STS) RFC 8693 · audience-bound · ephemeral no inbound token is ever forwarded Policy decision point deny by default · structural evaluation cryptographic approval binding Tool namespace & schema control server-scoped names · schema diff on update Egress control & SSRF defence destination allowlist · link-local denied Content inspection mitigation, not control — see §05 Decision & execution logging both the authorization and the result Admission control signature · provenance · SBOM · digest pinning unverifiable artifact does not run TB-2 SANDBOXED MCP SERVERS Server A — internal, reviewed own identity · own key boundary Server B — vendor, contracted scoped credential · no cross-tenant reuse Server C — third party, untrusted no network · read-only rootfs · caps dropped Per-server workload identity short-lived · attested · not a shared secret TB-3 MANAGED SYSTEMS Cloud · data · CI/CD · SaaS least privilege at the target, independently TB-4 OUTSIDE THE PLANE Audit archive · KMS · recovery
Four boundaries, each with a different failure mode. TB-1 separates the model's context from the enforcement plane; TB-2 separates enforcement from execution; TB-3 separates execution from the systems being managed; TB-4 keeps evidence and recovery outside anything the plane can rewrite. The management plane's own path into policy is deliberately unnumbered here and is treated separately in §10, because it is governed by separation of duties rather than by a network or process boundary.
The load-bearing rule. The MCP estate must not be a single undifferentiated trust domain. The client runtime should not inherently hold key-management authority. The broker should not be able to alter its own audit retention. A connector for one tenant should not hold credentials for another. An operator who can deploy should not also be able to mint administrators. Every one of those is a separation you have to build deliberately, because nothing about the protocol produces it for you.

03 Trust boundaries and what crosses them

A boundary you have not written down is a boundary you are not defending. For each, the question is what crosses, what could be forged, and what the blast radius is if it is.

BoundarySeparatesWhat crossesPrimary abuseEnforceable control
TB-1Model context ↔ enforcement planeTool schemas and descriptions inbound; structured tool calls outbound; retrieved resource content inbound.Tool description injection; schema tampering; tool shadowing across servers; consent fatigue exploitation.Server-scoped tool namespaces; schema pinning and diff-on-update; risk-tiered consent; reject duplicate registrations across connections.
TB-2Enforcement ↔ server executionAuthorized calls with an exchanged, audience-bound token; normalized results.A compromised or malicious server escaping its runtime, or reusing a credential outside its intended context.Per-server workload identity; audience-bound ephemeral tokens; sandboxed runtime with dropped capabilities and no ambient network.
TB-3Server ↔ managed systemAPI calls carrying the server's own downstream credential.Confused deputy; over-scoped downstream role; SSRF into internal or metadata endpoints.Least privilege enforced at the target, independently of the plane; destination allowlists; link-local and private-range egress denial.
TB-4Plane ↔ evidence and recoveryAudit events outbound; key operations requested, never key material returned.Log deletion or poisoning to evade detection; backup destruction to prevent recovery; key extraction.Append-only external archive under separate administration; non-exportable keys in a KMS/HSM boundary; backup credentials the plane does not hold.

Three boundaries the four-way model does not number

The numbered boundaries are the ones a request crosses. These three are real boundaries that a request does not cross, which is exactly why they get forgotten.

A Server to server

When an MCP server calls a second MCP server, or fronts a third-party API, it is simultaneously a resource server to its caller and a client to its target. Those are two different identities carrying two different tokens. The specification is explicit that the token received from the client is not the token to send onward.

B Registry to runtime

The path by which a server definition becomes a running process is a supply chain, and it is usually the least governed part of the estate. A curated catalogue with signature verification and digest pinning is the difference between installing software and executing whatever a repository happened to contain this morning.

C Management plane to policy

Whoever can author policy can widen it. There is no network boundary to place here — the control is separation of duties, four-eyes review on dangerous rules, and policy history in version control that the plane cannot rewrite. Treated in §10.

04 Threat taxonomy

Three tiers, because they need three different kinds of answer. Tier 1 is inherent to the protocol and its ecosystem. Tier 2 arises from putting a language model in the decision path. Tier 3 is ordinary control-plane security that people skip because the deployment feels experimental.

ThreatFramework anchorMechanismControl that actually holds
Tool poisoning & description injection LLM01:2026 LLM08:2026 The model reads tool names, descriptions and parameter schemas to plan. A server that controls those fields can place adversarial instructions in them, which the model may treat as authoritative. Treat tool metadata as code: review it, pin it, hash it, and diff schemas on every update with re-approval on change. Content filtering helps and does not suffice.
Tool shadowing across servers LLM03:2026 Two connected servers expose the same tool name in a flat namespace. The model selects the wrong one, and a trusted operation is served by an untrusted provider. Server-scoped names as a hard requirement, not a display convention. Reject duplicate registrations across connections. The spec notes the server name from serverInfo is not guaranteed to be unique across servers and SHOULD NOT be relied upon for disambiguation.
Rug pull — silent redefinition LLM04:2026 A server behaves correctly through review and adoption, then changes tool behaviour or descriptions after trust is established. Digest pinning rather than tag or version pinning; schema and description diffing on update; approval required for any scope or capability expansion.
Confused deputy via proxy servers API5:2023 LLM03:2026 A proxy server holds a static client identifier at a third-party authorization server while letting MCP clients register dynamically. An existing consent cookie can then be exploited to obtain authorization codes without fresh user consent. Per-client consent stored server-side and checked before the third-party flow begins; consent cookie set only after approval; CSRF protection and frame-ancestors on the consent page.
Token passthrough & audience confusion API2:2023 The host forwards the user's own access token to a server, which forwards it downstream. Audience validation is broken, rate limiting and monitoring keyed to audience are bypassed, and the downstream log shows the wrong actor. Exchange, never forward. The current specification forbids passthrough outright — see §06 for the exact normative text.
State handle hijacking API1:2023 MCP is stateless; servers that need continuity mint an explicit handle returned as an ordinary tool argument. An attacker who obtains or guesses a handle operates on another user's state. Never treat handle possession as authentication. Bind handles server-side to the authenticated principal — key stored state by user identity derived from the verified token, not from anything the client supplies.
Capability misrepresentation No spec control A server declares capabilities without cryptographic attestation. Nothing in the protocol proves a server is what it claims to be beyond the transport's identity. Compensate outside the protocol: a signed catalogue, an operator-maintained registry, and admission control that refuses anything not in it. This is a genuine gap, not a solved problem.
Indirect prompt injection LLM01:2026 Adversarial instructions embedded in content the model retrieves — a ticket, an email, a page, a document — contaminate the context and drive unauthorized mutating calls against other connected servers. Assume it succeeds. Constrain what a successful injection can reach: least privilege per tool, structural policy gates on mutating calls, egress control, and approval binding for destructive actions. See §05.
Consent fatigue exploitation LLM03:2026 High-frequency approval prompts train operators to approve reflexively. The destructive request arrives looking like the ninety before it. Risk-tiered consent so that the prompts that remain are rare and meaningful, and structured diffs rather than raw payloads so that what is being approved is legible in the two seconds anyone will spend on it.
Exfiltration through legitimate calls LLM02:2026 No exploit required. The model is persuaded to read sensitive data with one authorized tool and write it out with another — a webhook, an email draft, a file write, a search query. Separate read scope from write-and-send scope in policy, not in convention. Destination allowlists on anything that leaves. Output inspection on the egress path.
Hidden context exposure LLM08:2026 System instructions, retrieved policy text and tool schemas are extracted or reconstructed, revealing internal structure, allowlists and control logic. Assume the prompt is public. Put no secret, no credential and no security-relevant decision in it — if disclosure of the system prompt breaks a control, the control was in the wrong place.
Unbounded consumption LLM06:2026 API4:2023 A loop of tool calls, an oversized resource read, or a recursive agent chain consumes capacity and cost without a natural stopping condition. Iteration limits on tool loops, payload size caps, per-principal rate limits and a cost budget that terminates rather than alerts.
Improper output handling LLM10:2026 Model output is passed into a shell, a SQL string, a template or a browser without treating it as untrusted input. Parameterized invocation everywhere. Model output is user input from an unauthenticated source and should be handled with exactly that suspicion.
Command injection in host or server API8:2023 Ordinary unsafe shell interpolation — a URL, a filename or a parameter concatenated into a command line. A rogue server returns a crafted value and obtains code execution on the workstation. No shell interpreter in the invocation path. Parameterized process spawning with array arguments; scheme and character allowlists on anything that reaches a command.
SSRF & metadata exfiltration API7:2023 A fetch-style tool is pointed at link-local or internal addresses — classically the cloud instance metadata endpoint — and the credentials it returns enter the model context and then the logs. Network-level egress filtering that denies link-local and private ranges; DNS resolution validated after redirect, not only before; destination allowlists on every outbound fetch.
Local server compromise LLM04:2026 A stdio server runs as a child process with the user's own privileges and reaches the whole home directory, including cloud and SSH credentials. Containerized or kernel-isolated execution, non-root, read-only root filesystem, dropped capabilities, explicit and minimal mounts. See §09.
Supply-chain substitution LLM04:2026 Typosquatted or malicious server packages in public registries; unsigned artifacts; dependency compromise reaching the runtime. Curated catalogue, signature and provenance verification at admission, digest pinning, SBOM generation and continuous component scanning.
Credential leakage at rest LLM02:2026 API keys and tokens in configuration files, environment files, container layers, support bundles and traces. Managed secret stores and short-lived credentials; redaction enforced at the serialization layer rather than by reviewer discipline; secret scanning across source, build logs, images and support artifacts.
Unauthenticated management exposure API8:2023 API9:2023 A broker, registry or server bound to all interfaces, started in a development mode, or reachable from the internet without authentication — the failure mode that produced internet-wide exposure for earlier orchestration platforms. Localhost binding by default; Origin validation; authenticated private access paths; an authoritative exposure inventory checked continuously by external scanning.
Audit suppression ATT&CK Defense Evasion Logs disabled, deleted or drowned. The most efficient move available to an attacker who already holds administrative access to the plane. Continuous export to an archive under separate administration; alert on telemetry volume collapse and on export errors, not only on malicious events.
On framework identifiers. The LLM identifiers above are from the OWASP Top 10 for LLM Applications 2026 edition, and they are not the numbers most current writing uses. Excessive Agency moved from LLM06 to LLM03; System Prompt Leakage was renamed Hidden Context Exposure and moved to LLM08; LLM06 is now Unbounded Consumption. Cite the edition year with the identifier or the reference rots within twelve months.

05 The untrusted caller

Indirect prompt injection is the threat that makes MCP security different from API security. It deserves to be modelled as a path, with named gates, so that the honest question can be asked at each: is this a control, or is it a hope?

ATTACK PATH 1 · Plant instructions hidden in a ticket, page or document 2 · Retrieve an authorized read tool pulls it into context 3 · Influence model treats the payload as part of its instructions 4 · Act emits a mutating call to a different server 5 · Exfiltrate data leaves through a legitimate egress path DEFENCE GATES G1 · Ingestion filter scan retrieved content for injection signatures probabilistic G2 · Provenance framing deterministic delimiters, untrusted-source labelling probabilistic G3 · Policy gate deny by default on structure, not intent deterministic G4 · Scope & identity exchanged token cannot reach the target at all deterministic G5 · Egress destination allowlist and output inspection deterministic The gates are not equal, and pretending otherwise is the mistake G1 and G2 reduce the rate at which injection succeeds. They cannot be relied on, because they are classifiers operating on adversarial natural language. G3, G4 and G5 do not care whether the model was fooled — they evaluate structure, identity and destination, and they fail closed.
Design the deployment so that steps 4 and 5 are blocked by mechanisms that never had to understand the attack. Everything upstream of that is defence in depth, and should be budgeted as such.

What follows from taking that seriously

  • Read scope and write scope belong to different tokens. If one exchanged credential can both read the ticket and post the webhook, the injection has everything it needs. Split them and the chain requires two independent authorizations.
  • Mutating tools should be structurally constrained, not free-form. A tool that accepts a SQL string accepts an instruction; a tool that accepts a table name, a bounded filter and a row limit accepts a request. Schema design is a security control and it is usually the cheapest one available.
  • Approval must bind to the exact payload. An operator who approved a diff should not have approved whatever executes next. Bind consent cryptographically to a hash of the concrete request, with a short freshness window, so approval cannot be transplanted.
  • Cross-server chains are the unit of analysis. Threat-model the pairs, not the servers. “Read from the ticketing server, write to the repository server” is the risk; neither server can see it alone.
  • Human-in-the-loop is a control only where a human can actually adjudicate. The specification says there should always be a human able to deny tool invocations. That is right, and it is also the control most eroded by volume — which is why tiering it matters more than mandating it.
The claim to avoid making. No filter, classifier or system-prompt instruction reliably prevents prompt injection, and any architecture whose safety case depends on one is asserting a research result nobody has. State the residual risk plainly to the risk owner and spend the budget on the deterministic gates instead.

06 What the specification now requires

MCP historically prescribed no authentication and no authorization model, which is why so much published guidance treats identity as entirely the implementer's problem. That is out of date. Revision 2026-07-28 carries a normative authorization specification, and several of its requirements are the exact controls this architecture argues for.

Read this before reusing older material. Three things changed that break earlier guidance: the transport is Streamable HTTP, and the HTTP+SSE transport is deprecated; the protocol is stateless with no protocol-level sessions, so session-ID advice no longer applies and “Session Hijacking” is now “State Handle Hijacking”; and Dynamic Client Registration is deprecated in favour of Client ID Metadata Documents.
RequirementNormative textArchitectural consequence
No token passthrough “MCP servers MUST NOT accept any tokens that were not explicitly issued for the MCP server.” And on the client side: “MCP clients MUST NOT send tokens to the MCP server other than ones issued by the MCP server's authorization server. MCP servers MUST only accept tokens that are valid for use with their own resources. MCP servers MUST NOT accept or transit any other tokens.” The zero-passthrough principle is no longer a hardening recommendation you have to argue for. It is a conformance requirement, and an implementation that forwards the user's token is non-conformant as well as unsafe.
Audience validation “MCP servers MUST validate that access tokens were issued specifically for them as the intended audience, according to RFC 8707 Section 2.… Invalid or expired tokens MUST receive a HTTP 401 response.” Audience binding moves from a design preference to a testable assertion. Write the negative test: present a token minted for a different server and require a 401.
Resource indicators “MCP clients MUST implement Resource Indicators for OAuth 2.0 as defined in RFC 8707… The resource parameter… MUST be included in both authorization requests and token requests… MUST use the canonical URI of the MCP server.” And: “MCP clients MUST send this parameter regardless of whether authorization servers support it.” Down-scoping is expressed at request time, not inferred afterwards. The last clause matters operationally — the client sends it even against an authorization server that ignores it, so the estate is ready when the server catches up.
Protected resource metadata “MCP servers MUST implement OAuth 2.0 Protected Resource Metadata (RFC 9728). MCP clients MUST use OAuth 2.0 Protected Resource Metadata for authorization server discovery.” Discovery is standardized, which removes a class of configuration error and gives the broker a machine-readable place to publish required scopes.
PKCE, verified “MCP clients MUST implement PKCE according to OAuth 2.1 Section 7.5.2 and MUST verify PKCE support before proceeding with authorization… If code_challenge_methods_supported is absent, the authorization server does not support PKCE and MCP clients MUST refuse to proceed.” Refusing to proceed is the unusual and correct part. Most implementations degrade silently when a capability is missing; here the required behaviour is to stop.
Per-client consent “MCP proxy servers using static client IDs MUST obtain user consent for each dynamically registered client before forwarding to third-party authorization servers.” And: the consent cookie “MUST NOT be set until after the user has approved the consent screen.” This is the confused-deputy fix stated as a requirement. The ordering clause is the subtle half — setting the cookie first renders the consent screen decorative.
State handles are not credentials “MCP servers MUST NOT treat possession of a state handle as authentication… SHOULD bind handles server-side to the authenticated user… where the user ID is derived from the verified token rather than supplied by the client.” Object-level authorization on every call. This is API1:2023 in MCP clothing, and stateless protocols make it easier to get wrong.
Origin validation “Servers MUST validate the Origin header on all incoming connections to prevent DNS rebinding attacks… When running locally, servers SHOULD bind only to localhost (127.0.0.1) rather than all network interfaces (0.0.0.0).” The default-exposure failure that has burned every previous developer-tool ecosystem is addressed directly. Verify it rather than assume the SDK does it.
Server as onward client “If the MCP server makes requests to upstream APIs, it may act as an OAuth client to them. The access token used at the upstream API is a separate token, issued by the upstream authorization server. The MCP server MUST NOT pass through the token it received from the MCP client.” The two-identity rule, made explicit. This is the requirement most often violated by servers written as thin proxies.
Two caveats worth stating plainly. First, authorization in MCP is optional — “Authorization is OPTIONAL for MCP implementations” — so conformance does not imply any of the above is switched on. Verify per server. Second, the specification's own normative basis includes OAuth 2.1, which is still an Internet-Draft (draft-ietf-oauth-v2-1, revision 15 as of March 2026, no RFC number). Building against it is reasonable and current practice; describing it as a published standard is not, and the distinction matters in a control narrative an auditor will read.

07 Delegated identity without passthrough

The single highest-value control in the architecture, because it bounds a class of compromise: a server that is fully compromised holds a credential no other MCP server will accept, so its blast radius collapses to its own downstream scope rather than the user's whole session.

User authenticates once phishing-resistant Host / broker holds the subject token never forwards it Security token service grant_type = token-exchange subject_token = user token resource / audience = target scope = this call only act = delegation, recorded Server A aud = server-a · accepted scope: issues:read Server B aud = server-b · accepted scope: docs:read Server C — compromised holds only aud = server-c rejected everywhere else Downstream API separate onward token issued to the server Lateral use fails audience mismatch at every other resource What each property buys Audience binding kills lateral movement. Down-scoping kills privilege inheritance. Short lifetime kills the value of a stolen token. The actor claim gives the downstream log a delegation chain instead of a bare user identity, which is what makes an incident investigable afterwards.
Token exchange under RFC 8693. The user authenticates once; every downstream credential is minted per target, per call, with an explicit audience and an explicit actor.
DimensionDirect passthroughExchanged, audience-bound token
AudienceBroad — whatever the user's token was issued for, usually an entire API surface.One target — the canonical URI of the specific server, rejected by every other resource server.
Lateral movementFree — one compromised server reaches everything the user can reach.Structurally blocked — not detected and stopped, but never accepted in the first place.
Permission breadthThe user's full role, regardless of what the call needed.The scope of this call, requested explicitly at exchange time.
LifetimeSession-length — hours, and often renewable.Minutes or less, cached only within its own validity.
AttributionThe downstream log shows the user, with nothing recording which component acted.A delegation chain — the act claim names the acting party alongside the subject.
RevocationAll or nothing — revoking reaches the user's whole session.Granular — stop minting for one audience without disturbing anything else.
Rate limiting & monitoringBypassed where controls key on audience or client identity.Enforceable, because the identity presented is the one the control was designed around.

Delegation, not impersonation

RFC 8693 distinguishes the two precisely, and the distinction is the whole audit story. Under impersonation the acting party becomes indistinguishable from the subject; under delegation it keeps its own identity and the token carries both. The act claim is what expresses delegation. Be careful how far you push that, though: the RFC leaves the token representation of impersonation to the authorization server's discretion and policy, so the absence of act is a strong convention rather than a test a verifier is entitled to rely on. If the distinction matters to a downstream control, require the claim explicitly rather than inferring it from a gap.

“The act (actor) claim provides a means within a JWT to express that delegation has occurred and identify the acting party to whom authority has been delegated.”

“For the purpose of applying access control policy, the consumer of a token MUST only consider the token's top-level claims and the party identified as the current actor by the act claim.”

RFC 8693, OAuth 2.0 Token Exchange, §4.1 — Proposed Standard, January 2020
Exchange request — the shape, not a working credential
POST /oauth/token HTTP/1.1
Host: sts.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <host client credentials>

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange
&subject_token=<the user's token — consumed here, never forwarded>
&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token
&resource=https%3A%2F%2Fmcp.example.com%2Fissue-service
&scope=mcp%3Atools%3Aexecute+issues%3Aread
&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token
Two implementation notes that are easy to get wrong. In RFC 8693 the audience parameter is optional, so an authorization server is not obliged to honour it — which is exactly why MCP additionally mandates the RFC 8707 resource parameter and requires the client to send it whether or not the server supports it. And nested act claims record a delegation chain's history but are informational: authorization decisions consider the current actor only.

Verification at the resource server

Signature validity is not authorization. The Storm-0558 investigation turned on precisely this: libraries verified signatures cryptographically but did not automatically perform issuer and key-scope validation, and callers assumed they had. A conforming validator checks all of it — issuer, audience, scope, lifetime, key scope and the presence of the expected delegation claim — and returns 401 for an invalid or expired token, 403 for insufficient scope with a WWW-Authenticate challenge naming the scopes actually required.

Machine identity is not human identity with a different label. Servers and connectors should hold attested, short-lived workload credentials with an explicit audience, not interactive factors and not distributed static secrets. The mechanism matters less than three properties: short lifetime, explicit audience, and binding to a workload rather than a string copied between systems.

08 The deterministic policy gate

Between a model that decided to do something and a system that would do it, there should be a component whose behaviour does not depend on what the model was told. This is the gate that holds when everything upstream has been fooled.

Tool call model-originated Enforcement point normalizes the request parses structure attaches context Structured input principal · roles · assurance workload & server identity tool name · parsed operation target · environment · tenancy data classification touched approval token & payload hash change / ticket reference elevation state & expiry correlation identifier — no free-form intent — Decision point default deny violations == 0 AND an affirmative rule Allow execute · log decision and result together Deny reasons returned logged · counted Escalate bind approval to the payload hash, then retry
The decision point never sees natural language. It sees a typed document, and it answers a closed question about it — which is what makes the answer reproducible, testable and defensible to an auditor.

The rule shape

Default deny, an allow that requires both an empty violation set and an affirmative authorization rule, and violations aggregated from independent families so that adding a new class of check cannot accidentally weaken an existing one. Expressed in Rego:

Policy skeleton — deny-by-default with aggregated violations
package mcp.tools.authz

# On OPA 1.0 and later, Rego v1 semantics are the default and
# `import rego.v1` is a no-op. It is retained only by libraries
# that must also parse under pre-1.0 runtimes.

default allow := false

allow if {
    count(violations) == 0
    is_action_authorized
}

violations contains r if { some r in structural_violations }
violations contains r if { some r in destructive_violations }
violations contains r if { some r in classification_violations }
violations contains r if { some r in approval_violations }
violations contains r if { some r in environment_violations }

# Every family needs at least one rule head, or the module does not
# compile — an undefined family is an unsafe variable, not an empty
# set. One placeholder each; replace the bodies with the real checks.

structural_violations contains "Operation was not parsed into a typed structure." if {
    not input.operation.parsed
}

destructive_violations contains "Mutation submitted without a bounded predicate." if {
    input.operation.kind == "mutate"
    not input.operation.predicate
}

classification_violations contains "Restricted data touched without a data-owner role." if {
    input.target.classification == "restricted"
    not "data_owner" in input.principal.roles
}

environment_violations contains "Production change outside an approved window." if {
    input.target.environment == "production"
    not input.change.window_open
}

# Affirmative rules are separate and additive. A read in a
# non-production environment by a recognized role is allowed;
# the same read in production additionally requires assurance;
# a mutation additionally requires a bound approval.

is_action_authorized if {
    input.operation.kind == "read"
    input.target.environment in {"development", "staging", "qa"}
    some role in input.principal.roles
    role in {"analyst", "developer", "admin"}
}

is_action_authorized if {
    input.operation.kind == "read"
    input.target.environment == "production"
    input.principal.assurance == "phishing_resistant"
    some role in input.principal.roles
    role in {"admin", "data_owner"}
}

is_action_authorized if {
    input.operation.kind == "mutate"
    some role in input.principal.roles
    role in {"admin", "data_owner"}
    approval_is_bound
}

# Approval is bound to the exact payload and is short-lived, so an
# approval obtained for one request cannot authorize another. Note that
# the current time arrives in the input document rather than being read
# from the clock inside the policy: a rule that calls time.now_ns() is
# not replayable from its logged input, which forfeits the reproducibility
# this gate exists to provide.

approval_is_bound if {
    input.approval.verified == true
    input.approval.payload_hash == crypto.sha256(input.operation.canonical)
    age_ns := input.context.now_ns - (input.approval.issued_at * 1000000000)
    age_ns >= 0
    age_ns <= 300 * 1000000000
    endswith(input.approval.approver, "@example.com")
}

approval_violations contains "Mutating operations require a verified approval." if {
    input.operation.kind == "mutate"
    not input.approval.verified
}

approval_violations contains "Approval hash does not match the submitted payload." if {
    input.operation.kind == "mutate"
    input.approval.verified == true
    input.approval.payload_hash != crypto.sha256(input.operation.canonical)
}

# Without this rule an expired approval denies with an empty violation
# set: the caller learns only that it was refused, not why. Every path
# that can deny needs a reason, or the deny is undiagnosable in
# production and untestable in review.

approval_violations contains "Approval is outside its freshness window." if {
    input.operation.kind == "mutate"
    input.approval.verified == true
    input.context.now_ns - (input.approval.issued_at * 1000000000) > 300 * 1000000000
}
The mistake this pattern invites, stated plainly. Almost every published example of this control — including the drafts this reference was built from — performs lexical matching on a raw command string: uppercase it, look for DROP, check whether WHERE appears, count semicolons. That is not structural evaluation, and it is bypassable by anyone who has read the policy: comments, string literals, unicode, nested quoting, dialect-specific syntax and casing tricks all defeat substring matching. If the gate is going to make a structural claim, the enforcement point must parse the operation into an abstract syntax tree and put the parsed structure into the input document — statement type, referenced objects, predicate presence, projected columns — so the policy evaluates a typed object rather than a string. A policy that greps is a speed bump described as a wall.

What the input document must carry, and what it must not

Principal

Identity, roles, authentication assurance, elevation state and its expiry. Not a display name, and never a token.

Workload

Which server and which adapter version originated the call, and under which attested workload identity.

Operation

The parsed operation — kind, targets, predicates, bounds — plus a canonical serialization used for hashing. Not the raw string alone.

Target

Tenant, environment, data classification and the sensitivity of what is touched. This is where an object-level authorization decision becomes possible.

Approval

Verified flag, approver identity, issue time and a hash bound to the canonical payload. Time-bounded, single-use, non-transferable.

Correlation

Session, conversation and request identifiers, so that the decision, the execution and the downstream effect can be joined afterwards.

Test the policy like software, because it is. Property-based negative tests rather than happy-path assertions: tenant A can never mutate tenant B; a read-only principal can never cause a side effect; an expired elevation never authorizes; an approval issued for one payload never authorizes another; an audit administrator can never deploy. These are invariants, they can be generated across the whole role and tenant matrix, and they belong in the pipeline on every policy change.

09 Execution isolation

Assume the server is hostile. Not because most are, but because the cost of that assumption is a container configuration and the cost of the alternative is the workstation.

A local server launched over stdio is a child process of the host application and inherits the invoking user's privileges — the home directory, the cloud credential files, the SSH keys, the browser profile. Compromise it through a poisoned dependency or a prompt-driven misuse and the attacker has everything the user has. Standard container runtimes reduce that but share the host kernel, so a kernel-level escape remains on the table. A userspace application kernel interposes: the sandboxed workload's system calls are handled by a kernel implemented in userspace rather than passed to the host.

Get the claim right. gVisor is frequently described as implementing “over 300 system calls.” Its own compatibility documentation is more precise and slightly less flattering: for Linux on amd64, 288 of 351 syscalls have a full or partial implementation, with 63 unsupported. What is unconditional is interception, not implementation — gVisor never passes a system call straight through to the host. That is the security-relevant property, and it is the one worth stating.
ControlWhat it preventsHow it is verified — the negative test
Kernel isolationA container escape reaching the host kernel from an untrusted server.Query the kernel from inside the sandbox and confirm it reports the sandbox kernel, not the host's.
No ambient networkExfiltration, callback channels, and SSRF originating in the server itself.Attempt an outbound connection from inside and require it to fail. Grant egress only through the broker, per destination.
Read-only root filesystemPersistence, binary replacement, and tampering with the server's own code.Attempt a write outside the designated scratch mount and require a read-only filesystem error.
Non-root executionPrivileged operations inside the sandbox; damage from a partial escape.Assert the effective UID is the unprivileged service account, and that no-new-privileges is set.
All capabilities droppedRaw sockets, mount operations, module loading, ptrace against other processes.Enumerate capabilities inside the sandbox and require an empty set.
Non-executable scratch spaceDropping and running a payload in the one writable location.Write a binary to the scratch mount, attempt execution, require permission denied.
Explicit, minimal mountsAccess to home directories, credential files and adjacent projects.Attempt to read a canary file placed outside the mounted scope and require failure. Mount read-only wherever the tool does not write.
Resource boundsDenial of service against the host from a runaway or deliberately abusive server.Memory, CPU and process-count limits set explicitly and exercised under load, not left at defaults.
Build-tool strippingPost-compromise tooling — package managers and compilers — available inside the runtime image.Multi-stage build; assert the package manager binaries are absent from the final image.

Server-side input handling

Isolation bounds the damage; input validation prevents most of it from starting. Three requirements carry disproportionate weight in an MCP server, because they are the three that a model-driven caller will reach accidentally as well as maliciously:

  • Path containment resolved, not inspected. Rejecting .. in a string is not containment. Resolve the candidate path fully — through symlinks — and then assert that the resolved result is still inside the permitted root. A canonicalization step that falls back to the original path when resolution fails is a real, exploited bug class, not a hypothetical one.
  • Bounded payloads. Size caps on reads and writes, row limits on queries, and iteration limits on tool loops. Absent these, a single tool call is a denial-of-service primitive and a cost incident.
  • Typed schemas over free text. Constrain parameters to enumerations, patterns and ranges wherever the tool's purpose allows. Every free-form string field is a place where an instruction can be smuggled to something downstream that parses it.
Isolation is per-server, and the tiers should be visibly different. An internal, reviewed, signed server that touches a low-classification system does not need the same posture as a third-party server nobody in the organization has read. Publishing the tiers — and which server sits in which — is what stops the strictest configuration from being quietly relaxed to whatever the least trustworthy server needed.

10 Control-plane discipline

Everything to this point is AI-specific. This section is not, and it is the part most MCP deployments skip — because the estate still feels like a pilot long after it has become infrastructure that can change production.

Separate the powers that combine into ownership

The objective is narrow and worth stating as a single sentence: one compromised identity, process, server, key, API or administrative endpoint must not be able to become permanent authority over the estate. Everything below is a mechanism for making that true under a different failure mode.

CapabilityMust not be held withWhy the pair is the dangerous one
Identity administrationPolicy administration; routine operationWhoever can mint principals and also write the rules that judge them can authorize themselves.
Policy administrationDeploymentWidening a rule and shipping through it in one motion removes the only independent check on either.
Key custodyPlatform administrationSigning authority plus runtime control is the ability to forge what the platform trusts, then run it.
Secret brokeringWorkload deploymentDeploying a workload and granting it arbitrary downstream credentials is self-service privilege escalation.
Artifact signingProduction deploymentSign-and-ship in one identity makes provenance verification circular — it proves only that the deployer approved of themselves.
Audit administrationProduction administrationThe team being judged should not be able to alter the evidence used to judge it. This is the one to defend hardest.
Backup administrationProduction administrationAn administrator who can both corrupt state and destroy the means of restoring it can make a mistake permanent.
Server registrationGlobal or cross-tenant administrationAdding a connector is adding a new authorized path into managed systems. It belongs behind change control.

The controls, in priority order

RankControlRisk reducedEvidence it is real
P0No unauthenticated management exposurePre-authentication exploitation of the broker, registry or a server.External scan cannot reach a management endpoint except by an approved path; exposure inventory reconciled continuously.
P0Phishing-resistant authentication, separate privileged identityCredential phishing and password reuse against the people who administer the plane.Identity-provider policy export plus a negative test proving a weaker factor cannot reach administration.
P0Just-in-time elevation, no standing production authorityThe blast radius of a stolen session — which multi-factor authentication does not prevent.Ordinary administrative accounts hold zero standing production privilege; grants and expiries are recorded.
P0Deny-by-default resource authorizationCross-tenant access and privilege-boundary crossing.An automated negative suite for object- and function-level authorization passes on every policy change.
P0Short-lived workload identityReuse of a stolen static credential after exfiltration.A revocation and expiry test terminates access within the stated bound.
P0Root keys non-exportable, in a KMS or HSM boundarySigning-key theft, which converts to forged identity and forged artifacts.Runtime cannot export a root private key; key-use is logged; key administration is a separate role.
P0Independent, append-only audit archiveEvidence deletion, and the investigative blind spots that follow.A production administrator cannot erase an archived record; dropped-event and volume-collapse alerts fire in test.
P0Emergency patch path for exploited vulnerabilitiesKnown-exploited flaws in externally reachable components of the plane.An emergency deployment completes without the normal release delay — and without skipping signature verification or audit.
P0Recovery that does not depend on the planeDestructive compromise, and the case where desired state itself is the thing that was corrupted.A clean-room restore exercise, from backups whose integrity does not rest on the compromised system.
P1Signed artifacts and provenance at admissionSubstituted or untrusted server images reaching the runtime.An unsigned or unverifiable artifact is rejected, and the rejection is logged as a security event.
P1Per-server, per-tenant credential isolationOne connector becoming a universal pivot across the estate.Connector A demonstrably fails to access tenant B; no credential is shared between environments.
P1Egress restriction and SSRF defenceReaching internal services and cloud metadata endpoints from inside the plane.Unapproved destinations, link-local ranges and post-redirect resolutions all fail in test.
P1Configuration policy-as-codeDrift, and the quiet reintroduction of a public endpoint or an over-scoped role.A non-compliant configuration is rejected in the pipeline or at admission, with the test output retained.
P2Continuous authorization and session-risk signalsLong-lived sessions that remain valid after the context that justified them has changed.A simulated context change triggers re-authentication or revocation.
P2Fault injection against security dependenciesUndefined behaviour when the identity provider, policy engine, key service or audit sink is unavailable.A game-day record showing the system fails closed rather than open, per dependency.
Fail-closed has to be decided in daylight. When the policy engine cannot be reached, does the tool call proceed? Decide it before the outage, with the people who own the availability number in the room, and write it down. A rule invented during an incident gets reversed during the incident, and the reversal is what causes the damage. The default position this architecture takes is that authorization uncertainty is never silently a permit.

Incident response for a compromised plane

The sequence, and why the order is the order
1Declare high blast radius immediately. Assign an incident commander, and separate leads for platform, identity, forensics, and governance. A control-plane incident is a potential supply-chain event from the first minute, not after the first finding.
2Preserve independent evidence first. Snapshot the external archive, identity logs, key-use records, configuration history and target-side execution logs. Do not rely on evidence stored inside the system you suspect.
3Constrain authority before investigating it. Quarantine identities and sessions, suspend new elevation, restrict the management endpoint, isolate suspect servers. Preserve running workloads where you safely can — the objective is to stop the granting of authority, not to stop the business.
4Rotate in dependency order. Root, federation and signing material first, because everything downstream derives from it; then plane service credentials; then server credentials; then exposed downstream credentials. Rotating downstream first while the signing key is still compromised accomplishes nothing.
5Establish a trusted recovery authority. Do not rebuild using credentials, artifacts or backups whose integrity depends on the compromised plane. If that authority does not already exist, this step is where you discover it — which is the argument for building it beforehand.
6Validate software and desired state. Compare images against trusted signatures and provenance; compare policy and configuration against reviewed version-control history; hunt for new identities, keys, webhooks, registered servers and scheduled tasks.
7Recover in stages. Identity and key services, then core state, then policy, then the audit pipeline, then managed environments one at a time. Reopen administrative access only after independent validation.
8Hunt downstream. Inspect every cloud account, cluster, repository, pipeline, secret store and SaaS tenant the plane could reach, for activity in the exposure window. The plane's own logs are not sufficient evidence about the plane.
9Meet the reporting obligations. Where personal data is in scope, GDPR Article 33 requires notification to the supervisory authority without undue delay and, where feasible, within 72 hours of awareness — unless the breach is unlikely to result in a risk to the rights and freedoms of natural persons. Note the direction of that test; it is a negative condition, and paraphrasing it as “where there is a risk” inverts the burden.
10Review the system, not the person. “Employee error” and “unpatched server” are where a bad post-incident review stops. For a high-authority platform the question is always why one error was sufficient.

11 Incident register

Six compromises — five with nothing to do with AI, all of them directly instructive. They are here because the failure pattern is the one an MCP estate will reproduce.

The common denominator. In every case below, authority was transferable or amplifiable. A stolen session was reusable; a service credential remained useful outside its intended context; a signing key made forged identity authoritative; an over-privileged component held credentials far beyond its function; a reachable service converted an unauthenticated request into platform control. Not one of these was a failure of a single decisive perimeter control — which is the argument against designing around one.
IncidentWhat happenedWhat it implies for an MCP estate
Storm-0558
2023, updated 2024
An acquired Microsoft consumer signing key was used to forge tokens that reached enterprise email. Microsoft's stated leading hypothesis remained that “operational errors resulted in key material leaving the secure token signing environment,” later accessed through a compromised engineering account with access to a debugging environment. Separately, Microsoft found that validation libraries verified signatures but had not automatically performed the necessary issuer and key-scope validation. Cryptographic validity is not authorization. Validate issuer, audience, scope, purpose and lifetime — not just the signature. Keep signing material non-exportable and forbid sensitive diagnostic artifacts from leaving a protected environment. Both halves of this apply directly to a broker minting delegated tokens.
CircleCI
December 2022 – January 2023
Malware on an engineer's laptop stole “a valid, 2FA-backed SSO session,” which the attacker used to impersonate the employee and escalate into production, exfiltrating customer environment variables, tokens and keys. Encryption at rest did not protect the data: the attacker “extracted encryption keys from a running process.” Multi-factor authentication does not survive session theft, and encryption at rest does not defend against an authorized running process. Both are lessons for a broker that holds delegated credentials in memory. The remediation direction — toward OIDC, granular permissions and more ephemeral credentials — is the same direction this architecture argues for.
Okta support system
October – November 2023
Files associated with 134 customers were accessed; some were HAR files containing session tokens, and the sessions of five customers were hijacked. The root cause was a service-account credential saved into an employee's personal Google profile. A roughly 14-day investigative gap arose because navigating directly to a different interface generated an entirely different log event than the one being monitored. Telemetry completeness must be validated by adversary path, not by volume. A monitored event type is not a monitored capability. Sanitize support artifacts — diagnostic bundles from an AI tool chain are exactly the kind of artifact that quietly contains tokens.
TeamCity CVE-2026-63077
Actively exploited, August 2026
An unauthenticated attacker with HTTP(S) access can exploit the agent-polling protocol through unsafe deserialization to execute operating-system commands with the privileges of the server process. CVSS 9.8, CWE-502. Fixed in 2025.11.7 and 2026.1.3, with a patch plugin for older supported installations. JetBrains reported active and attempted exploitation, and it was added to CISA's Known Exploited Vulnerabilities catalogue on 5 August 2026. An agent-polling protocol is a fleet-wide trust path and must be fuzzed before it becomes one. This is the closest structural analogue to an MCP transport in production today: an externally reachable orchestration service whose deserialization boundary is the whole attack. Note also the compensating control — restricting network reach — is necessary and is not a substitute for patching.
ingress-nginx CVE-2025-1974
March 2025
CVSS 9.8. “An unauthenticated attacker with access to the pod network can achieve arbitrary code execution in the context of the ingress-nginx controller. This can lead to disclosure of Secrets accessible to the controller.” Kubernetes noted that “in the default installation, the controller can access all Secrets cluster-wide.” Fixed in 1.11.5 and 1.12.1. Internal networks are trust boundaries too, and a component's default privilege is usually the real vulnerability. Ask what an MCP server can read if it is fully compromised — and if the answer is “every secret in the namespace,” the code-execution flaw was only the delivery mechanism.
Cursor IDE, CVE-2026-50548 / 50549
June 2026
Two sandbox-escape remote code execution flaws in Cursor IDE, both CVSS 9.8, fixed in version 3.0. One allows agent manipulation of a working-directory parameter so the sandbox grants write access outside the workspace; the other is a canonicalization failure where a fallback to the original path allows a write without approval. The discovering researchers note the injection can be delivered through a malicious MCP server request or a poisoned web result. The sandbox is part of the trust boundary and it can have its own bugs. This is the AI-adjacent case in the register, and the honest reading is narrow: these are editor sandbox-escape flaws, not protocol flaws. The transferable lesson is the canonicalization fallback — resolve, then assert containment, and fail closed when resolution fails.
On attribution. The last row is included with a correction. Several published summaries describe these CVEs as an “MCP exploit chain.” They are not: neither the vulnerability records nor the vendor advisory mentions MCP, and MCP appears in the researchers' account only as one optional delivery channel for the injection, alongside a poisoned web result. Attributing an editor sandbox-escape flaw to the MCP ecosystem overstates the protocol's record, and this reference declines to do it. See §17.

12 Standards crosswalk

An architectural mapping, not an official equivalency. A control that satisfies one framework should never be assumed to satisfy another — the point of the table is to make the evidence conversation tractable, not to claim conformity.

Control domainNIST CSF 2.0ISO/IEC 27001 & 27002CIS Controls v8.1AI-specific anchor
Governance & risk ownershipGovernISMS context, leadership, planning, risk treatmentGovernance across safeguards; Control 17NIST AI RMF GOVERN 1.2 — trustworthy-AI characteristics integrated into policy and practice
Estate inventoryIdentifyAsset and information managementControls 1–2; data inventory under Control 3A signed catalogue of approved servers, with owners
Identity & account lifecycleProtectIdentity and access managementControl 5 — Account ManagementPer-server workload identity, not shared secrets
Authorization, PAM & JITProtectAccess control; segregation of dutiesControl 6 — Access Control ManagementOWASP LLM LLM03:2026 Excessive Agency
Secrets & key protectionProtectCryptography; information protectionControl 3 — Data ProtectionOWASP LLM LLM02:2026 Sensitive Information Disclosure
Network isolation & egressProtectNetwork security controlsControl 12 — Network Infrastructure ManagementOWASP API API7:2023 SSRF
Vulnerability managementIdentify / ProtectTechnical vulnerability managementControl 7 — Continuous Vulnerability ManagementKnown-exploited catalogue as an emergency trigger
Audit & telemetryDetectLogging and monitoringControl 8 — Audit Log ManagementTool-call traces with caller identity, arguments and downstream effect
Secure software lifecycleProtectSecure development, change and supplier controlsControl 16 — Application Software SecuritySLSA v1.2 build and source tracks; OWASP LLM LLM04:2026 Supply Chain
Incident responseRespondIncident planning, assessment and responseControl 17 — Incident Response ManagementNIST AI RMF MANAGE 2.4 — mechanisms to supersede, disengage or deactivate
Backup & recoveryRecoverICT readiness and continuityData-recovery safeguardsRecovery that does not depend on the plane
Supplier & connector riskGovern / IdentifySupplier relationships; cloud securityService-provider and supply-chain safeguardsCSA AI Controls Matrix v1.1 — secure development lifecycle for AI applications

Regulatory scope follows what the estate touches

Obligations attach to the systems an MCP deployment can reach, not to the deployment's own novelty. Three worth naming precisely:

  • GDPR Article 32 requires risk-appropriate technical and organizational measures, and names four elements explicitly: pseudonymisation and encryption; the ability to ensure ongoing confidentiality, integrity, availability and resilience; the ability to restore availability and access in a timely manner after an incident; and a process for regularly testing, assessing and evaluating the effectiveness of those measures. The fourth is the one architecture documents usually omit and auditors always ask about.
  • HIPAA technical safeguards at 45 CFR §164.312 — access control, audit controls, integrity, person or entity authentication, and transmission security — map cleanly onto the controls above. HHS published a proposed rule in January 2025 to strengthen the Security Rule; as of publication it is neither finalized nor withdrawn, and the existing Security Rule remains in effect. Design to the rule that is in force and track the proposal separately.
  • SOC 2 and ISO/IEC 27001 treat these components as in-scope systems once they can affect a system in scope. An MCP server that can change a production configuration is a production system, whatever the internal project classification says.
The evidence pattern that survives an audit. Policy → configuration → runtime evidence → test result → exception. A written statement that “administrators use phishing-resistant authentication” is the weakest form of this. An identity-provider policy export, plus a sample of authentication events, plus a negative test proving a weaker factor cannot reach the management plane, is the strongest — and it is the same artifact set that tells you whether the control actually works.

13 Assurance and testing

Different techniques find different defect classes. They are layers, not competing options, and an annual penetration test is not a testing programme.

LayerWhat it must cover here specificallyCadence
Static analysisUnsafe deserialization, shell construction, path handling and canonicalization fallbacks, SSRF sinks, authorization-bypass patterns, secret handling.Every change
Composition analysisServer dependencies, base images, plugins and SDKs, with an SBOM per artifact and continuous re-evaluation as new advisories land.Every build, plus continuous
Configuration scanningPublic administrative endpoints, permissive bindings, missing Origin validation, over-scoped downstream roles, absent audit configuration, unencrypted stores.Every infrastructure change
Secret scanningSource, build logs, container layers, artifacts and support bundles — and the model's own transcript and trace stores, which are a new and frequently unscanned location.Commit, build, release
Dynamic and API testingAuthentication bypass, object- and function-level authorization, audience validation against a foreign token, request limits, SSRF, rate controls.Continuous in staging
FuzzingJSON-RPC parsing, transport framing, schema validation and the policy evaluator's edge cases. Fuzz any agent or webhook protocol before it becomes a fleet-wide trust path.Campaign based
Authorization property testsGenerated across the role, tenant and environment matrix, asserting invariants rather than checking examples.Every policy or API change
Adversarial prompt testingInjection through every ingestion path the estate actually has — tickets, documents, pages, tool descriptions — measured on whether the deterministic gates held, not on whether the model was fooled.Per release, and on new server onboarding
Penetration testingSession theft, authorization-boundary crossing, tenant escape, policy manipulation, key access, agent impersonation, malicious server registration, artifact substitution, audit suppression, backup destruction.Major changes, plus periodic
Red teamStart from a realistic foothold — a compromised workstation, a stolen session, a poisoned dependency, a malicious server — and measure whether it can be turned into durable estate-wide authority.Periodic
Fail-safe testingLoss of the identity provider, the policy engine, the key service, the audit sink and each connector, verifying the system degrades closed and the degradation is visible.Pre-production, plus periodic
Recovery testingCorrupted desired state and a compromised administrator, not merely a crashed host. Restore from evidence whose integrity does not depend on the plane.Scheduled game days
The one test most estates are missing. Mint a token for server A and present it to server B. If B accepts it, the entire delegation model on this page is decorative, and you will not find that out any other way — every positive test passes in both the correct and the broken configuration.

14 Metrics that mean something

Measure enforcement and exposure, not activity. “Servers reviewed” and “people trained” are weak outcome indicators if administrators still hold reusable global tokens and one endpoint is public.

IndicatorDefinitionReference target
Token audience isolationServers that reject a token minted for a different audience, over total servers. Measured by test, not by configuration review.100%
Passthrough incidenceRequests in which an inbound token was forwarded unchanged to any downstream system.0
Unique server identity coverageServers holding a unique, scoped workload identity, over total servers.100%
Standing privilege rateAdministrators with non-expiring production authority over the plane, over total administrators.Approaching 0%
Phishing-resistant coveragePrivileged interactive identities using a compliant strong authenticator, over total privileged identities.100%
Unapproved public endpointsManagement, registry or server endpoints reachable externally without an approved exception.0
Cross-tenant escape testsForbidden cross-tenant operations that succeeded in the automated negative suite.0
Catalogue coverageRunning servers present in the signed catalogue with a pinned digest, over total running servers.100%
Provenance at admissionProduction deployments whose artifact signature and provenance were verified before execution.100%
Exploited-vulnerability exposure timeFrom publication of a known-exploited issue affecting a component of the plane, to effective mitigation.Emergency SLO, hours not weeks
Audit completenessExpected high-value events successfully received at the external archive, over expected events.Approaching 100%
Audit export error rateFailed security-event exports over total. A rise here is an incident indicator in its own right.Near zero, alert immediately
Approval binding rateMutating operations executed under an approval cryptographically bound to the exact payload, over all mutating operations.100%
Approval grant rateApprovals granted over approvals requested, tracked as a consent-fatigue indicator. A rate approaching one means the prompt has stopped functioning as a decision.Monitored, not maximized
Denied-then-succeeded sequencesCases where a repeatedly denied operation subsequently succeeded — the signature of an attacker or an agent iterating toward a permitted phrasing.Investigated, every one
Credential revocation completionFrom revocation initiation to every enforcement point rejecting the credential.Bounded and exercised
Demonstrated recoveryMeasured clean-room restoration against the approved objective.Meets the objective, proven
Two of these are unusual and both earn their place. Approval grant rate is the only quantitative handle on consent fatigue: if operators approve everything, the human-in-the-loop control has degraded into a click-through and should be re-tiered rather than defended. Denied-then-succeeded sequences catch the behaviour that distinguishes an agent probing a policy boundary from a user making a mistake, and it is invisible to any single-request check.

15 Phased roadmap

Sequenced so that each phase is independently valuable and each gate is a thing that can fail. A phase whose exit criterion cannot fail is not a gate.

Phase 0 — Establish scope and stop the bleeding

Premise: most organizations already have an MCP estate; it simply has not been inventoried. Start by finding out what can already reach what.

1Inventory every host, client, server and registry in use, including the ones on developer workstations. Record what each server can reach and under which credential.
2Classify by maximum reachable authority, not by CPU or traffic. A server that can write to a repository outranks one that reads a wiki, whatever the usage numbers say.
3Find and eliminate token passthrough. This is the highest-value single change available and it is usually a small number of code paths.
4Externally scan for reachable endpoints. Bind local servers to localhost; confirm Origin validation is actually implemented rather than assumed from the SDK.
5Disable auto-install and auto-approval of public servers. Establish an allowlist even if it is initially just a list.

Gate: a named owner for every server; zero unapproved external exposure; no code path that forwards an inbound token.

Phase 1 — Isolation and identity baseline
1Containerize every server. Non-root, read-only root filesystem, dropped capabilities, explicit minimal mounts, resource bounds, no ambient network. Kernel-level isolation for anything third-party.
2Give each server and connector its own workload identity. Remove shared static secrets; prefer attested or federated short-lived credentials.
3Stand up token exchange. Audience-bound, down-scoped, ephemeral, one per target. Enforce audience validation at each server.
4Enforce server-scoped tool namespaces and reject duplicate registrations across connections.
5Route all security events to an external archive under separate administration, and verify that dropped events alert.

Gate: the cross-audience test — a token minted for one server is rejected by every other. A negative test proving a container cannot reach the network, write outside scratch, or read a canary outside its mounts. Audit events observable in the archive with production administration unable to delete them.

Phase 2 — Policy, supply chain and approval
1Deploy the policy decision point in front of every mutating tool. Deny by default. Build the structured input contract, with the enforcement point parsing operations rather than passing strings.
2Implement risk-tiered consent: automatic with audit for idempotent reads; session-scoped batch approval with rollback for reversible changes; cryptographically bound per-payload approval for destructive ones.
3Build admission control: signature verification, provenance, SBOM generation, digest pinning. An artifact that cannot satisfy policy does not run.
4Add egress control and SSRF defence at the broker: destination allowlists, link-local and private-range denial, resolution validated after redirects.
5Introduce authorization property tests and policy regression tests into the pipeline.

Gate: an unsigned artifact is rejected and the rejection is logged; the negative authorization suite passes across the full role and tenant matrix; an approval issued for one payload demonstrably fails to authorize a different one.

Phase 3 — Continuous assurance and recovery
1Establish just-in-time elevation for administration of the plane, with no standing production authority and step-up for policy, identity, key and audit changes.
2Separate the powers — identity, policy, key custody, signing, audit and backup administration into distinct roles held by distinct people.
3Build detection correlations rather than collection: privileged login followed by rapid policy widening; new server registration outside a change window; mass secret reads; key use from a novel workload; audit volume collapse; denied-then-succeeded sequences.
4Run adversarial prompt testing and red-team exercises from realistic footholds, scored on whether authority could be made durable.
5Prove recovery: restore the plane's state, policy and identity configuration from evidence whose integrity does not depend on the plane, in a clean environment.

Gate: a completed restoration exercise, a red-team report showing the foothold could not be converted to durable estate-wide authority, and a fail-safe test per security dependency.

Deliberately not a calendar. Published day counts for programmes like this are guesses dressed as plans, and they are the first thing a sponsor holds you to. The sequence is the durable part: exposure and passthrough before isolation, isolation before policy, policy before approval workflows, and recovery proven before the estate is treated as production. Fit that sequence to the organization's actual change capacity.

16 Decisions and open questions

Recorded with the reasoning intact, including the ones that are genuinely unresolved. An architecture document that contains no open questions is describing a system nobody has thought hard about.

#DecisionReasoningStatus & revisit
D-01Treat the model as an untrusted caller holding a legitimate credentialIt is the only assumption that survives indirect prompt injection. Every control is then placed to be effective whether or not the model was manipulated, which is a testable design property rather than a hope.Settled · foundational
D-02Zero token passthrough, without exceptionConverts full server compromise from lateral movement into a contained event. Now also a specification requirement, so the argument is no longer about whether but about migration order.Settled · conformance
D-03Policy evaluates structure, never intentA gate that reasons about what the request appears to mean inherits the ambiguity it exists to remove. Structural evaluation is reproducible, testable and explainable to an auditor; semantic evaluation is none of the three.Settled · foundational
D-04Content filtering is defence in depth, never a controlClassifiers on adversarial natural language have no reliability guarantee. Budget them as rate reduction and never let a safety case rest on one.Settled · annual
D-05Fail closed on loss of the policy or identity pathAuthorization uncertainty must never silently become a permit. The availability cost is real and is accepted explicitly by the risk owner rather than absorbed as an implementation detail.Settled · risk-owner approval
D-06Kernel-level isolation for third-party servers; container isolation for reviewed internal onesTiering by provenance rather than applying one posture everywhere. A uniform strict posture gets relaxed to the weakest requirement; a uniform weak one is indefensible for unreviewed code.Settled, cost-sensitive · per onboarding
D-07Audit custody sits outside the plane's administrative domainThe team operating the estate must not be able to alter the evidence used to judge whether it operated safely. This also survives the compromise case, which internal logging does not.Settled · governance
D-08Approval binds cryptographically to the canonical payload, with a short freshness windowOtherwise approval is transferable and an operator who approved one thing has approved the next thing. Freshness bounds the window in which a captured approval is useful.Settled · foundational
D-09Digest pinning rather than version pinning for serversA version tag is mutable in most registries. The rug-pull threat is specifically a change behind a stable identifier, which is exactly what a tag is.Settled · stable
D-10No exactly-once or fully-mitigated claim anywhere in the architectureInjection is not solved, capability attestation does not exist in the protocol, and content filtering is probabilistic. Stating residual risk plainly is what makes the rest of the document credible.Settled · foundational

Open questions — genuinely unresolved

Q1 Capability attestation

Nothing in the protocol lets a client cryptographically verify that a server is what it claims to be, or that its declared capabilities are honest. Signed catalogues compensate operationally but do not close it. Proposals exist — signed capability certificates, message-level authentication — and none is standardized.

Q2 Where the AST parser lives

Structural policy evaluation requires parsing the operation before the policy sees it. Putting a parser for every downstream query language in the enforcement point is a large surface with its own vulnerability profile. Pushing it to the server makes the parse untrusted. There is no clean answer; the current position is a small number of parsers for high-risk languages only, running sandboxed.

Q3 Consent at agentic speed

Risk-tiered consent reduces prompt volume but does not resolve the tension: the workflows with the most authority are exactly the ones designed to run without a human. Batch and budget-based approval models are promising and unproven at scale.

Q4 Cross-server chain authorization

Authorization is evaluated per call. The risk lives in sequences — read here, write there — and no widely deployed mechanism carries provenance from a read into the authorization decision for a later write. Taint tracking across tool calls is the obvious direction and nothing production-grade implements it.

Q5 Transcript and trace custody

Model transcripts, tool arguments and traces are a new class of sensitive store that inherits the sensitivity of everything the estate touched. Retention, access control and scanning for this class are immature almost everywhere, including in otherwise well-governed organizations.

Q6 Standards drift

The specification, the OWASP LLM identifiers, the OAuth 2.1 draft and the AI control frameworks all moved within the last twelve months. Any architecture in this space needs a scheduled re-verification cadence, and this one is written on the assumption that parts of it will be wrong within a year.

17 Source discipline

This reference was assembled from working drafts and then checked against primary sources. Several claims did not survive. Recording that is not a disclaimer — it is the part of the method that makes the rest usable.

Removed entirely. Two widely repeated figures — that “at least 14 CVEs” have been assigned across MCP servers and SDKs, and that “over 200,000 MCP servers” are exposed with remote-code-execution-capable configurations — do not survive checking. Both trace to one vendor research post from April 2026, and the misquotation is downstream of it rather than in it: that source's own headline says ten CVEs and its table lists nine unique identifiers, and its exposure sentence reads “7,000+ exposed servers — and up to 200,000 vulnerable instances in total,” where the larger number estimates total installations rather than internet-exposed ones. Secondary coverage collapsed the two, inflating the exposed-server count by roughly a factor of thirty. There is no authoritative count of MCP ecosystem vulnerabilities; competing claims in the same period range from nine to more than forty. The honest statement is that the number is unknown and growing, and neither figure appears anywhere else on this page.
Claim as draftedWhat the primary source actually saysWhy it matters
MCP specification revision 2025-06-18; transports are stdio and HTTP+SSEThe current revision is 2026-07-28. The transports are stdio and Streamable HTTP; HTTP+SSE has been deprecated since 2025-03-26.Two revisions of drift. Guidance written against the older transport gives advice about mechanisms that no longer exist.
MCP does not define authentication, authorization or identity bindingIt now does. The current revision carries a normative authorization specification requiring RFC 9728 metadata, RFC 8707 resource indicators, audience validation and PKCE — while remaining optional overall.The single most consequential correction here. Most published MCP security writing still asserts the older position.
Sessions and session IDs; “Session Hijacking” as a threatThe protocol is stateless with no protocol-level sessions. The threat is now State Handle Hijacking, and session-ID guidance applies only to 2025-11-25 and earlier.Advice about securing session identifiers is advice about a mechanism the current protocol does not have.
OWASP LLM06 Excessive Agency; LLM07 System Prompt LeakageIn the 2026 edition, Excessive Agency is LLM03:2026; System Prompt Leakage was renamed Hidden Context Exposure and is LLM08:2026. LLM06:2026 is Unbounded Consumption and LLM07:2026 is Misinformation.Two mappings pointed at the wrong risks. Any LLM Top 10 citation without an edition year will be wrong within a year.
OAuth 2.1 cited as though normativeStill an Internet-Draft — draft-ietf-oauth-v2-1, revision 15, March 2026, no RFC number. The MCP specification itself normatively depends on an earlier revision of that draft.Describing a draft as a standard is exactly the kind of imprecision that costs credibility in a control narrative.
gVisor implements “over 300” Linux system callsgVisor's own compatibility table: 288 of 351 have a full or partial implementation on linux/amd64, with 63 unsupported. What is unconditional is that no system call is passed through to the host.The overstated number is checkable in one click, and the correct framing — interception, not implementation — is the stronger security claim anyway.
Rego policy using rtrim; import rego.v1 presented as requiredrtrim is not a Rego built-in — the function is trim_right. And since OPA 1.0, import rego.v1 is effectively a no-op, retained only for cross-version library compatibility.A published policy that does not compile is worse than no published policy.
Policy validates “SQL AST structure”The drafted policy performed uppercase substring and regular-expression matching on a raw string. That is lexical matching, not structural evaluation, and it is bypassable by comments, literals, casing and dialect syntax.The gap between the claim and the mechanism was the most serious defect found. It is now stated as a limitation and as an architectural requirement on the enforcement point — see §08.
“DuneSlide” as an MCP exploit chainCVE-2026-50548 and CVE-2026-50549 are real, both CVSS 9.8, but they are Cursor IDE sandbox-escape flaws fixed in version 3.0. Neither record nor the vendor advisory mentions MCP; the researchers list a malicious MCP server as one possible delivery channel alongside a poisoned web result.Attributing an editor vulnerability to the protocol inflates MCP's incident record. Corrected in §11 rather than dropped, because the canonicalization lesson is genuinely transferable.
Storm-0558: a crash dump allowed key material to leave the secure environmentMicrosoft's March 2024 addendum states plainly: “we have not found a crash dump containing the impacted key material.” The leading hypothesis remains operational error; the specific crash-dump mechanism is unconfirmed.Presenting a vendor's stated hypothesis as established fact is the most common way security writing becomes unreliable.
CVE-2024-27199 as a 9.8 authentication bypassCVE-2024-27198 is the CVSS 9.8 authentication bypass. CVE-2024-27199 is a CVSS 7.3 path traversal permitting limited administrative actions. The vendor's advisory describes them jointly, which is where the conflation comes from.Per-CVE precision is exactly what a knowledgeable reader checks first.
Six functions attributed to NIST SP 800-207Govern / Identify / Protect / Detect / Respond / Recover are the CSF 2.0 Functions. SP 800-207 defines seven tenets of zero trust, a different document with a different purpose.Framework conflation is the kind of error that makes an assessor doubt everything else in the mapping.
Phishing resistance “recommended” at AAL2SP 800-63B-4: “Verifiers SHALL offer at least one phishing-resistant authentication option at AAL2,” and “Federal agencies SHALL require their staff, contractors, and partners to use phishing-resistant authentication to access federal information systems.” The SHOULD applies to encouraging use. Note the scoping clause on the second sentence: it is a federal-systems requirement, not a blanket one, and dropping those five words — as an earlier draft of this page did — broadens it into something NIST did not write.Understating a normative requirement weakens the case for a control that should not need arguing for.
“CSA AI Controls Matrix, AIS-04”The identifier exists in both the AI Controls Matrix (v1.1, “Secure Application Development Lifecycle”) and the Cloud Controls Matrix (“Secure Application Design and Development”). The citation is ambiguous without naming the framework and version.A control reference an assessor cannot resolve is not a control reference.

What could not be verified, and is therefore not asserted

  • Any count of MCP ecosystem vulnerabilities or exposed servers. Circulating figures range from nine to more than forty across the same period, none from an authoritative registry. No number appears on this page.
  • The current status of the proposed HIPAA Security Rule strengthening. The rulemaking record shows neither a final rule nor a withdrawal, and the responsible pages have not been refreshed recently enough to restate it. The defensible statement is that it remains proposed and the existing rule is in force.
  • Effectiveness rates for prompt-injection defences. Vendor-published detection rates are not independently reproducible and are not cited here in either direction.
  • The exact release date of CSA's AI Controls Matrix v1.1, where the issuing body's own pages disagree by three weeks. The version is cited; the date is not.
Why this section exists. Anyone can assemble a plausible security architecture from secondary sources; the failure mode is that it reads well and contains a dozen things that are quietly untrue. Publishing the corrections is the only way to distinguish work that was checked from work that was merely written — and it tells a reader precisely how much of this to trust when the next revision moves again.

18 References

Primary sources only. Every technical claim on this page traces to one of these; where a source has since been revised, the revision is what is cited.

Protocol

Identity and authorization

Frameworks and control catalogues

Runtime, policy and incidents