Bifrost is an open-source AI gateway that sits between your application and your LLM providers. It handles routing, load balancing, and fallback logic for models from OpenAI, Anthropic, Google, and about a dozen others. It also speaks MCP, the Model Context Protocol, which means it can register and manage tool-providing clients as part of the AI agent stack. Thousands of teams use it. The official Docker image ships with management authentication disabled and the API bound to 0.0.0.0. One unauthenticated POST to /api/mcp/client gets you a shell.
CVE-2026-90898. CVSS 9.8. Discovered by Yuval Moravchick at JFrog Security Research. Patched in transports/v2.1.0. If you're running anything in the 2.0.x or 1.6.x lines, your MCP management API is accepting anonymous client registrations right now.
The Attack Surface
MCP supports multiple transport types. The one that matters here is stdio: a client type that launches a local subprocess and communicates with it over standard input/output. When you register a stdio MCP client through Bifrost's management API, the gateway spawns the specified command as its own process user.
The stock Bifrost binary binds the management API to localhost by default. Limited exposure. The official Docker image overrides this to 0.0.0.0, because containers need to accept connections from outside their network namespace. If you published the management port in your docker-compose.yml, which you probably did because the documentation shows you how, the API is now reachable from anywhere that can route to your host.
Management authentication is disabled by default. The setting is governance.auth_config.is_enabled, and it defaults to false. The attack requires exactly one HTTP request.
The Request
An attacker sends a POST to /api/mcp/client with a JSON body specifying a stdio-type connection:
{
"connection_type": "stdio",
"auth_type": "none",
"stdio_config": {
"command": "/bin/sh",
"args": ["-c", "id && cat /etc/passwd && env"],
"tools_to_execute": ["*"]
}
}
Bifrost spawns /bin/sh immediately. It doesn't wait for the MCP handshake to complete. The HTTP request eventually times out because the spawned process isn't speaking MCP protocol back, but the command has already executed. The shell ran as appuser, which is the default user in the Bifrost Docker image.
That env at the end of the command chain is the important part. In a running Bifrost instance, environment variables contain API keys for every connected LLM provider: OpenAI, Anthropic, Google, Cohere, whatever you've configured. One request, arbitrary command execution, and your entire provider key set exfiltrated.
Why This Keeps Happening
This is the third significant MCP-related vulnerability in the past month. On September 6, Bifrost itself caught a separate flaw (CVE-2026-86242, CVSS 8.1). On September 14, the broader MCP ecosystem was dealing with tool description injection attacks where malicious tool metadata could manipulate agent behavior. And that's just the named CVEs. The protocol's attack surface is expanding faster than the security model can keep up.
The fundamental problem: MCP has no client authentication story. The protocol defines tool providers and tool consumers, but the registration mechanism for new clients is left to the transport implementation. Bifrost's implementation was "accept the POST and spawn the process." No token, no certificate, no challenge. The fix in transports/v2.1.0 returns a 403 for unauthenticated stdio registration, which is the right answer, but it's a patch on a design gap.
If you're deploying MCP-enabled infrastructure in production, your security posture depends on every gateway, proxy, and transport layer having independently implemented client authentication correctly. There's no protocol-level guarantee. Every implementation is rolling its own auth story, and some of them are rolling "none."
Teams running persistent agent deployments with frameworks like those covered in the OpenClaw Automation Bible should audit every MCP endpoint in their stack. The YAML configs that define your agent topology are also your attack surface map. If a config specifies a stdio transport, the underlying gateway better require authentication before spawning anything.
Checking Your Exposure
If you're running Bifrost in Docker, check your compose file first:
# Vulnerable: management port published, auth disabled
services:
bifrost:
image: bifrost-ai/bifrost:2.0.0
ports:
- "8080:8080" # Management API exposed
environment:
- BIFROST_AUTH_ENABLED=false # Default
Test whether the management API is reachable:
$ curl -s http://your-bifrost-host:8080/health
# If this returns 200, the management API is accessible
Check the version:
$ curl -s http://your-bifrost-host:8080/api/version
# Anything below transports/v2.1.0 is vulnerable
If you can't upgrade immediately, enable authentication:
governance:
auth_config:
is_enabled: true
admin_username: "admin"
admin_password: "something-that-isnt-the-default"
And restrict the management listener to trusted networks. If you're behind a reverse proxy, don't expose port 8080 at all. The management API should never face the internet.
For Already-Compromised Instances
If your Bifrost instance was internet-facing with authentication disabled for any period, assume compromise. The remediation sequence:
- Rotate every provider API key. OpenAI, Anthropic, Google, Cohere, whatever's in your environment variables. All of them. Today.
- Rotate Bifrost virtual keys. These are the internal keys your applications use to authenticate to Bifrost. If an attacker had shell access, they have these too.
-
Audit container logs for unexpected POST requests to
/api/mcp/client. The request body will show the command that was executed. -
Check for persistence. The
appuseraccount in Docker has limited privileges, but depending on your container configuration, an attacker may have written to mounted volumes or established outbound connections.
# Search for suspicious MCP client registration attempts
$ docker logs bifrost 2>&1 | grep -i "mcp/client"
# Check for unexpected outbound connections during the exposure window
$ docker logs bifrost 2>&1 | grep -E "(curl|wget|nc |ncat)"
The Broader Pattern
The MCP ecosystem is moving fast. New gateways, new transport implementations, new client libraries, all shipping features faster than they're shipping security reviews. Bifrost is open-source, well-maintained, and had JFrog's security research team actively looking at it. They still shipped a CVSS 9.8 in their default Docker configuration.
The uncomfortable question for anyone running MCP in production: if a maintained, actively-audited gateway shipped with authentication disabled and the management API bound to all interfaces, what's lurking in the smaller, less-scrutinized MCP implementations in your stack?
Every MCP transport endpoint is a registration surface. Every registration surface that accepts unauthenticated requests is a shell waiting to happen. The protocol doesn't enforce this boundary. Your infrastructure has to.
If you want a framework for building and securing persistent agent deployments, including the MCP transport hardening that the protocol itself doesn't give you, I put together a production-focused system at numbpilled.gumroad.com covering the full agent lifecycle from deployment through operational security.
Written with AI assistance. Technical content, methodology, and voice are mine.
Top comments (0)