Back to all posts
Published on · by Renaud Deraison

The gateway accepted any token

On September 2, CISA added seven exploited flaws to the KEV catalog. Three are AI infrastructure, the first batch where AI makes up nearly half. Read the LiteLLM entry: its MCP endpoint admitted an unauthenticated request carrying any Bearer token at all, because a failed key check fell through to an empty auth object, and the caller could then list and invoke every tool the gateway was wired to. Bromure Agentic Coding holds the same authority behind a hypervisor and a vsock, with no listening port, no header to forge and no fallback branch.

You built the gateway so that no single laptop would hold every key. It worked, and it held every key. Then its front door accepted a Bearer token somebody invented.

On September 2, CISA added seven vulnerabilities to the Known Exploited Vulnerabilities catalog. Two SonicWall SMA1000 bugs, a Sangoma Switchvox SQL injection, a JFrog Artifactory authentication bypass, an OS command injection in Kestra, an HTTP smuggling flaw in Starlette, and an improper-authentication flaw in BerriAI's LiteLLM.

Three of those seven are AI and ML infrastructure. Forkast called it the first KEV batch where AI components make up nearly half the additions, the kind of milestone that earns a headline and then gets filed. Look at which AI infrastructure it is and the batch deserves more than filing. All three of them sit in the middle: the components you deploy so that your agents and the resources they touch stop talking to each other directly. Start with the LiteLLM entry.

The fallback branch

LiteLLM is an AI gateway: one proxy that sits between your agents and the model providers, holds the provider keys so nobody else has to, enforces budgets and rate limits, writes the logs, and now fans out to Model Context Protocol servers so that a team's agents share one MCP endpoint instead of each developer wiring up their own.

CVE-2026-59822, published on July 8 and carrying a CVSS of 8.8 in GitHub's advisory, lives on that MCP endpoint. One sentence of the advisory carries the whole story:

the fallback path could replace failed LiteLLM key validation with an empty UserAPIKeyAuth() object.

The MCP Streamable HTTP endpoint supported OAuth2 passthrough, so that a token meant for an upstream MCP server could be carried through the gateway rather than validated against LiteLLM's own key store. That is a reasonable feature, and it needs a branch: try the LiteLLM key first, and take the passthrough path when the key is not what arrived.

You reach that branch by failing. Send an Authorization header containing any string at all, key validation rejects it because the string is not a key, and the handler builds an empty UserAPIKeyAuth() and carries on with it. Your request now holds an authenticated MCP session belonging to nobody. From there you enumerate every MCP tool the gateway was configured with and call them: internal applications, databases, cloud consoles, development systems, whatever the team had connected. The flaw affects every version before 1.84.0. CISA's remediation deadline for federal agencies is September 16, and LiteLLM's advice for anyone who cannot upgrade today is to block /mcp/ at the reverse proxy in front of it.

CVE-2026-59822 — the branch that failure reachesunauthenticated requestPOST /mcp/Authorization: Bearer anything-at-allvalidate LiteLLM keythe string is not a keyvalidation failsOAuth2 passthrough fallbacksubstitutes an empty auth object and continuesUserAPIKeyAuth()authenticated MCP session, belonging to nobodylist every configured tool · invoke any of them · no LiteLLM key was ever presentedinternal applicationsbehind the gatewaydatabaseswhatever was wired upcloud serviceswith the gateway's reachdevelopment systemssource, CI, tickets
The MCP handler tried the LiteLLM key first and, when that failed, took the OAuth2 passthrough path, which built an empty UserAPIKeyAuth() and continued. A request carrying an invented Bearer token arrived where a valid key would have: an authenticated MCP session with the gateway's full tool list behind it.

The other three broke in the same function

Read the rest of the batch and you get something more useful than the AI-versus-not headline.

Starlette, CVE-2026-48710, nicknamed BadHost. Starlette is the ASGI toolkit under FastAPI, which a large share of Python AI services, LiteLLM included, are built on. A crafted Host header injects a path into the host portion of the request, which shifts how the URL is reconstructed, which means path-based authentication middleware evaluates one route while the application serves another. CISA's entry states the result: authentication bypass, when the authentication depends on the reconstructed URL's path. It carries a CVSS of 6.5, the lowest score in the batch and the one that says least about the consequence. LiteLLM shipped its own companion fix for the same shape in 1.84.0, describing it as "a crafted Host header could make the proxy's auth gate evaluate a different route from the one it served", and told operators whose proxy had been exposed to rotate their keys and audit the management logs.

JFrog Artifactory, CVE-2026-82329, CVSS 9.8. Improper authentication under the default configuration: a "phantom" join key that lets an unauthenticated attacker forge administrator tokens. watchTowr documented exploitation in the wild on September 1, four days after disclosure, with attackers minting admin credentials and enumerating users. This is the artifact repository, the host that answers npm install and pip install for the whole organization.

