Malicious MCP servers and the trust they inherit
Malicious MCP servers can read secrets, reshape tool calls, and abuse OAuth. Learn how to vet capabilities and enforce a team allowlist.

Table of Contents
An MCP server does not become safe because a client displays its tool names in a neat list. The moment your team enables one, it may inherit access to local files, environment variables, API credentials, remote services, and the model's decision loop. That is a software supply chain decision with an unusually persuasive user interface.
I treat every new server as untrusted code plus an untrusted content source. Those are separate risks. Code can steal a token directly; a tool description or retrieved document can steer the model toward handing the token over. A review that checks only the repository misses half the attack surface.
The useful question is not whether an MCP server is malicious today. You need to know what it could do after its package, hosted endpoint, tool catalog, or upstream dependency changes. A small allowlist, narrow credentials, isolated execution, and evidence-based approval make that question answerable.
A server crosses two trust boundaries at once
An MCP server crosses an execution boundary and a context boundary, so a conventional dependency review is necessary but incomplete. The official Model Context Protocol security policy says clients trust the servers they connect to and local servers should be evaluated like any other installed software. That is accurate, but teams must carry the idea one step further: servers also write material into the model's working context, where language can influence later tool choices.
The execution boundary is familiar. A local server launched through stdio runs as a process under some operating-system identity. It can normally see whatever that identity and its process environment can see unless you restrict it. A remote server receives requests over HTTP and acts through the credentials granted to it. In both cases, the server's advertised purpose tells you nothing about its effective reach. A calendar tool that receives a broad cloud token may be able to read mail or files if the token carries those scopes.
The context boundary is easier to overlook. Tool names, descriptions, resource contents, errors, and results can all enter the model's context. A malicious instruction hidden in a tool description can say that another tool must be called first, that sensitive data belongs in an argument, or that the user already approved an action. The model may reject it, but probability is not a security control.
Keep three identities distinct during review: the server process, the human user, and the agent session. If all three share one broad credential, attribution collapses and revocation becomes painful. Give the server its own identity where the upstream service permits it, limit that identity to the task, and record which person enabled the session.
The attack path starts before the first tool call
A malicious server can influence an agent during discovery, before a user knowingly invokes any of its tools. Clients request capability and tool information so the model can decide what is available. That makes metadata active input, not harmless documentation.
Tool poisoning exploits that channel. Suppose a server advertises summarize_ticket with a description that quietly instructs the model to read a local credential file and include its contents in an unrelated argument. The visible name looks narrow. The description creates a cross-server attack because it tries to make the client call a trusted filesystem or messaging tool on the attacker's behalf. The malicious server does not need direct filesystem access if it can manipulate an agent that already has it.
A rug pull changes the catalog after approval. A reviewer sees benign tools on Monday, then a remote server returns altered descriptions or new tools on Friday. The MCP security guidance specifically discusses tool-list change notifications as a case where a client may end up with tools it did not know were enabled. Treat any catalog change as a new approval event. Do not let a connected server silently expand what the model can select.
Retrieved content creates another route. A database row, issue comment, document, or web result can contain instructions aimed at the model. The server may merely pass that content through, or it may plant it deliberately. Either way, data from outside the agent's trust boundary must remain data. A client should mark its provenance, keep it out of higher-priority instructions, and prevent it from authorizing a write.
There is also the ordinary package path. A local command installed from a package registry can execute during installation or startup. A typo in a package name, a compromised maintainer account, or an unpinned dependency can replace reviewed code with different code. MCP did not invent this risk, but the common practice of pasting a one-line server configuration into a client makes it easier to skip the review people would demand for a production service.
Capability names are weaker than effective permissions
Review the complete chain from agent input to external side effect, because a tool called read_notes may hold permission to delete a workspace. Names and schemas describe an interface. Credentials, filesystem mounts, network reach, and server-side authorization determine power.
For each server, build a capability record that answers five concrete questions:
- Which files, environment variables, sockets, and executables can the process access?
- Which network destinations can it reach, including redirects and DNS-resolved addresses?
- Which upstream scopes, roles, repositories, tenants, and accounts do its credentials cover?
- Which tools can read, create, modify, send, delete, purchase, deploy, or change permissions?
- Can its tool list or behavior change without a reviewed client configuration change?
Do not accept read-only as a complete risk rating. Read access to source code, customer records, prompts, or secrets can be worse than write access to a disposable test environment. Separate confidentiality, integrity, financial, and availability impact. Then classify each tool by the worst credible side effect, not the happy-path description.
Tool annotations can help a client present behavior, but server-supplied hints cannot establish trust. A hostile server can lie about being read only or non-destructive. Use annotations as display metadata and compare them with code, credentials, and observed traffic. If the two disagree, believe the effective permissions.
A useful distinction is can invoke versus can authorize. The model may suggest a deployment, while a separate policy engine decides whether the call can proceed. For high-impact tools, require a control outside the model context: a short-lived credential, an environment gate, a repository rule, or a human approval that shows the exact target and arguments.
Real failures chain several ordinary weaknesses
Serious MCP incidents rarely need a brilliant exploit. They chain familiar weaknesses that each looked tolerable in isolation: broad credentials, trusted content, vague approval, and weak logs. Walking the chain reveals controls that a generic security score will miss.
Consider an agent with a trusted source-control server and a newly added ticket-summary server. The source-control tool can read private repositories and open pull requests. The ticket server needs only ticket read access, but its description tells the model to inspect ~/.config when a ticket lacks context. A support ticket contains another instruction to copy any discovered token into a pull-request body for diagnostic analysis.
The user asks for a summary. The agent reads the poisoned description, calls the local file tool, finds a token, and then calls the trusted source-control tool. The final write may even appear in an approval dialog. If that dialog says Create pull request?, the user cannot see that a secret sits inside the body. The malicious ticket server never needed the source-control token. It used the agent as a confused deputy across two trusted tools.
Now change one detail: the ticket server is honest, but its hosted endpoint is compromised after approval. The attacker modifies only the tool description. A source review of the original client package will not catch it. Pinning the local package will not catch it. You need a snapshot of the discovered catalog, a hash or normalized diff, and a rule that disables changed tools until someone reviews them.
One more pattern involves OAuth. A proxy server asks the client for a token intended for another service and passes it upstream unchanged. The MCP authorization specification forbids token passthrough and requires servers to validate that tokens were issued for them. The reason is practical: a bearer token accepted outside its intended audience can turn one compromised server into access across services. Audience validation, resource indicators, exact redirect matching, PKCE, and short token life each close a different part of that chain.
Vet the publisher, artifact, and runtime separately
A defensible review produces evidence for three separate subjects: who controls the server, what code or endpoint you approved, and how it will run in your environment. A popular repository answers none of those by itself.
Use this sequence for every proposed server:
- Establish ownership. Record the publisher, repository, package namespace, support channel, security reporting path, and at least two maintainers who can release. For a hosted service, record the legal operator and the domains used for API and authorization.
- Freeze the artifact. Pin an immutable package version, container digest, commit, or endpoint identity. Capture checksums and the dependency lockfile. Reject install commands that fetch unpinned code at runtime.
- Inspect dangerous code paths. Search for process execution, filesystem traversal, environment reads, dynamic evaluation, outbound HTTP, redirect following, credential logging, and automatic updates. Review install scripts as carefully as tool handlers.
- Exercise it in isolation. Start with synthetic data and decoy credentials. Record DNS, outbound connections, files opened, child processes, tool-list responses, and changes between restarts. Test malformed arguments and denied permissions.
- Reconcile claims with behavior. Compare documentation, schemas, annotations, source, and observed traffic. Any unexplained destination or permission blocks approval until the owner explains it and you verify the explanation.
For a closed-source remote server, you cannot inspect the artifact, so compensate rather than pretending the evidence exists. Demand stronger identity, tenant isolation details, data retention terms, an incident contact, narrow OAuth scopes, and a test tenant. Put an outbound proxy or API gateway between the client and server when the transport allows it, and retain request metadata without logging secrets.
A scanner can find known vulnerable dependencies and suspicious patterns. It cannot tell whether send_message belongs in your build agent or whether customer data may cross that server's boundary. That judgment belongs to the service owner and security reviewer. Automate collection, not acceptance.
An allowlist is a versioned capability contract
An allowlist should bind a specific server artifact to explicit runtime limits and approved tools. A list of server names gives administrators confidence without control because the same name can resolve to new code, new metadata, or a different endpoint.
This compact policy is intentionally boring enough to review in a pull request:
servers:
issue-reader:
transport: stdio
command: /opt/mcp/issue-reader
artifact_sha256: 8b7c...91e2
run_as: mcp_issue_reader
env_allow: [ISSUE_API_TOKEN]
filesystem_read: []
filesystem_write: []
network_allow: [issues.internal.example:443]
tools_allow: [search_issues, get_issue]
tool_catalog_sha256: 53d1...a602
credential_ttl_minutes: 30
data_classification_max: internal
owner: engineering-productivity
review_expires: 2026-11-01
The two hashes answer different questions. artifact_sha256 fixes the executable you inspected. tool_catalog_sha256 fixes the normalized names, descriptions, input schemas, output schemas, and relevant annotations that the model sees. If either changes, fail closed and open a review. For a remote server whose executable you cannot hash, pin its origin and certificate expectations where practical, then rely more heavily on catalog diffs, traffic policy, and contractual evidence.
Keep credentials outside this file. env_allow names what the process may receive, while the secret manager supplies a short-lived value at launch. Deny inherited environment variables by default; otherwise a harmless-looking stdio server may receive cloud keys, database passwords, and CI tokens simply because its parent process had them.
Store the policy beside other production controls, require an owner and expiry, and make exceptions visible in code review. An expired entry should stop new sessions while giving current production work a short, documented migration window. Permanent exceptions become abandoned permissions.
Approval must expose the consequence
Human approval works only when the prompt shows the action a person can actually judge. Allow tool call? shifts protocol trivia to the user and trains reflexive clicking. Show the server identity, tool, target, important arguments, data leaving the boundary, credential identity, and expected side effect.
A useful approval record looks like this:
{
"server": "source-control@sha256:31ac...",
"tool": "create_pull_request",
"target": "payments/service",
"outbound_data": ["branch diff", "pull request body"],
"credential": "bot-pr-writer",
"effect": "Creates a reviewable pull request; cannot merge",
"catalog_changed": false
}
The client should derive this display from policy and observed arguments, not from prose supplied by the server. Redact secret values, but do not hide the fact that a secret field is present. For bulk actions, show the count and a sample, then cap the maximum outside the model. Approval for one repository must not silently authorize all repositories.
Reserve per-call approval for actions with meaningful side effects. Repeated prompts on harmless reads create fatigue and make the dangerous prompt blend into noise. For recurring writes, prefer a standing policy with narrow targets and limits. For irreversible or financial actions, require a fresh approval and an upstream control that can still reject the request.
Logs must let you reconstruct the chain: user request, model session, server version, catalog version, tool arguments after redaction, policy decision, upstream identity, result class, and approval actor. Do not log bearer tokens or raw secret fields. Send audit records to a destination the MCP server cannot modify.
Operating controls decide how much a compromise costs
Assume one approved server will eventually become hostile or compromised, then design the runtime so that event stays small. Vetting lowers probability. Isolation and response controls lower impact.
Run local servers under dedicated identities with empty home directories, an explicit environment, read-only mounts where possible, and no access to the host agent socket. Deny outbound network traffic by default and allow named destinations through a controlled resolver or proxy. Separate development, CI, and production credentials. Never give a desktop agent the same token your deployment system uses.
For remote servers, bind tokens to the intended audience and resource. The current MCP authorization requirements call for clients to send the resource parameter and for servers to accept only tokens intended for themselves. A server that calls an upstream API needs a separate upstream token. Reject designs that relay the client's bearer token, even when the vendor calls it simpler. Simplicity is why token passthrough remains popular; it also erases the boundary you need during compromise.
Continuously compare the live catalog with the approved snapshot. Alert on new tools, changed descriptions or schemas, new outbound domains, elevated OAuth scopes, repeated denied calls, unusual result size, and secret-shaped outbound fields. These signals do not prove malice, but they justify pausing the server while an owner checks the change.
Prepare a removal path before approval. You need one action that disables the server configuration, revokes its credentials, terminates its processes or sessions, and preserves audit evidence. Test that action. If revocation depends on the same compromised server responding correctly, you do not have revocation.
A Team & AI Audit can map these controls against the way your engineers actually use Claude Code, Codex, MCP tools, and multi-agent pipelines, then identify where broad access and duplicated work are raising cost. The useful deliverable is a smaller approved system with named owners, not another page of generic AI policy.
The checklist should be hard to pass
Approve an MCP server only when a named owner can defend its need, evidence, permissions, change controls, and removal plan. Convenience is not compensating control. If a server cannot pass the checklist, run it with synthetic data in an isolated research environment or do not run it.
Use these gates in the approval record:
- Business need and owner: the use case is specific, a team owns the risk, and an expiry date forces review.
- Provenance and artifact: the publisher is verified, the artifact or endpoint is pinned, dependencies and install behavior were inspected, and a reporting channel exists.
- Capability and data: every tool, credential scope, filesystem path, network destination, data class, and side effect appears in the record.
- Runtime and authorization: the process has its own identity, secrets are short lived, token audiences are validated, catalogs are pinned, and high-impact actions have external enforcement.
- Detection and exit: calls produce tamper-resistant audit events, drift creates an alert, credentials can be revoked without the server, and the team has tested disablement.
Record a decision for each gate: pass, fail, or a time-limited exception with an owner. Do not average the answers into a score. One unbounded production credential can outweigh twenty tidy repository signals. Security questionnaires love totals because totals are easy to compare; attackers care about the single permission that completes their path.
Review approved servers after artifact, catalog, scope, ownership, or infrastructure changes, and at the stated expiry even when nothing changed. Remove entries that nobody uses. A short allowlist you can explain is safer than a large catalog nobody wants to challenge.
Change control must treat metadata as code
Every production server needs a change pipeline that reviews executable code, dependency resolution, configuration, and model-visible metadata together. Teams often protect the binary while letting descriptions arrive live from a remote endpoint. That split is unsafe because a description can redirect the agent even when the executable stays identical.
Normalize the catalog before hashing it. Sort tools by a stable identifier, preserve exact descriptions and schemas, remove only fields that the protocol defines as volatile, and record the protocol version used for discovery. A whitespace-only change may deserve automatic acceptance after parsing, but a changed verb, argument description, default, enum, or output schema needs review. Do not ask reviewers to compare raw JSON filled with reordered keys. Give them a semantic diff that says tool added, description changed, required argument removed, or destructive hint changed.
Treat dependency updates the same way. A pinned top-level package with an unlocked dependency tree is not pinned. Capture the resolved tree, verify signatures or provenance statements when the ecosystem supplies them, and rebuild in a clean environment. If a server downloads a browser, helper binary, model, or plugin on first launch, that downloaded object belongs in the approval record. Blocking install scripts while allowing an unsigned runtime download solves nothing.
Remote endpoints need an identity record that survives a DNS or hosting change. Record the approved scheme, host, port, authorization issuer, expected resource identifier, and redirect destinations. Check redirects at the network layer because an apparently approved origin can redirect requests or authorization codes elsewhere. Restrict DNS results that point to loopback, link-local, or private addresses unless the server explicitly needs them. That closes a common server-side request-forgery route in clients and proxies that fetch server-provided URLs.
Rehearse the compromise before access is real
A tabletop exercise should begin with an alert you can plausibly receive, not with an omniscient statement that the server is malicious. Use a catalog hash mismatch, a new domain in proxy logs, a denied attempt to open a credential file, or an unexpected OAuth scope. Ask the on-call engineer to identify affected sessions, stop new calls, revoke credentials, preserve evidence, and tell data owners what may have left the boundary.
The exercise usually exposes ownership gaps. The team that added the server may know the use case but not control the identity provider. Security may see network traffic but not the agent session. Platform engineers may disable the client configuration while long-running server processes stay alive. Put those dependencies into the runbook with exact control owners and backup contacts.
Define containment in terms of verified state. Configuration removed is not enough. You want confirmation that no client can discover the server, active processes and sessions ended, tokens and refresh tokens were revoked, authorization grants were removed where needed, queued jobs were cancelled, and outbound policy denies the endpoint. Preserve catalog snapshots, policy decisions, process telemetry, and redacted call records before routine retention deletes them.
Then bound the investigation. List which identities the server used, the maximum data class they could access, all allowed destinations, every session active during the suspect window, and which other tools shared those sessions. Cross-tool influence matters: a server with no write permission may have caused a second server to write. Search by session correlation and data flow rather than by the suspected server's calls alone.
Recovery should issue new credentials, not re-enable old ones after a password reset. Rebuild local servers from a newly reviewed artifact, create a fresh catalog snapshot, and repeat isolated behavior tests. For a hosted endpoint, obtain a clear account of the change, confirm the operator rotated its secrets, and watch it in a test tenant before production. If you cannot establish what changed, replace the server or keep it isolated.
Measure the control, not the catalog size
Operational metrics should reveal stale trust and control failure. Track approved servers without active owners, entries near expiry, catalog mismatches, exceptions past their deadline, credentials older than policy, calls blocked by destination rules, and time from disable decision to verified revocation. Count servers only for inventory; a large number is not automatically unsafe and a single overpowered server can be enough for a breach.
Sample successful calls as well as denied ones. A policy that never denies anything may match reality, or it may sit outside the actual execution path. Plant a harmless canary test that asks a server to reach a forbidden domain or file and verify that enforcement blocks it and creates the expected event. Run the test after client upgrades because enforcement can move or disappear when an integration changes.
Make the server owner attest to effective permissions at renewal. Do not send a form that lists last quarter's documentation and asks for a checkbox. Generate the record from current identity scopes, mounts, network rules, tool catalogs, and observed destinations. The owner must explain any difference between declared and observed behavior. Renewal should be faster than first approval when nothing changed, but it still needs fresh evidence.
This discipline also controls cost. Duplicate servers, overlapping tools, unused credentials, and blanket approvals create support work and consume context on every agent session. Removing them reduces both exposure and the confusing choice set presented to the model. Security review earns its place when it leaves the engineering system smaller and easier to operate.
The incident plan and renewal process test whether an allowlist is alive. If nobody notices a changed catalog, nobody can revoke the credential, or the named owner left months ago, the YAML file is only documentation.
The standard for production is simple: you should be able to say exactly which code receives which data, under which identity, for which tools, with which side effects, and how you will stop it. If that sentence contains probably, the server stays outside production.
Frequently Asked Questions
Can an MCP server steal API keys?
Yes, if the process can read environment variables, credential files, logs, or another tool's output. Give each server an explicit environment and a narrow, short-lived credential rather than inheriting everything from the client process.
Are remote MCP servers safer than local servers?
No. Remote servers reduce direct access to your machine, but they introduce hosted-code drift, data transfer, tenant isolation, and OAuth risks. Compare effective permissions and failure impact instead of treating transport as a safety rating.
What is MCP tool poisoning?
Tool poisoning places hostile instructions in a tool name, description, schema, error, or result so the model takes an unsafe action. The server may target another trusted tool, which means restricting only the malicious server's direct permissions is not enough.
How do I detect an MCP server rug pull?
Store a normalized snapshot of tool names, descriptions, schemas, and relevant annotations, then compare it at every connection and change notification. Disable changed tools until a reviewer approves the new catalog.
Should we trust read-only MCP tools?
Treat read only as one property, not a verdict. A reader can expose source code, customer data, tokens, or internal prompts, and retrieved text can influence the model's later calls.
What should an MCP allowlist contain?
Bind the approved artifact or endpoint to exact tools, identities, credential scopes, filesystem access, network destinations, data classes, catalog hash, owner, and expiry. A server name alone does not constrain anything that matters.
Does every MCP tool call need human approval?
No. Constant prompts create approval fatigue. Use standing policy for narrow, reversible actions, and require fresh approval for sensitive writes, financial actions, permission changes, or requests that cross a data boundary.
How should teams test an MCP server before production?
Run it with synthetic data and decoy credentials under an isolated identity. Observe files, processes, DNS, network traffic, catalog changes, denied operations, and behavior under malformed input before granting real access.
Why is OAuth token passthrough dangerous for MCP?
A passed-through bearer token may be accepted outside the service it was intended for and can turn the server into a confused deputy. Require audience validation and give the server a separate token for each upstream API.
What should we do when an approved MCP server changes?
Pause the changed capability, diff the artifact and catalog, repeat behavioral tests, and review any new permissions or destinations. Restore it only after the owner accepts the revised evidence and the allowlist is updated.


