Monitor vulnerabilities like this one.
Sign up free to get alerted when software you use is affected.
7.1
Flowise: Server-Side Request Forgery Attacks Can Succeed
GHSA-2x8m-83vc-6wv4
Summary
Flowise's security features to prevent Server-Side Request Forgery (SSRF) contain flaws that allow attackers to bypass restrictions. This means they can trick Flowise into making unwanted requests to internal servers or websites. To fix this, make sure the HTTP_DENY_LIST environment variable is set, and consider updating your Flowise version to the latest patch.
What to do
- Update henryheng flowise to version 3.1.0.
- Update henryheng flowise-components to version 3.1.0.
Affected software
| Ecosystem | Vendor | Product | Affected versions |
|---|---|---|---|
| npm | henryheng | flowise |
<= 3.0.13 Fix: upgrade to 3.1.0
|
| npm | henryheng | flowise-components |
<= 3.0.13 Fix: upgrade to 3.1.0
|
Original title
Flowise: SSRF Protection Bypass (TOCTOU & Default Insecure)
Original description
### Summary
The core security wrappers (secureAxiosRequest and secureFetch) intended to prevent Server-Side Request Forgery (SSRF) contain multiple logic flaws. These flaws allow attackers to bypass the allow/deny lists via DNS Rebinding (Time-of-Check Time-of-Use) or by exploiting the default configuration which fails to enforce any deny list.
### Details
The flaws exist in packages/components/src/httpSecurity.ts.
Default Insecure: If process.env.HTTP_DENY_LIST is undefined, checkDenyList returns immediately, allowing all requests (including localhost).
DNS Rebinding (TOCTOU): The function performs a DNS lookup (dns.lookup) to validate the IP, and then the HTTP client performs a new lookup to connect. An attacker can serve a valid IP first, then switch to an internal IP (e.g., 127.0.0.1) for the second lookup.
### PoC
nsure HTTP_DENY_LIST is unset (default behavior).
Use any node utilizing secureFetch to access http://127.0.0.1.
Result: Request succeeds.
Scenario 2: DNS Rebinding
Attacker controls domain attacker.com and a custom DNS server.
Configure DNS to return 1.1.1.1 (Safe IP) with TTL=0 for the first query.
Configure DNS to return 127.0.0.1 (Blocked IP) for subsequent queries.
Flowise validates attacker.com -> 1.1.1.1 (Allowed).
Flowise fetches attacker.com -> 127.0.0.1 (Bypass).
Run the following for manual verification
"// PoC for httpSecurity.ts Bypasses
import * as dns from 'dns/promises';
// Mocking the checkDenyList logic from Flowise
async function checkDenyList(url: string) {
const deniedIPs = ['127.0.0.1', '0.0.0.0']; // Simplified deny list logic
if (!process.env.HTTP_DENY_LIST) {
console.log("⚠️ HTTP_DENY_LIST not set. Returning allowed.");
return; // Vulnerability 1: Default Insecure
}
const { hostname } = new URL(url);
const { address } = await dns.lookup(hostname);
if (deniedIPs.includes(address)) {
throw new Error(`IP ${address} is denied`);
}
console.log(`✅ IP ${address} allowed check.`);
}
async function runPoC() {
console.log("--- Test 1: Default Configuration (Unset HTTP_DENY_LIST) ---");
// Ensure env var is unset
delete process.env.HTTP_DENY_LIST;
try {
await checkDenyList('http://127.0.0.1');
console.log("[PASS] Default config allowed localhost access.");
} catch (e) {
console.log("[FAIL] Blocked:", e.message);
}
console.log("\n--- Test 2: 'private' Keyword Bypass (Logic Flaw) ---");
process.env.HTTP_DENY_LIST = 'private'; // User expects this to block localhost
try {
await checkDenyList('http://127.0.0.1');
// In real Flowise code, 'private' is not expanded to IPs, so it only blocks the string "private"
console.log("[PASS] 'private' keyword failed to block localhost (Mock simulation).");
} catch (e) {
console.log("[FAIL] Blocked:", e.message);
}
}
runPoC();"
### Impact
Confidentiality: High (Access to internal services if protection is bypassed).
Integrity: Low/Medium (If internal services allow state changes via GET).
Availability: Low.
The core security wrappers (secureAxiosRequest and secureFetch) intended to prevent Server-Side Request Forgery (SSRF) contain multiple logic flaws. These flaws allow attackers to bypass the allow/deny lists via DNS Rebinding (Time-of-Check Time-of-Use) or by exploiting the default configuration which fails to enforce any deny list.
### Details
The flaws exist in packages/components/src/httpSecurity.ts.
Default Insecure: If process.env.HTTP_DENY_LIST is undefined, checkDenyList returns immediately, allowing all requests (including localhost).
DNS Rebinding (TOCTOU): The function performs a DNS lookup (dns.lookup) to validate the IP, and then the HTTP client performs a new lookup to connect. An attacker can serve a valid IP first, then switch to an internal IP (e.g., 127.0.0.1) for the second lookup.
### PoC
nsure HTTP_DENY_LIST is unset (default behavior).
Use any node utilizing secureFetch to access http://127.0.0.1.
Result: Request succeeds.
Scenario 2: DNS Rebinding
Attacker controls domain attacker.com and a custom DNS server.
Configure DNS to return 1.1.1.1 (Safe IP) with TTL=0 for the first query.
Configure DNS to return 127.0.0.1 (Blocked IP) for subsequent queries.
Flowise validates attacker.com -> 1.1.1.1 (Allowed).
Flowise fetches attacker.com -> 127.0.0.1 (Bypass).
Run the following for manual verification
"// PoC for httpSecurity.ts Bypasses
import * as dns from 'dns/promises';
// Mocking the checkDenyList logic from Flowise
async function checkDenyList(url: string) {
const deniedIPs = ['127.0.0.1', '0.0.0.0']; // Simplified deny list logic
if (!process.env.HTTP_DENY_LIST) {
console.log("⚠️ HTTP_DENY_LIST not set. Returning allowed.");
return; // Vulnerability 1: Default Insecure
}
const { hostname } = new URL(url);
const { address } = await dns.lookup(hostname);
if (deniedIPs.includes(address)) {
throw new Error(`IP ${address} is denied`);
}
console.log(`✅ IP ${address} allowed check.`);
}
async function runPoC() {
console.log("--- Test 1: Default Configuration (Unset HTTP_DENY_LIST) ---");
// Ensure env var is unset
delete process.env.HTTP_DENY_LIST;
try {
await checkDenyList('http://127.0.0.1');
console.log("[PASS] Default config allowed localhost access.");
} catch (e) {
console.log("[FAIL] Blocked:", e.message);
}
console.log("\n--- Test 2: 'private' Keyword Bypass (Logic Flaw) ---");
process.env.HTTP_DENY_LIST = 'private'; // User expects this to block localhost
try {
await checkDenyList('http://127.0.0.1');
// In real Flowise code, 'private' is not expanded to IPs, so it only blocks the string "private"
console.log("[PASS] 'private' keyword failed to block localhost (Mock simulation).");
} catch (e) {
console.log("[FAIL] Blocked:", e.message);
}
}
runPoC();"
### Impact
Confidentiality: High (Access to internal services if protection is bypassed).
Integrity: Low/Medium (If internal services allow state changes via GET).
Availability: Low.
ghsa CVSS3.1
7.1
Vulnerability type
CWE-367
CWE-918
Server-Side Request Forgery (SSRF)
Published: 16 Apr 2026 · Updated: 16 Apr 2026 · First seen: 16 Apr 2026