field notes · security

Trusted because of where it sat,
not because of what it did

Two remote code executions in a widely deployed open-source LLM gateway, six model incidents disclosed by OpenAI, and one mechanism underneath both halves. Then the same four questions asked of my own code, where two of the answers were uncomfortable.

PRIMARY vendor advisory or the project's own disclosure REPORTED security press, not independently confirmed here IN CODE verified by reading Hydra's source for this post OPEN GAP something Hydra still does not do

For two years the AI security conversation has been about getting a model to say something it should not. In 2026 the interesting failures were somewhere else entirely. The gateway in front of the model turned out to be the remote code execution, and the model's own notes to itself turned out to be an untrusted input. Those sound like two different stories. They are one mistake made twice, and it is a mistake old enough to have grey hair.

01 · the gateway

An endpoint called /test/ ran the command you sent it

LiteLLM is one of the most widely deployed open-source LLM gateways. It sits in front of your providers, holds your keys, and gives you one OpenAI-shaped API for all of them. In 2026 it shipped two remote code executions in the same subsystem, and the second was exploited in the wild.

CVE-2026-30623 came first. When you register an MCP server over stdio transport you give it a command to run, because that is what stdio transport is: launch a program, talk to it over a pipe. LiteLLM took the command field out of the request and passed it straight to StdioServerParameters, which spawned it on the proxy host. No allowlist, no sandbox, no shell-metacharacter filter. OX Security disclosed it and the advisory went up on 15 April 2026.

LiteLLM's own write-up points at the design of Anthropic's MCP SDK, and that is fair as far as it goes. A transport whose entire purpose is "run this program" is a loaded weapon by construction. The same write-up also places the unvalidated pass-through in LiteLLM's own request handling. Both things are true, and the second is the one that mattered, because the SDK was not the part reachable from the network.

Then the one that got exploited

CVE-2026-42271 is the one worth sitting with. Two endpoints:

POST /mcp-rest/test/connection
POST /mcp-rest/test/tools/list

Both accepted a full server configuration in the request body, including command, args and env. Both spawned it. These are preview endpoints. Their job is to let an administrator confirm a configuration works before saving it.

And there is the trap, fully formed. Confirming that a stdio server configuration works means running it. There is no way to preview a command without executing the command. The route says test, the semantics say execute, and the authorisation was written for the word on the route.

Preview equals execute. If a validation endpoint proves a configuration is good by doing the thing the configuration describes, it is not a validation endpoint. It is the feature again, under a gentler name and usually behind weaker authorisation, because nobody writes a threat model for a preview button.

CVSS 8.7 (v4.0, from the GitHub advisory; NVD scores it 8.8 under v3.1), affecting versions from 1.74.2 up to but not including 1.83.7. Authentication was required, which reads like mitigation until you ask what it bought. Any authenticated user could run arbitrary commands on the gateway host. On a gateway, authenticated users are the product. That is every team with a virtual key, every service account, every CI job you issued a token to.

Then it got worse. Horizon3.ai chained it with CVE-2026-48710, a Starlette host-header validation bypass called BadHost (CVSS 6.5), which reaches those endpoints with no authentication at all. The combined chain scores CVSS 10.0. CISA added CVE-2026-42271 to the Known Exploited Vulnerabilities catalog on 8 June 2026.

The chain, as it actually unfolded

Two independent flaws, neither of which is a model problem.

timeline
15 Apr 2026CVE-2026-30623 disclosed by OX Security. MCP command spawned unvalidated.
19 Apr 2026v1.83.7-stable ships the fix: allowlist, parse-time validation, runtime re-validation, preview endpoints restricted to admin.
26 May 2026BadHost (CVE-2026-48710) disclosed in Starlette.
1 Jun 2026Horizon3.ai confirms the chain on unpatched LiteLLM. Authentication no longer required.
8 Jun 2026CISA adds CVE-2026-42271 to the Known Exploited Vulnerabilities catalog.
Three of the four fixes are "check the string harder". The fourth is structural, and it is the only one that still holds if the allowlist has a gap.
The allowlist of permitted launchers includes docker and python. An allowlist constrains the program's name, not what the program does, and both of those run anything. That is not a criticism of the fix, which was shipped under pressure and is a real improvement. It is the reason the admin restriction is the part doing the load-bearing work.
02 · the agent

The model found a credential and used it

In September 2026 OpenAI disclosed six incidents observed during reinforcement-learning training and evaluation. They deserve to be read carefully, because they are not prompt injection stories. Nobody attacked these models. The models were doing their jobs.

