RC RANDOM CHAOS

MCP is an attack surface, not a feature

MCP puts data and control in one channel and runs the model's tool calls with the user's full authority. Why the design fails, from the attacker's view.

· 9 min read
MCP is an attack surface, not a feature

The Model Context Protocol gives a language model the ability to call tools, read files, and reach external systems through a single standard interface. That is the entire value proposition. It is also the entire attack surface. A protocol whose purpose is to let a probabilistic text generator take actions against real systems is not a productivity feature. It is a delegation of authority to a component that cannot reason about authority. From a penetration tester’s position, that sentence is the whole finding. Everything else is detail.

MCP works by exposing servers that advertise tools. Each tool ships a name, a description, and an input schema. The client injects those descriptions into the model’s context so the model knows what it can call and when. The transport is stdio or HTTP. The client is usually a desktop agent or an IDE running with the full privileges of the logged in user. When the model decides to call a tool, the client executes that call on the user’s behalf. The identity boundary at that moment is the user’s identity, not the model’s, and not the server’s. That distinction is where the design starts to fail before a single exploit is written.

Read the flow as an operator reads an access path. Untrusted text enters the model. The model produces a tool call. The client executes it with real credentials against real systems. There is no enforced separation between the data the model reads and the actions the model takes. The same context window that holds a web page, an email, or a document also holds the instructions that decide which tools fire. Input and control share one channel. In every other domain we treat that as a defect. In MCP it is the specification.

The protocol was built on an assumption that made sense inside a demo and does not survive contact with production. The assumption is that the model is the trusted decision maker at the center, that the tools it calls are the tools its operator intended, and that the content flowing through the context is data to be read rather than instructions to be obeyed. Under that assumption, the model is treated as a reasoning agent that selects tools the way a careful engineer would, and the human stays in a supervisory role that catches anything wrong before it executes. The trust model places the language model where a policy engine should be.

That assumption also treats the MCP server as a benign extension of the client. A server publishes tool descriptions, the client presents them to the model, and the description is taken at face value as a neutral capability label. Nobody in the original design treated the tool description itself as attacker controlled input. Nobody treated the data a tool returns as attacker controlled input either. The mental model was a clean function call: the model asks, the tool answers, the answer is trusted. Authentication between client and server was not a hard requirement in the early specification. Trust was assumed by proximity. If the server was installed, it was treated as authorized, and whatever it said was treated as true.

Stacked on top of that is a permission assumption. The design assumes that granting a model access to a set of tools is equivalent to granting a well behaved script access to those tools. It assumes the operator understands, at connect time, the full set of actions those tools enable and the full set of systems they touch. It assumes the human reviewing tool calls has the context, the speed, and the attention to approve or reject each one on its merits. Those are not security controls. Those are hopes about human behavior wrapped around an autonomous execution loop. If a system allows an action, that action will eventually run. The assumption was that the model, and the human behind it, would choose not to.

What breaks the assumption is that the model does not distinguish between instructions from its operator and instructions embedded in the content it processes. A document, a web page, a code comment, an email body, or a returned API response can carry text that the model reads as a command. This is indirect prompt injection, and MCP turns it from a nuisance into remote code execution by proxy. The attacker does not need to compromise the client. The attacker writes instructions into content the model will read, the model calls a tool in response, and the client executes that call with the user’s credentials. The trusted decision maker at the center is steerable by anyone who can place text in its path.

The tool description channel breaks the assumption a second way. Because descriptions are injected into context and treated as authoritative, a malicious or compromised server can write instructions into a tool description that the model will follow. This is tool poisoning. The description that is supposed to say what a tool does can instead tell the model to exfiltrate a file, to prefer one tool over another, or to pass secrets into an argument. The user sees a short label in the interface. The model sees the full payload. The gap between what the human approves and what the model actually reads is not an edge case. It is a standing condition of the protocol, and it is invisible at the point of consent.

The last thing that changed is scale and composition. A single client now runs multiple servers at once, and the model can chain them. A tool that reads untrusted data and a tool that writes to an external system sit in the same context with the same identity. Nothing in the base design enforces that data from a low trust tool cannot become the argument to a high trust one. That is the confused deputy problem, rebuilt as a feature. Automation scales both control and failure, and MCP automates the step between reading hostile input and acting on it. The assumption that a human stays in the loop does not hold when the loop runs faster than a human can review it and when the instruction that triggers the action is buried in content the human never sees.

