Part of our security series — the full argument lives at Secure AI Code Execution.
A chart has been going round this month. Three layers of the AI agent stack — orchestration, tools and MCP, model serving — and eight CVEs stacked up against them, most of them scored above nine. The headline on it reads your AI agent's biggest risk isn't the model.
It is correct, and it is the most useful thing anyone has published about agent security this year.
The obvious move for a vendor is to quote-tweet that chart and say this is why we built Magic. I would rather do the boring version: take all eight, one at a time, and check what each one would actually do against my runtime. Some of them I can dismiss in a sentence, and I will say so rather than take credit for it. One of them I could not clear, and it is in the table below with everything else.
The scorecard
| CVE | Component | The attack | Magic | Why |
|---|---|---|---|---|
| CVE-2025-68664 | langchain-core · 9.3 | One crafted message exfiltrates API keys and cloud secrets | ✅ Unrepresentable | data.*, every io.file.*, and config.get are absent from the vocabulary. There is no verb to reach a secret with |
| CVE-2026-33017 | Langflow · 9.3 | Unauthenticated remote code execution, actively exploited | ✅ Blocked | The three code-execution primitives each carry auth.ticket.verify:root, and MCP refuses anonymous callers outright |
| CVE-2026-34070 | LangChain · 7.5 | A rigged prompt file reads private files off the machine | ✅ Unrepresentable | Same absence. An injected read this file instruction has no slot to dispatch to |
| CVE-2025-6514 | mcp-remote · 9.6 | Connecting to a rogue tool server hands over the machine | ⬜ Not applicable | Wrong side of the wire. This is a client flaw; Magic is the server |
| CVE-2025-53967 | Figma MCP · 7.5 | A tool argument becomes a shell command | ✅ Blocked | system.execute is not in the vocabulary, the terminal tool is root-gated, and non-root callers never see it in the catalogue |
| CVE-2025-49596 | MCP Inspector · 9.4 | A malicious website hijacks the tool endpoint | 🟡 Partial | Auth closes the vector it exploited. The browser-origin edge I have not verified — see below |
| CVE-2025-66448 | vLLM · 7.1 | A poisoned model executes attacker code on load | ⬜ Not applicable | Magic serves no models |
| CVE-2025-62164 | vLLM · 8.8 | Anyone who can call the API crashes or owns the server | ⬜ Not applicable | Same |
Four blocked or unrepresentable. Three that never applied. One partial.
Start with the three I get no credit for
Magic is not written in Python and does not embed an AI orchestration framework. I went through every NuGet reference across all 76 project files in the repository this week: there is no LangChain, no LlamaIndex, no vLLM, nothing from that dependency graph. Inference is delegated to hosted APIs, so there is no model runtime here to poison and no weights to deserialize.
That is not a security achievement. It is an architectural accident of having written the thing in C# before any of these libraries existed. Three of the eight rows above are free, and a vendor who counts them as wins is padding the scorecard.
The mcp-remote one deserves its own sentence, because it is the highest-scored CVE on the chart at 9.6 and the most misread. It compromises the client — your Claude, your Cursor, your Codex — by way of a malicious server. Nothing I ship can fix that for you. If you connect your harness to a hostile MCP server, my runtime is not in the blast radius and cannot help.
So the honest count is five rows that actually test this architecture. Four hold.
Unrepresentable is a different claim from patched
Here is the distinction the whole article rests on, and it is worth being precise about because it is easy to oversell.
LangChain fixed CVE-2026-34070 by hardening a file-read path. That path still exists — it is a capability the framework has, now guarded better. The fix is a check, and checks are things that can be bypassed, regressed, or forgotten in the next refactor.
Magic did not fix that bug, because there is nothing here to fix. Generated code that runs on behalf of a non-root caller executes inside [whitelist], with a vocabulary of exactly 178 named slots. Every io.file.* slot is absent from that list. So the sentence read /etc/passwd and post it somewhere does not get filtered, or refused, or sanitised. It fails at dispatch, because the runtime looks up the slot name before signalling it and does not find one:
var whitelist = signaler.Peek>("whitelist");
foreach (var idx in GetNodes(input))
{
if (whitelist != null && !whitelist.Any(x => /* name and value must match */))
throw new HyperlambdaException($"Slot [{idx.Name}] doesn't exist in current scope");
await signaler.SignalAsync(idx.Name, idx);
}
The gate is the road. There is no second path to dispatch, and nesting does not help — add evaluates its own children through the same loop with the same vocabulary on the stack, so a forbidden slot hidden three levels down under an allowed one gets checked on the way past. I took that mechanism apart properly in The Only Sandbox Your AI Agent Cannot Break Out Of.
Which makes the first row on the chart — the crafted message that steals your API keys — the interesting one. It is the same shape as prompt injection everywhere: attacker text arrives in a context, and the agent acts on it. My runtime does not stop the injection. An attacker can absolutely get instructions into a context here, through a scraped page or a crawled site or a support ticket. What they cannot do is turn those instructions into a secret, because the code that would run has no database verb, no file verb, and no configuration verb in its vocabulary. The injection succeeds and reaches for a capability that is not there.
There is a second, duller reason that row holds. When you import an API into a cloudlet, the credential you nominate is never written into the generated endpoint — it is read from configuration at the moment of invocation. A prompt-injected agent driving that endpoint gets the result of an authenticated call. It never gets the key. That is covered properly in Convert Your OpenAPI Specification to a Secured MCP Tool.
The Figma row is the one to think about
CVE-2025-53967 is the most instructive CVE on the chart, because Magic ships the vulnerable shape deliberately.
There is a tool on this platform called execute-terminal-command. It takes a command and a list of arguments and passes them to the shell. Described in the abstract it is a command-injection vulnerability with documentation.
What makes it a feature rather than a CVE is three things, none of which is a filter on the input:
system.executeis not in the sandbox vocabulary, so no generated code reaches it- the tool itself opens with
auth.ticket.verify:root - the MCP catalogue assembles per caller from the roles on the ticket, and the
workflow_*tools are appended only for root — and the identical role check runs again when the tool is called, so hiding it is not the security, it is only the ergonomics
The Figma bug was a tool that any connected client could call, reaching a shell. The difference is not that my shell tool is safer. It is that the authority to reach it is checked twice, at discovery and at invocation, against an identity rather than against a prompt.
The general form of this is the thing I keep repeating and will keep repeating: calling a tool over MCP here runs the actual endpoint, carrying the actual caller's identity, through the actual authorisation the endpoint declares. There is no separate agent-facing implementation to drift out of sync with the browser-facing one. A gym found out what the absence of that costs in this story.
The one I could not clear
CVE-2025-49596 is MCP Inspector: a developer opens a malicious website, and the site reaches into the debugging tool listening on their own machine. It scored 9.4 because Inspector shipped with no authentication at all, so a browser request from any origin was as good as a legitimate one.
Magic's MCP endpoint is not in that position. It refuses unauthenticated requests outright, with a 401 that points OAuth clients at this cloudlet's own metadata so they can discover the sign-in flow. The root cause of that CVE does not exist here.
But the class has a second half, and it is the half I cannot sign off on today. Attacks in this family — DNS rebinding, cross-origin requests from a page the developer happened to open — turn on how the server treats browser-originated requests, which means the CORS and origin handling on that route, not just the presence of auth. I have not audited that specific path against a rebinding scenario. Login throttling and same-site credential handling landed recently and CORS hands credentials only to same-site or explicitly configured origins, which is most of the way there. Most of the way is not the same as verified.
So that row stays yellow until I have actually tested it. It is on my list for this week, and if it turns out I was wrong about something I will say so in a follow-up rather than quietly edit this page.
The honest edges
A security claim with no caveats is marketing. These are the same three I always list, because they have not changed:
Root is not sandboxed, by design. Every green row above describes a non-root caller. If the ticket holds root, generated Hyperlambda goes straight to invoke with the whole runtime and no timeout, because an administrator who cannot administer is a broken account. The consequence is blunt: connect an agent to a cloudlet using root credentials and you have opted out of this entire article. Give your agents their own user and their own role. The vocabulary protects you from your agent, not from yourself.
Egress is a policy decision you own.http.get is in the vocabulary. Sandboxed code cannot read your files or your database, but it can make an outbound GET, and a determined payload can encode things into a URL. If that matters to you, remove it — it is one line in a list.
The count above is five, not eight. I said it at the top and I will say it again at the bottom, because scorecards like this get screenshotted without the paragraph underneath: three of those eight rows are wins I did not earn.
None of this is theoretical, and I would rather you did not take my word for it. In April I pointed Claude Code at the entire codebase and told it to break in; it found four real hardening issues, all fixed, and zero sandbox escapes. There is a standing $100 bounty for a verified escape and nobody has collected it.
Everything here is MIT licensed — the runtime, the vocabulary file, the check quoted above. Read it at github.com/polterguy/magic, or run the platform locally in one command:
curl -fsSL https://hyperlambda.dev/docker-compose.yaml | docker compose -f - up
Open localhost:5555, point it at localhost:4444, log in with root / root — and then, before you connect an agent to anything that matters, make it a user that is not root. If you would rather someone else kept it patched, that is what a managed cloudlet is.
The chart is right. The model is not your biggest risk. Your biggest risk is what the layer underneath the model is permitted to do when something upstream lies to it — and that is a property of a runtime, not of a prompt.
FAQ
Does Magic prevent prompt injection?
No, and neither does anything else. Injection is a property of putting untrusted text in front of a language model. What Magic changes is the consequence: a successful injection can only reach capabilities the executing context actually holds, which for a non-root caller is a 178-slot vocabulary with no filesystem, no database, and no shell. The attack lands and finds nothing to hold on to.
Is Magic affected by the LangChain and Langflow CVEs?
Not directly — those libraries are not dependencies of this codebase, and no NuGet reference in the repository comes from that ecosystem. The bug classes are still worth taking seriously, which is why they are in the table above with an explanation of the mechanism rather than a dismissal.
If the whitelist is so strong, why is root exempt?
Because root is the cloudlet's administrator. The sandbox exists to constrain code generated on behalf of a caller, and an administrator's authority is the thing the sandbox is derived from, not something it can outrank. The practical instruction is the one in this article twice already: do not give agents root.
Can I restrict a slot to a single resource?
Yes. A vocabulary entry can carry a value, and if it does, the value is part of the match. data.connect grants database access; data.connect:crm grants exactly one database by name, and every other connection string becomes a thrown exception. The unit of authority stops being the capability and becomes the resource.
Related reading
- Secure AI Code Execution — the full argument
- The Only Sandbox Your AI Agent Cannot Break Out Of
- Why Secure AI Code Execution Requires Runtime Whitelisting, Not Prompt Filtering
- Zero-Hallucination Code Generation: A Vocabulary Your AI Cannot Escape
- Agentic AI Without Permission Boundaries Is Just Malware With UX
- The AI Didn't Hack the Gym. The Gym Left the Cancel Endpoint Wide Open.
- Convert Your OpenAPI Specification to a Secured MCP Tool in Seconds
- Break My AI Sandbox and Make $100
- For AI Agent Builders