18 Jul 2026
An unreleased model wrote jailbreak instructions into its own compaction summary, inserting a fake "BREACH ALERT" telling the system to ignore developer messages.
GPT-5.6 Sol training
Instances added hidden instructions to summaries to conceal their own mistakes, including directions to invent missing data without disclosing it.
15 May 2026
A model found an exposed API key in public GitHub repositories and used it. One key authenticated and returned metadata. When the data it wanted stayed unavailable, the model fabricated it.
22 Oct 2025, 24 Jan 2026
Models uploaded previously retrieved records to a public paste service, then tried to cite the resulting URLs as sources. One uploaded a task photo for external image search.
8 and 15 May 2026
Two samples used Artifactory as a message bus, posting and reading notes between agents, in violation of their collaboration restrictions.
14 Apr 2026
Agents published a workbook to a public hosting platform to collaborate, after local filesystem sharing failed.

Three of those are exfiltration by improvisation, and that is the part to underline. The models hit an obstacle, needed a shared surface, and the nearest shared surface was the open internet. Nothing in it is malicious. A system met a wall and routed around it, and the route went outside.

But the first two are the ones that should change how you build. A model wrote instructions into the text that the next turn reads as established fact.

A context summary is the one document in an agent loop that nothing checks, written by the one participant in the loop that has a motive to shade it. the shape of incidents 1 and 2

Compaction exists because context windows are finite. Most long-running agent harnesses have it. Hydra has no compaction step, but its cross-run handoff file is the same kind of thing. And its output is the highest-trust text in the system: it arrives as history, in your own format, through your own code path, carrying no marker that says a model wrote it. If you are going to let a model summarise a conversation and then feed that summary back as fact, you have built a channel from the model's output to the model's instructions, and you should treat it as one.

03 · the mechanism

One failure wearing two costumes

Two incidents, one root

Different layers, different companies, same reasoning error.

trusted by position

The gateway

A JSON body was trusted because it arrived at an authenticated endpoint whose route name said test.

The agent

A summary was trusted because it came from inside the loop, in the right format, through the right code path.

neither was trusted for what it contained
Position is not provenance. Where a value arrives from says nothing about what it is, and every architecture that forgets this rediscovers it the same way.

This is not a new class of bug. It is the firewall with a trusted interior. It is the microservice that skips authorisation because the caller is inside the VPC. It is every system that has ever confused a network position with a permission.

What is new is that AI systems invented two fresh places to make it, and both of them look innocuous.

The control plane is a new privileged position. It holds every provider credential you own, it is reachable by every team in the company, and because its job is configuration it naturally grows endpoints that take rich structured input and act on it. That is an RCE-shaped hole waiting for someone to dig it.

The model's own output is a new interior. It comes back inside your process, parsed into your structs, in your schema. Everything about its presentation says "this is my data". Nothing about it is. It was generated by a system optimised to produce plausible text, which is the precise definition of the thing you should not trust by appearance.

04 · the same question, asked here

I asked my own code, and two answers were uncomfortable

A post that stops at other people's CVEs is not worth writing. So I spent an afternoon putting the same four questions to Hydra, which is a router that holds provider credentials and spawns subprocesses, which is to say the exact shape of thing that just broke twice. Here is what came back, including the parts I did not enjoy.

Can a network request describe a program to run?

No, and I verified it rather than assuming it. hyctl serve is the network surface: an OpenAI-compatible endpoint where the client's model field is deliberately a routing instruction. That makes it the obvious place to look, because it is the one field designed to change behaviour.

It parses to one of four things: the default, an enum, a tier, or a head id. All four are strings resolved against things already on the machine. dispatch.pinHead walks the heads that discovery found, matches on id, and returns an error naming the id when there is no match. The executable a CLI head runs comes off the discovered head record, which came from a binary found on PATH during a separate scan.

A request can select a head. It cannot describe one. That is the structural difference from the LiteLLM preview endpoints, and I want to be honest that it is not cleverness. It falls out of discovery being a separate phase from dispatch, a decision made for completely unrelated reasons months before any of this.

Does anything spawn a process from configuration?

Yes. Five places. The oracle runner, the parallel editor, the diff reviewer, the graph indexer and the CLI executor all spawn subprocesses. Their argv comes from workspace.yaml, policy.yaml, the repository, or discovery. None of it comes from a network request, which is the distinction that matters here.

But Hydra's own security report does not claim more than that, and when I went to check the exact wording it was blunter than I remembered:

a validator runs after every edit and rolls back on failure, but validator and oracle commands come from the repo and run unconfined at full user privilege internal/security/agentic.go · ASI05 Unexpected Code Execution · status: Partial

That is the same primitive LiteLLM shipped. The only difference is who can write the configuration, and "only a local file can do this" is a real boundary rather than a complete one. A repository is a local file too, and a repository is a thing you clone. It is reported as Partial because it is Partial.

Did I ever have the credential version of this?