The failure executes in the gap between the model emitting a tool call and the client running it. Nothing in that gap checks provenance. The client does not ask whether the instruction that produced the call came from the operator or from a web page the model read two steps earlier. It runs the call under the user’s session, with whatever tokens, keys, and file handles that session already holds. Authority is ambient. The action inherits the full standing privilege of the human, and the model’s decision is treated as equivalent to the human’s decision. There is no step that re-authenticates the intent behind the call.

Because every connected server shares one context and one identity, capability composes without a trust boundary between components. A read tool that pulls an external document and a write tool that posts to an internal system sit at the same privilege level. The output of the first can flow directly into the argument of the second. No mechanism downgrades the trust of data that entered from a low trust source before it is used to drive a high trust action. The model is the only thing deciding whether that flow is safe, and the model cannot separate operator instructions from injected ones. The deputy holds the authority. The attacker supplies the direction.

The human approval step does not close this. Approval operates on the label the interface shows, not the payload the model consumed. The reviewer sees a tool name and a short description. The reviewer does not see the poisoned tool description, the injected instruction inside a returned document, or the argument the model assembled from hostile content. Consent is granted against a summary and executed against the full context. Speed compounds it. The loop runs at machine rate across chained tools, and a human cannot inspect the provenance of each argument at that rate. The control that was supposed to catch the failure is positioned where it cannot see the failure.

The pattern is old. Any system that carries data and control instructions in the same channel will eventually execute attacker data as control. SQL injection is that defect in a query string. Command injection is that defect in a shell line. Format string bugs are that defect in a parser. MCP is that defect at the level of an autonomous agent, and the interpreter is a model built to follow instructions in whatever text it is given. Every prior instance of this class was fixed by separating the channels: parameterized queries, argument arrays, escaping at the boundary. MCP has no equivalent separation, because the model’s input and the model’s control are the same tokens by construction.

Identity is the boundary, and MCP places the boundary in the wrong component. The action carries the user’s identity while the decision is made by a model steerable by third party content. The system trusts by proximity. An installed server is treated as authorized. A returned response is treated as true. A tool description is treated as a neutral label. None of those trust assumptions are validated at the point of action. Trust that is assumed once at connect time and never revalidated is not a control. It is a standing grant, and the protocol extends that grant across every server the client runs.

Automation is what converts the defect into exposure. A human reading a hostile document and choosing to ignore it is a non-event. A model reading the same document and emitting a tool call is code execution by proxy. The protocol removed the human from the execution path and left the human only in an approval path that cannot see provenance. That is the confused deputy problem rebuilt as the core loop. If a system allows an action, that action will run. MCP allows any action the connected tools expose, triggered by any text the model reads, under the user’s identity. The exposure is not a fault in one implementation. It is the shape of the design.

The model is not a policy engine. It cannot enforce authority because it cannot separate authority from suggestion, and the protocol feeds it both through the same channel. Enforcement has to sit below the model, in the client and in the systems the tools reach, where provenance and privilege can be evaluated by a component that is not steerable by the text it processes. Any design that leaves the model as the decision point for privileged action has already accepted that third party content can direct that action.

What must now be true is specific. Each tool call must carry the provenance of the instruction that produced it, and high trust actions must refuse arguments derived from low trust sources. Identity must be scoped per tool and per action, not inherited whole from the user’s session. Tool descriptions and tool outputs must be handled as untrusted data, not as authoritative context. Authentication between client and server must be mandatory, not assumed by installation. Human approval must present the full payload the model acted on, not a label. None of these are present in the base protocol. Until they are, the connected client runs with the user’s full authority under the direction of anyone who can place text in the model’s path.

MCP did not introduce a new vulnerability class. It industrialized an old one and wired it to production credentials. The question was never whether the model would be steered. Models follow instructions. That is their function. The question is why a protocol handed a probabilistic instruction follower the standing authority of the logged in user with no boundary between the content it reads and the actions it takes. Controls that are not enforced are not controls. Trust that is not revalidated is not trust. A protocol that cannot tell data from command has not delegated a task. It has delegated the keys, and it will hand them to whoever writes the right paragraph. That is the finding. Everything else is remediation.

Share

Keep Reading

Latest on the Wire

Full wire →

New signal daily · RSS

Stay in the loop

New writing delivered when it's ready. No schedule, no spam.