Abstract technical illustration on a dark navy field: a glowing input port on the left wall of a translucent box admits a single thin pale line carrying no badge and no key. Inside the box the line meets a stack of small rounded panels and branches into a short row of angular command-like strokes that travel down into a lower compartment holding a cluster of glowing key-shaped tokens. A second, closed gate outlined in pale blue sits beside the input port. No text, no people, no organisation logos.
AI Safety Incidents

One Unauthenticated POST to Bifrost's Management API Started a Command as the Gateway User

Author

Magnus Corvin

Date Published

Latest models · 20View all models →
Benchmarks: Artificial Analysis · updated daily
Back to all posts

On 14 September 2026, JFrog Security Research published CVE-2026-90898, a command-execution flaw in Bifrost — the open-source AI gateway maintained by Maxim — and scored it 9.8 out of 10. The attack was one HTTP request. An unauthenticated POST /api/mcp/client registers a stdio MCP client, which Bifrost reads as a program name plus an argument list and starts immediately, before any MCP handshake, as the user running the gateway; on the official image that user is appuser. A gateway is also where an operator's provider keys and virtual keys live, so a process running there reaches credentials. What made the request anonymous was a shipped default: governance.auth_config.is_enabled is false, and, in JFrog's phrasing, "auth off means every caller is a local admin." No cited source reports the flaw was ever exploited. The record's own sources, severity grade and confidence assessment sit in the AI incident archive, alongside the other documented AI-gateway and MCP vulnerabilities of the past year.

Bifrost is a single service that sits between an application and the language models it calls. Its repository describes it as an open-source, Apache-2.0-licensed gateway that "unifies access to 23+ providers ... through a single OpenAI-compatible API", with MCP proxying, virtual keys, budgets and per-provider load balancing among its features, and it carried roughly 9,150 GitHub stars on 28 October 2026. That combination is the whole reason a shell on this particular host is worth a critical score: the process that routes the requests is also the process that holds the credentials for every upstream it routes to. The Hacker News, reporting the flaw on 22 September, put the consequence plainly — because the gateway stores API keys for every connected provider, executing commands on the gateway process grants the attacker access to those credentials.

The whole attack was a single POST

JFrog's advisory, published under reference JFSA-2026-001686326, describes an endpoint that exists to let an administrator add an MCP server. A stdio client in the MCP specification is not a URL; it is a command and its arguments, which the host program is expected to spawn. Bifrost accepts that configuration over the management API and starts the program as soon as the client is added. No capability negotiation, no server identity check, no handshake has to succeed first — the advisory is explicit that the handshake is not what starts the process.

The proof of concept in the advisory is a short JSON body posted to a gateway with authentication left at its default. It registers a client whose command is /bin/sh and whose arguments write a marker file, then reads the marker back. JFrog adds an operational detail that matters during incident response: the HTTP request may time out while Bifrost waits for a handshake that never comes, and a timeout in the logs does not mean the command failed to start. The process is already running by then.

The CVE record, assigned by JFrog as the CVE Numbering Authority, maps the defect to CWE-306, missing authentication for a critical function, and CWE-284, improper access control, and names the two files that carry the handler and its middleware: transports/bifrost-http/handlers/mcp.go and transports/bifrost-http/handlers/middlewares.go. The affected module is github.com/maximhq/bifrost/transports, on Linux and macOS, for every version below 2.1.0.

The default that made the request anonymous

None of that would be exploitable without a second condition, and the second condition is the product's own default. Bifrost's management authentication middleware is gated by governance.auth_config.is_enabled, which ships as false. With it off, the middleware treats every caller as a local administrator — the advisory's own summary is that authentication off means every caller is a local admin — so the check that would have stopped the request was never in the path.

The fix, pull request #6757, was merged into the project on 2 September 2026, twelve days before the CVE was published and six days before the release that carries it. Its description is unusually candid about the shape of the problem: the pull request closes two unauthenticated-access primitives in the same registration API, not one. The first is the stdio client. The second is an http or sse MCP client pointed at a loopback or private-network address, which the request calls an SSRF primitive against internal services, "including the 169.254.169.254 cloud metadata endpoint". Both are now refused with HTTP 403 when the request reaches the handler without a genuine credential check. Authenticated administrators keep both capabilities, and operators can still provision stdio clients through config.json.

That second primitive does not appear in the CVE text. It is in the patch, and it is the reason the fix is a gate rather than a removal: the feature was never the bug. Treating an unauthenticated caller as an administrator was.

Where the management API was actually listening

Reachability is the part of the record the sources describe most consistently, and measure least. JFrog's advisory gives the stock binary's default as localhost:8080, which confines an attacker to the host itself. The same team's advisory for the companion flaw states that the official Docker image binds 0.0.0.0 instead, and The Hacker News repeats it: the image makes the management API reachable from outside the container if the port is published. Bifrost's own README quick start runs "docker run -p 8080:8080 maximhq/bifrost", and Docker's -p publishes a container port on every host interface unless an address is given. The GitHub advisory for the flaw records that the reported path was verified against the published maximhq/bifrost:latest image, where an unauthenticated request launched /bin/sh and wrote a marker as appuser.