Kestra, CVE-2026-49869. OS command injection reachable by an unauthenticated remote attacker, who can create and execute arbitrary workflows with no credentials at all. The orchestrator does what an orchestrator does, on behalf of somebody who never signed in.

Four products, four different codebases, and the same broken function in each: the one that decides whether this request is allowed to be here. An empty auth object. A reconstructed path. A default join key. In Kestra's case, no check at all on the route that runs things.

What the attackers did next was boring, and the boredom is the tell for volume work. The Hacker News collected the observed behavior: reverse shells, XMRig miners, credential enumeration, and the theft of API keys and LLM provider credentials. Microsoft summarized it:

The observed objectives were consistent. Across the cases, telemetry showed credential collection, durable access mechanisms, and resource monetization, even though the execution path differed by product.

One batch, one function, four ways to get it wrongCOMPONENTTHE CHECK THAT DECIDEDWHAT IT LET THROUGHLiteLLM · AI gatewayCVE-2026-59822 · 8.8validate the LiteLLM API keyon the MCP endpointany Bearer token at allfailure fell through to an empty auth objectStarlette · ASGI toolkitCVE-2026-48710 · 6.5path-based auth middlewareon the reconstructed URLa route it never guardeda crafted Host header shifted the reconstructionJFrog Artifactory · registryCVE-2026-82329 · 9.8join-key validationunder the default configurationforged administrator tokensexploited in the wild four days after disclosureKestra · orchestratorCVE-2026-49869none, on the route that runs thingsworkflow create and executearbitrary commands, unauthenticatedno credentials required
Four entries from one KEV batch, four codebases, one function. Each product broke at the point where it decides whether a request belongs, and three of the four are components you deploy in order to centralize access instead of letting every developer hold their own.

The gateway was the right idea, and this is its bill

The AI gateway is one of the better ideas of the last two years, and most teams arrived at it for sound reasons.

Before the gateway, every developer's coding agent held a provider key in an environment variable, every developer wired up their own MCP servers with their own tokens, and nobody could answer what any of it had spent or touched. Putting a gateway in the middle fixes all of that at once. One place holds the provider keys and the MCP configuration, and writes the log. Rotating a key becomes a single operation, and so does cutting off a departing employee. The industry converged on this for good reason.

The bill is that you have built a network service whose whole purpose is holding the authority for everyone, and it decides who you are with a function. Functions have branches, and one branch handles the case where the first thing you tried did not work.

LiteLLM did not invent that shape. Every gateway has a front door, every front door has an authentication function, and an authentication function that supports two credential schemes has a path it takes when the first one fails. The reach sitting behind that door is deliberate: concentrating it is the product. So when a gateway gets into trouble, everything it holds is already in the room.

The usual conclusion drawn from an incident like this is "go back to per-developer keys," which is worse on every measure you can take. Keep centralizing. The question worth arguing about is where the centralized thing sits, and whether it has to answer requests from strangers in order to do its job.

Where Bromure puts the same job

Bromure Agentic Coding runs each coding agent in a hardware-virtualized Linux VM on your own Mac, and every security control sits on the host side of that boundary. The controls are the same list a gateway offers: hold the credentials, mediate the MCP servers, classify what the agent does with your infrastructure, keep the record. Nothing on any network can reach the component enforcing them.

There is no front door. The guest's HTTPS never leaves over the network. It tunnels over a virtio socket, vsock port 8443, to the host proxy, and the manual is blunt about the topology: the VM has no other route to the network. The proxy holds no listening port that your LAN, your corporate network or the internet can address. It never asks who is calling, because one thing sits on the other end of that socket and the hypervisor decides what that thing is. No Authorization header, no key store, no scheme negotiation, and so no branch to take when the first scheme fails.

The MCP bearer token is a fake before it ever reaches the agent. For HTTP-transport MCP servers configured in a workspace, the real bearer token stays encrypted on the Mac. The agent's config gets a brm-mcp_… placeholder. The settings pane labels that field Never sent to VM — swapped by proxy, and the host proxy substitutes the real value on the wire, scoped exact-or-subdomain to that server's host and no other. The proxy also answers the server's OAuth and OIDC discovery paths with 404, so Claude Code treats the server as pre-authenticated instead of attempting a browser flow the VM could never complete. LiteLLM's fallback stood on that same passthrough surface. Here the workspace holds no credential to fumble.

Every operation gets its own decision. A gateway settles the question at the door: pass it, and everything behind it is yours. Bromure's Guardrails classify the agent's calls inside the proxy, across Kubernetes, AWS, git forges, container registries and databases, and apply a per-service write policy. New workspaces default to Prompt before write, so reads flow and every mutation pauses for a host-side dialog showing the literal operation: the exact SQL, or METHOD /path. Read-only blocks every mutation. Ask before use gates the credential itself, with grants measured in minutes. A forged session still cannot kubectl delete its way through anything, because the second decision happens on your Mac and owes nothing to how the first one went.