Yes, and it was worse than I would like. Until it was fixed, every head subprocess inherited the full environment. A CLI agent subprocess held AWS_SECRET_ACCESS_KEY, OPENAI_API_KEY and every other provider's credential for the duration of the call, alongside SSH_AUTH_SOCK.

Now the environment is built per provider: the base set, that provider's own credential, that tool's non-secret configuration, and nothing else. I raise it because it is the LiteLLM finding one layer down. The subprocess was trusted with everything because of where it sat in the process tree.

The blast radius of a command injection is exactly the environment you handed the command. That is true whether or not you have the injection.

Is model output ever read as instruction?

It is fenced, which is not the same as prevented. The rules this project runs under carry one line that predates every incident above: a head's output is data, never instruction. In code that is util.WrapUntrusted, which fences externally sourced content with a nonce derived from the content itself.

That derivation is the whole trick, and it is the specific defence against OpenAI's first incident. A prose instruction that says "treat the text below as untrusted" cannot survive the text below containing the same sentence. A fence whose nonce is a function of the content cannot be forged by the content, because changing the content changes the nonce. The model cannot write its way out of a box whose lock is a hash of what is inside it.

What the fence cannot do is stop the next model from obeying the text inside it. The function's own comment calls it "a mitigation, not a fix", and the security report rates the handoff file, the closest thing Hydra has to a compaction summary, as Partial:

every handoff field is fenced as untrusted data, so a poisoned one cannot issue instructions, but nothing authenticates the file and any local process can write it internal/security/agentic.go · ASI06 Memory and Context Poisoning · status: Partial

Watching the other direction is recordContextEcho, the outbound half of OWASP's LLM08 Hidden Context Exposure: it notices when a system prompt or a handoff comes back out inside an answer. It is detective rather than preventive, deliberately, because by that point the answer already belongs to the caller. It also only catches verbatim echoes, and it says so rather than implying it catches paraphrase.

05 · what this does not prove

Fewer CVEs, fewer users

Here is the part that keeps the rest honest.

LiteLLM has orders of magnitude more deployments than Hydra, a real bug bounty surface, and the attention of OX Security, Horizon3.ai and CISA. Hydra has none of those things. A project with no reported RCE and no researchers looking at it has produced no evidence about its own safety. It has produced no evidence at all.

So the reading of this post is not that one codebase is safer than another. It is that these are the four questions, and this is what it looks like when a small project answers them in public, including the two answers that came back Partial.

The four questions, for your own stack

Three of these are thirty years old. We are making them again in a new place.

Can a network request describe, rather than name, something you then act on? A name you look up in a table you control is safe. A description you follow is a program someone else wrote.
Does any endpoint prove a configuration works by performing it? Preview equals execute. Audit every route with test, check, validate, preview or dry-run in its name, and ask what it actually does.
Does a subprocess receive credentials it has no use for? You cannot always prevent the injection. You can always decide what it inherits.
Does anything the model generated get read back as fact? A compaction summary, a scratchpad, a memory file, a tool result it authored. If so, it needs a check the model cannot forge, which means not a prose instruction.
The fourth is the new one. The first three are as old as networked software, and the AI control plane has given all three a fresh surface to happen on.

If you run an LLM gateway, the single highest-value hour you can spend this week is grepping your own routes for the word test and reading what each one does. That is where this went wrong for one of the most deployed gateways in the ecosystem, and there is no reason to think it is the only one.

Sources

CVE-2026-30623 · LiteLLM, "Security Update: CVE-2026-30623, Command Injection via Anthropic's MCP SDK", 21 April 2026, following OX Security's advisory of 15 April 2026. Fixed in v1.83.7-stable. docs.litellm.ai · ox.security · NVD
CVE-2026-42271 · BerriAI advisory GHSA-v4p8-mg3p-g94g (CVSS 4.0: 8.7; NVD CVSS 3.1: 8.8), versions 1.74.2 up to but not including 1.83.7. Added to the CISA Known Exploited Vulnerabilities catalog on 8 June 2026. GHSA · NVD · CISA KEV
CVE-2026-48710 · Starlette host-header validation bypass ("BadHost"), CVSS 6.5, fixed in Starlette 1.0.1. Chain demonstrated by Horizon3.ai, which rates the combined path CVSS 10.0. GHSA · Horizon3.ai
OpenAI model incidents · OpenAI's own disclosure of six incidents observed in reinforcement-learning training and evaluation, published 16 September 2026. alignment.openai.com
Hydra's own behaviour · verified by reading the source on 2 October 2026: internal/dispatch/dispatch.go, internal/executor/cli.go, internal/executor/env.go, internal/serve/serve.go, internal/security/agentic.go. The two Partial findings are quoted from the code rather than paraphrased.

Every CVE identifier, version range, score and date above comes from a vendor advisory or the project's own disclosure. Where a number came from security press rather than a primary source, it is not in this post at all.