What no source provides is a number. There is no count of internet-facing Bifrost instances, no scan result, no telemetry and no victim. The record carries no real harm, and that absence is a fact about the reporting rather than a reassurance — the vulnerability class is only dangerous where two conditions coincide, an exposed listener and an untouched default, and nobody has published how often they did.

One endpoint, two flaws, one root cause

Eight days before the MCP advisory, on 6 September 2026, the same research team published CVE-2026-86242, a second unauthenticated path into the same process. Found by Or Peles and scored 8.1, it abuses POST /api/plugins: Bifrost accepts an enabled custom plugin whose path is an HTTP URL, the shared-object loader treats an http-prefixed path as a download, writes the body to a temporary .so file and hands that file to Go's plugin.Open. If the load succeeds, an optional Init function runs immediately with the attacker's configuration, as the gateway user.

The build flavour decides what an attacker gets. On the dynamically linked builds that the project documents for custom Go plugins, the load is expected to succeed, which the advisory calls unauthenticated remote code execution. On the published statically linked Docker image, plugin.Open fails with "Dynamic loading not supported", so the same request degrades to server-side request forgery. A loadable plugin must also match the host's Go version, operating system, architecture and linkage, which is a real constraint rather than a formality. The fix is transports/v2.0.0, via pull request #5763, and the affected range is everything below it.

The trap is the release ordering. v2.0.0 fixes the plugin path and does not fix the MCP path — The Hacker News says so explicitly, and JFrog's MCP advisory confirms that v2.0.0 still allows the unauthenticated stdio registration. An operator who patched promptly after the 6 September advisory was, on that point alone, still exposed until transports/v2.1.0 shipped on 8 September. Both flaws reduce to the same sentence in both advisories and in the vendor's own pull request: authentication disabled is the default.

What the sources establish, and what they do not

The mechanism, the version range, the fix and the credit are agreed across the advisory, the CVE record JFrog assigned, the project's own pull request and release notes, and The Hacker News. The remaining disagreement is about severity, and it is real.

The 9.8 is JFrog's score as the assigning CNA, and it is the score NVD carries — the NVD record lists reefs@jfrog.com as the metric source and, as of 28 October 2026, still shows the entry in "Awaiting Analysis" status, meaning NVD has published the assigner's vector rather than its own analysis of it. The CISA Coordinator's SSVC assessment attached to the same record, timestamped 14 September 2026, reads exploitation "none", automatable "yes" and technical impact "total". FIRST's EPSS model put the probability of exploitation in the next 30 days at 2.3 percent on 27 October 2026, in the 83rd percentile across all scored CVEs, and neither this CVE nor CVE-2026-86242 appears in the CISA Known Exploited Vulnerabilities catalogue dated 27 October 2026.

Against that, Maxim disputes the rating. In a statement to The Hacker News, Akshay Deo, the company's chief technology officer and co-founder, said the practical risk is lower than the critical score suggests, because exploitation requires the management interface to be reachable from an untrusted network and the operator to have left authentication unset; the default and documented deployment, he said, places the gateway inside a private network. The Hacker News reports that Maxim considers the flaw low-severity and is working with JFrog, which assigned the CVE, on revising the assessment. That statement appears in that one outlet; no second publication quoting Maxim's position was found, and no revision had been published as of 28 October 2026 — the CVE record was last updated on 14 September and the NVD entry on 18 September, both still at 9.8.

Two further gaps are worth naming rather than smoothing over. The project's GitHub advisories, published on 23 September 2026 and crediting JFrog's researcher as the reporter, still carry 9.8 for the MCP flaw and 8.1 for the plugin flaw, with no sign of the revision Maxim described. And the affected ranges in those advisories are written against Go module versions — below 1.5.27 and below 1.6.3 — rather than the transports release tags the JFrog advisories use, so an operator matching one document against the other has to reconcile two numbering schemes before deciding whether a build is affected.

Rendered two-column card with the kicker 'BIFROST · CVE-2026-90898' and the title 'What the sources establish — and what they do not', subtitled 'Sources: JFrog JFSA-2026-001686326 (14 September 2026), the CVE record assigned by JFrog, NVD, FIRST EPSS, the CISA KEV catalogue, and the project's pull request 6757 and release tags.' The left column, headed 'ESTABLISHED' in blue, lists eight items: score 9.8 with the vector AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H supplied by the CNA; CWE-306 missing authentication with CWE-284 access control; unauthenticated POST /api/mcp/client starting a program as the gateway user; affected below transports 2.1.0 when management auth is disabled, the default; fixed in transports 2.1.0, PR 6757 merged 2 September, released 8 September 2026; CISA SSVC of 14 September 2026 with exploitation none, automatable yes, impact total; EPSS 2.3 percent at the 83rd percentile on 27 October 2026; and not in the CISA KEV catalogue dated 27 October 2026. The right column, headed 'NOT ESTABLISHED, OR CONTESTED' in amber, lists eight items: no exploitation reported, and the archive records real harm as false; no count of exposed or affected deployments in any cited source; severity contested, the vendor's CTO telling The Hacker News it is low; no published revision to the score as of 28 October 2026; NVD still Awaiting Analysis, so the 9.8 is the assigner's and not NVD's; affected ranges stated in two numbering schemes across the advisories; no detection or triage guidance in either advisory; and no evidence tying the flaw to any intrusion. A footer reads 'Card rendered by OrcaRouter'. The OrcaRouter logo is composited in the bottom-right corner.