Default deny answers reachability, in both directions. The workspace's egress firewall is an ordered rule table with a default for unmatched traffic. Set it to Deny and the VM reaches the hosts you listed and nothing else, on any protocol. Two components outside the guest enforce that: the virtual switch matches every flow by destination IP and DNS-snooped hostname, and the proxy matches again by TLS server name. Edits reach running sessions with no reboot. In the other direction, NAT mode keeps the VMs off your physical LAN, and nothing outside opens a connection to them unless you publish a service on purpose. The scanners that swept up those exposed gateways get no answer from a Mac.

Whatever a compromised gateway sends back is untrusted input. This part outlives the CVE. Once a gateway belongs to somebody else, model output, tool results and error strings all arrive from an attacker, on the highest-privilege wire in the stack: the one the agent is built to act on. Bromure's source-code detector scores tool_result spans in the agent's outbound AI traffic with a local PromptGuard model, on-device, before the model acts on them, and can log, ask or block; a block returns HTTP 451 and the model never sees the content.

The exfiltration leg fails closed. Every credential in a workspace is a deterministic fake with one legitimate destination. The proxy scans every outbound request for a fake headed somewhere it was not minted for; when it finds one it refuses the request without forwarding a byte, pauses the VM and raises an alert, logged as a red Credential brokering row in the Security Timeline. Because only the fake was ever in reach, the real credential does not need rotating.

authority in the middleone service, reachable by anything that can route to itagent · laptop Aagent · laptop Ba strangerwith a made-up tokenAI gatewaylistening on :443auth function reads a headerholds every provider keyholds every MCP wiringone branch decides everythingmodel providers · databases · cloud · internal appsreached with the gateway's authority, not the caller'swhat an attacker needsa route to the port, and one bad branch in the checkauthority at the edgeone Mac, one hypervisor, nothing listeningworkspace VM · the agent runs herebrm-mcp_… · sk-ant-api03-brm-… · ghp_…placeholders only — no real secret is in the guestvsock 8443 — the only route outhost proxy · on your Macno listening port · no header to forge · no fallback branchswaps the real credential in, scoped to one hostegress firewall · write policy · injection scanone trace row per request, on your machinewhat an attacker needsa route that does not exist, to a port that is not open
Left: authority in the middle. A network service holds every key and every MCP wiring, and one authentication function stands between it and anyone who can route to it. Right: authority at the edge. The same controls run on your Mac behind a hypervisor, reached over a virtio socket with no listening port. The VM holds only fakes, and the proxy rules on each operation after the request has left the guest.

If you run a gateway today

Upgrade LiteLLM to 1.84.0 or later; if you cannot, block /mcp/ at the reverse proxy in front of it. Then follow LiteLLM's own advice from the Host-header fix and treat an exposed proxy as an exposed key store: rotate the provider keys it held, audit the management logs. Federal agencies have until September 16 on CVE-2026-59822, which is a reasonable date to borrow.

The rule that shrinks the question

In the workspace's Guardrails pane, set Unmatched traffic to Deny and list what the job needs: allow web api.github.com, allow web registry.npmjs.org, default deny. A workspace that is not allowed to reach an internal gateway cannot be used to walk through one, and saving pushes the policy to live sessions without a restart.

Hold the secret, do not check it

One line in Bromure's own architecture notes is worth rereading after a week like this one:

Bypassing the boundary gains an attacker nothing, because the boundary is not where secrets are checked — it is the only place secrets exist.

A gateway is a place where credentials get checked: a request arrives, a function inspects it, and that function's verdict is the only thing standing between the caller and the keys. Checks have edge cases and fallback paths. Checks earn a CVE, a KEV entry and a federal deadline, then earn another two months later on a different branch of the same function.

A wire boundary is a place where credentials are. Nothing arrives to be inspected, so no verdict can go wrong. The real token sits encrypted on a Mac, the workspace holds a brm- placeholder worth nothing anywhere, and the substitution happens on a socket only one hypervisor can write to.

We have written variations of this three times in six weeks: an egress proxy that trusted a hostname the agent could write, a command guard reading bash differently than bash does, and three coding agents whose bypasses all ended at a real credential in a process. Each time, the failing component was doing an honest job of evaluating something, and that is the constant. Evaluation is a weaker primitive than absence, and we keep handing evaluation the work that absence does for free.

Centralizing your agents' authority was the right call. Put the centralized thing where nobody can send it a request. Install Bromure Agentic Coding and give each agent a boundary with no front door.