Four MCP security incidents and the control that would have stopped each
Four MCP security incidents from the past year, each traced to the advisory or the commit that fixed it. None of them needed a new kind of defence. Each was stopped by an ordinary control that the affected setups did not have.
I spent years on payment systems, and most incident reviews I sat in ended the same way: a check everyone assumed another layer was doing. MCP is young enough that those reviews are happening in public. I traced these four back to their advisories and patches to see what was affected, what fixed it, and what would have made each one a non-event before the patch existed.
LiteLLM: any Bearer token opened an MCP session
LiteLLM is a popular LLM proxy that can also put MCP servers behind one endpoint. Before version 1.84.0, its MCP endpoint accepted a made-up Authorization: Bearer header as a logged-in session.
The cause is in the advisory. When LiteLLM's own key check failed, the request fell into an OAuth2 passthrough path meant for upstream servers. That path swapped the failed check for an empty user object and carried on, where it should have rejected the request. With that session an attacker could list and call whichever MCP tools were open to all keys. Wiz, who found it, reported probes for it in their honeypots the day before they published.
CISA added it, CVE-2026-59822, to its Known Exploited Vulnerabilities catalog on September 2. You may have read that this was the first MCP bug on that list. It was not. CVE-2026-42271, a command execution bug in LiteLLM's MCP test endpoints, went on in June.
Fixed in: 1.84.0, released May 14. The fix shipped two months before the bug was disclosed, so anyone who kept up with releases was never exposed.
What would have stopped it: a second, separate check in front of the endpoint. The bug was in LiteLLM's own authentication, so a reverse proxy that required SSO or a client certificate, or simply keeping the endpoint off the internet, would have refused the forged session before LiteLLM saw it. If you do not use LiteLLM's MCP feature, block /mcp/ at the proxy, which is the vendor's own workaround.
The official fetch server reached private addresses
mcp-server-fetch is one of the reference servers in the MCP project. It has one tool, fetch, which takes a URL from the model and returns the page. Until October it requested any URL it was given, followed redirects, and never checked where the host resolved. A page with a prompt injection could steer the agent to http://169.254.169.254/ or to a service on your private network, and the response came back to the model.
The project's README already warned that the server could reach internal addresses, so a fair reading is an unsafe default rather than a hidden bug. A third-party numbering authority still issued CVE-2026-104120 for it. Its list of affected versions is wrong: by the releases on PyPI, every version before 2026.10.10 lacks the check.
The fix, PR #5033, resolves the host before each request and on every redirect hop, and refuses loopback, private, link-local and metadata ranges. Turning that off takes a command-line flag, --allow-private-ips, so the model cannot ask for it.
Fixed in: mcp-server-fetch 2026.10.10.
What would have stopped it: an egress allowlist on whatever runs the server. If the container can reach only the public internet, the redirect to a metadata address goes nowhere, whatever the code does. Blocking the metadata addresses at the host firewall and requiring IMDSv2 on AWS covers the worst outcome, which is stolen cloud credentials. Running it over stdio does not help here, because the attacker is the content being fetched, and it is already inside. How to sandbox an AI agent and control its egress walks through the container setup.
Grafana's MCP server accepted sessions it never issued
Over its network transports, mcp-grafana checked that an Mcp-Session-Id header looked like a session ID. It did not check that it had issued that ID. Ariel Fogel at Pillar Security showed that anyone who could reach the port could make up an ID and call every enabled tool as the server's Grafana service account, including grafana_api_request, which calls the Grafana API with that account's token.
There is no CVE for the session part. Grafana's position is that the server shipped without inbound authentication by design, so adding it is an improvement and not a fix. The same report did produce CVE-2026-19516, a server-side request forgery through an X-Grafana-URL header, rated 9.1.
Changed in: v1.1.0, August 10. It added an optional bearer token for the SSE and streamable HTTP transports and removed the header. The token is optional. A v1.1.0 or later server started on a network address without one still runs, and only logs a security error at startup.
What would have stopped it: run it over stdio, or bind it to 127.0.0.1, or configure the token. Any one is enough. Then give the service account the Viewer role on the folders the agent needs, because that account is the ceiling on what a forged session can do. Local vs remote MCP servers covers when a server should be listening on a network at all.
postmark-mcp copied every email to its author
This one is older, from September 2025, but it is still the cleanest supply chain example MCP has. Postmark's official server is @activecampaign/postmark-mcp. Someone else published an unscoped package called plain postmark-mcp, shipped fifteen versions that worked, and in 1.0.16 added one line to the send tool: a hard-coded Bcc to an outside address. Every email an agent sent through it, password resets and invoices included, was copied out.
npm shows 1.0.16 going up on September 17, 2025 and every version being unpublished on September 25. OpenSSF tracks it as MAL-2025-47604, and Snyk's analysis shows the diff. Copies already installed kept working after the unpublish.
What would have stopped it: installing the vendor's own scoped package, and pinning an exact version so a new release cannot arrive unreviewed. npx -y postmark-mcp takes whatever is newest every time it starts. Anyone pinned to 1.0.15 never ran the bad code, and the diff to 1.0.16 was one visible line. How to pin MCP server versions has the commands for npx, uvx and Docker.
The controls that helped with the other three do little here. An egress allowlist does not catch it, because the stolen mail went out inside a legitimate call to Postmark's own API. A log of tool calls shows normal arguments, because the BCC was added inside the server. The only other place it showed was Postmark's message log, which listed a recipient nobody had asked for.
What the four have in common
| LiteLLM | fetch | Grafana | postmark-mcp | |
|---|---|---|---|---|
| Separate auth in front of network endpoints | Stops | - | Stops | - |
| Localhost or stdio only | Stops | No | Stops | No |
| Egress allowlist, metadata blocked | No | Stops | Limits | No |
| Official package, pinned version | Stops | Stops | Limits | Stops |
| Least-privilege upstream credential | Limits | Limits | Limits | Limits |
| Tool call log with arguments | Detects | Detects | Detects | No |
The last two rows never stop anything on their own, but they decide how bad a bad day is. A read-only token caps what a forged session or a steered fetch can do. A log with the caller and the arguments is how you find out, which matters most for the LiteLLM case, where every forged call came from an empty identity.
Two of these, Grafana and the fetch server, were working as documented. If you run MCP servers from their quick start, assume the defaults were picked for a laptop and check them before anything listens on a network.
Where Statio fits
I build Statio, so here is what it does about each of these, and where it would not have helped.
The gateway refuses to call anything that resolves to a private, loopback or metadata address, and connects to the exact address it checked. Servers Statio hosts have no direct route to the internet: their traffic goes through an egress proxy that allows only the destinations on that server's list, and they are not reachable from outside, so the Grafana and LiteLLM style of attack has no port to aim at. The details are on /security.
For supply chain, our Postmark entry runs @activecampaign/postmark-mcp pinned to 2.1.1, with a comment in the catalog source telling whoever edits it next never to switch to the unscoped name. Catalog servers are scanned before they deploy, including for unexpected outbound calls. I would not promise that scan catches a one-line BCC, because the call it rides on is one the server is meant to make. Pinning the right package is what protects you there.
Questions
Put your MCP servers behind one checked door. Three servers and 10,000 calls a month, free.