In May 2025, Invariant Labs showed how to steal private code with one GitHub issue. A user had the official GitHub MCP server connected to their agent, plus one public repository and some private ones. An attacker opened an issue on the public repository with instructions hidden in its text. Then the owner asked the agent something harmless: have a look at the open issues. The agent read the issue, followed it, pulled data from the private repositories and published it in a new pull request on the public one. In the demo that leak included the names of private projects, a plan to move to South America and a salary.

Nothing in the GitHub server was broken. The researchers stressed that the flaw sat in how the agent was put together, so GitHub could not fix it with a server patch. Every tool did what it was built to do. The agent simply had private data, untrusted text and a way to publish, all in one session. Claude Desktop asked for confirmation on each tool call by default, but the researchers note that many users switch to always allow.

About two weeks later, part of the same team published a paper with researchers from IBM, Google, Microsoft, ETH Zurich and others: Design Patterns for Securing LLM Agents against Prompt Injections. Its starting point is blunt. With current models, the authors doubt that general-purpose agents can offer reliable safety guarantees. So they build narrower agents. One rule sits behind all six patterns: once an agent has read untrusted input, that input must not be able to trigger an action with real consequences.

The six patterns, in short:

  • Action selector. The model only picks from a fixed list of actions and never sees their results.
  • Plan then execute. The model fixes its list of tool calls before it reads any untrusted data. Injected text can change what a step contains, not add new steps.
  • Map-reduce. Each untrusted document goes to its own isolated model, which can only return a narrow answer such as yes or no.
  • Dual LLM. A privileged model plans and calls tools but never reads untrusted data. A quarantined model reads it but has no tools, and its results come back as references, not as text.
  • Code then execute. The model writes a small program first, and the program then runs on the data.
  • Context minimization. The user’s prompt is removed from the context once it has been turned into an action.

In our reading, plan then execute alone would have stopped the GitHub leak. A request to look at open issues never plans a read of a private repository or a new pull request. Invariant’s own fix points the same way: a policy that allows one repository per session.

What to check in your own agent:

  1. For each session, write down what private data it can read, what untrusted text it reads, and where it can send things. If all three meet in one session, you have the GitHub setup.
  2. Fix the plan before the agent reads outside content, and refuse tool calls that are not in it.
  3. Keep the model that reads untrusted text away from tools.
  4. Do not count on approval prompts. People click always allow.

The limits are real. Every pattern trades some usefulness for safety, and the paper says no single pattern fits every case. It argues through ten case studies, not through measured attacks.

Several small open-source tools already automate the first check. We are testing them on real MCP servers and will publish what they catch and what they miss.

Sources: the Invariant Labs disclosure of 26 May 2025, and Beurer-Kellner et al., arXiv, June 2025, CC BY 4.0. This note is our own summary of both.