What changed, and what operators were told to do

The chronology runs backwards from the disclosure, which is the least intuitive part of the record. Pull request #6757 merged on 2 September 2026. transports/v2.1.0, the release the advisory names as the first containing the fix, was published on 8 September — six days before CVE-2026-90898 was published on 14 September. The project's plugin fix preceded its own advisory in the same way: transports/v2.0.0 shipped on 26 August, eleven days before the 6 September CVE. Operators who track releases rather than feeds had the patches before they had the warnings.

What the upgrade buys is narrow and specific. On 2.1.0 and later, an unauthenticated stdio registration returns 403 and the command does not start; authenticated administrators retain the capability, and config.json provisioning still works. The advisory's stopgap for anyone who cannot move is to set governance.auth_config.is_enabled to true, use strong administrator credentials and keep the management listener off untrusted networks. JFrog's instruction for instances that ran exposed with authentication disabled is not to investigate and hope: treat them as compromised, and rotate virtual keys and provider credentials.

The version arithmetic is the part most likely to be misread. v2.0.0 does not fix this flaw. The 1.6.x line through 1.6.11 does not contain the fix either, and the project's newest 1.6-series tag is transports/v1.6.11, published on 14 August 2026, with no 1.7 line in the repository as of 28 October 2026 — so there is no patched release inside that series, and a 1.6.x operator has to cross to 2.1.0 or later rather than wait for a backport. The current release line is well past that point: transports/v2.3.2 was published on 20 October 2026.

Rendered release-timeline card with the kicker 'BIFROST · RELEASE TIMELINE', the title 'The fix shipped six days before the advisory' and the subtitle 'Dates from the project's release tags and pull request metadata, the JFrog advisories and The Hacker News. Every date below is a single day.' A horizontal timeline carries seven dated markers: 26 Aug 2026, transports 2.0.0 published; 2 Sep 2026, PR 6757 merged; 6 Sep 2026, CVE-2026-86242 published, 8.1; 8 Sep 2026, transports 2.1.0, the fix; 14 Sep 2026, CVE-2026-90898 published, 9.8; 22 Sep 2026, vendor statement reported; 23 Sep 2026, GitHub advisories published. Below it, three bars show release lines: a hatched amber bar labelled '1.6.x line' reading 'affected — no patched release in the series; newest tag 1.6.11, 14 August 2026'; a bar labelled '2.0.0' split between a pale blue segment reading 'plugin flaw fixed' and a red segment reading 'MCP flaw still open'; and a solid blue bar labelled '2.1.0 and later' reading 'MCP fix — unauthenticated stdio registration returns 403'. A foot band headed 'THE PART THAT IS EASY TO GET WRONG' notes that 2.0.0 was the patched release on 6 September and did not close the MCP path, that for the two days before 2.1.0 an operator who patched on the day of the warning was still exposed, and that the 1.6.x series has no fixed release at all. A footer reads 'Card rendered by OrcaRouter'. The OrcaRouter logo is composited in the bottom-right corner.

Two things happened after publication that a defender can use. The project's GitHub security advisories — GHSA-86gf-xh3g-rvxq for the MCP flaw at critical and GHSA-2qp8-4xgm-fw6g for the plugin flaw at high — went up on 23 September 2026, giving the dependency and advisory feeds a second identifier to match on. And on 13 October 2026 a detection template for CVE-2026-90898 was merged into the public nuclei-templates repository, which means the check for an unauthenticated registration attempt is now something a scanning pipeline can run rather than something each team has to write.

Abstract illustration on a dark navy field: a wide translucent slab representing a credential store, filled with evenly spaced glowing key-shaped tokens, stands behind a low wall. A narrow gateway aperture in the wall holds a fresh, brighter set of tokens, a blank card-shaped slip lies pushed aside on the ground in front of it, and a thin amber arc sweeps from the aperture around to the store. A second gateway aperture in the background remains closed and unlit. No text, no people, no organisation logos.

What is left open is the severity, and it will be settled or not by the two organisations still talking about it. Maxim's position, as reported, is that a 9.8 overstates a flaw that needs an exposed listener and an unset default to matter — a claim that is testable in principle and untested in public, because no one has published how many deployments met those two conditions. JFrog's position is the score it assigned as CNA, still standing in the CVE record and in NVD. Until one of them publishes a revision, the operational reading is the same either way: the flaw is real and was verified end to end on the published container image, the fix shipped before the warning, the 1.6 series has no fix at all, and any instance that ran with authentication disabled on a reachable listener should be treated as a credential incident rather than a patch to schedule.