Skip to content

Fix Mass Assignment in Execution Update Endpoint#6416

Open
tonghuaroot wants to merge 1 commit into
FlowiseAI:mainfrom
tonghuaroot:bugfix/execution-update-strip-protected-fields
Open

Fix Mass Assignment in Execution Update Endpoint#6416
tonghuaroot wants to merge 1 commit into
FlowiseAI:mainfrom
tonghuaroot:bugfix/execution-update-strip-protected-fields

Conversation

@tonghuaroot
Copy link
Copy Markdown

Summary

PUT /api/v1/executions/:id accepted the raw request body and applied it to the matched Execution row via Object.assign(new Execution(), req.body) followed by repository.merge(existing, new) and save(), with no allowlist of writable fields. A workspace user could overwrite any column on an execution they owned, including:

  • isPublic: flipping this to true exposes the row through the unauthenticated GET /api/v1/public-executions/:id route (whitelisted in utils/constants.ts::WHITELIST_URLS), which leaks the full LLM / tool transcript stored in executionData. _removeCredentialId only strips credentialId fields; the rest of the transcript is returned verbatim.
  • workspaceId: reparents the execution into another workspace.
  • agentflowId: re-points the row at a chatflow the attacker does not own, confusing the leftJoinAndSelect('execution.agentflow', ...) join in getAllExecutions.
  • executionData, state, action, stoppedDate: lets an attacker rewrite the audit trail of their own executions.

The UI only calls this endpoint to toggle isPublic (packages/ui/src/views/agentexecutions/ExecutionDetails.jsx, packages/ui/src/views/agentexecutions/ShareExecutionDialog.jsx, packages/observe/src/infrastructure/api/executions.ts), so this PR narrows the writable surface to a single explicit field. Internal callers that need to mutate executionData / state / stoppedDate use the dedicated updateExecution helper in utils/buildAgentflow.ts, which does not pass through this service function and is therefore unaffected.

This follows the same fix model used by the prior mass-assignment patches on Variable / Tool / Chatflow / Assistant / CustomTemplate / Dataset / Evaluation update endpoints.

Refs: GHSA-v2cc-6h2j-w847

Changes

  • packages/server/src/services/executions/index.ts — narrows updateExecution writable surface to an explicit EXECUTION_UPDATABLE_FIELDS allowlist (['isPublic']). Builds a sanitized partial from the request body and passes only that to Object.assign before merge + save.
  • packages/server/src/services/executions/index.test.ts (new) — co-located service test with 9 cases: legitimate isPublic toggle still works; workspaceId, agentflowId, state, action, executionData, stoppedDate, and id from the request body are ignored; mixed legitimate + malicious payloads still toggle isPublic while ignoring the rest; lookup still correctly scoped by workspaceId; NOT_FOUND on missing row.

Test plan

  • pnpm --filter "./packages/server" test -- --testPathPattern services/executions — 9/9 pass on patched source, 5/9 mass-assignment tests fail when run against the unpatched source (confirms tests are real regressions, not no-ops).
  • pnpm lint -- packages/server/src/services/executions/index.ts packages/server/src/services/executions/index.test.ts — 0 errors.
  • End-to-end reproduction against flowiseai/flowise:3.1.2: pre-patch, PUT {isPublic:true} flips the column and the row becomes readable through unauthenticated GET /api/v1/public-executions/:id with full PII transcript; with this patch, isPublic toggles still work but workspaceId / agentflowId / state / executionData / stoppedDate payloads are silently ignored.

Signed-off-by: tonghuaroot tonghuaroot@gmail.com

`PUT /api/v1/executions/:id` previously accepted the raw request body
and applied it to the matched Execution row via
`Object.assign(new Execution(), req.body)` followed by
`repository.merge(existing, new)` and `save()`, with no allowlist of
writable fields. A workspace user could overwrite any column on an
execution they owned, including:

- `isPublic`: flipping this to `true` exposes the row through the
  unauthenticated `GET /api/v1/public-executions/:id` route (whitelisted
  in `utils/constants.ts::WHITELIST_URLS`), which leaks the full LLM /
  tool transcript stored in `executionData`. `_removeCredentialId` only
  strips `credentialId` fields; the rest of the transcript is returned
  verbatim.
- `workspaceId`: reparents the execution into another workspace.
- `agentflowId`: re-points the row at a chatflow the attacker does not
  own, confusing the `leftJoinAndSelect('execution.agentflow', ...)` join
  in `getAllExecutions`.
- `executionData`, `state`, `action`, `stoppedDate`: lets an attacker
  rewrite the audit trail of their own executions.

The UI only ever calls this endpoint to toggle `isPublic`
(`packages/ui/src/views/agentexecutions/ExecutionDetails.jsx`,
`packages/ui/src/views/agentexecutions/ShareExecutionDialog.jsx`,
`packages/observe/src/infrastructure/api/executions.ts`), so the
writable surface is narrowed to a single explicit field. Internal
callers that need to mutate `executionData`, `state`, `stoppedDate`,
etc. use the dedicated `updateExecution` helper in
`utils/buildAgentflow.ts`, which does not pass through this service
function and is therefore unaffected.

Adds a co-located regression test that exercises the legitimate
`isPublic` toggle and verifies that `workspaceId`, `agentflowId`,
`state`, `action`, `executionData`, `stoppedDate`, and `id` cannot
be mass-assigned through this service.

Refs: GHSA-v2cc-6h2j-w847
Signed-off-by: tonghuaroot <tonghuaroot@gmail.com>
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an allowlist for the updateExecution service to prevent mass assignment vulnerabilities by restricting updates to the isPublic field. It also adds comprehensive unit tests to verify that server-managed columns like workspaceId and agentflowId cannot be overwritten via the request body. Feedback was provided to further strengthen security by avoiding mass assignment utilities like Object.assign() and repository.merge() in favor of explicit property mapping.

Comment on lines 147 to 150
const updateExecution = new Execution()
Object.assign(updateExecution, data)
Object.assign(updateExecution, sanitized)
await appServer.AppDataSource.getRepository(Execution).merge(execution, updateExecution)
const dbResponse = await appServer.AppDataSource.getRepository(Execution).save(execution)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid using mass assignment utilities like repository.merge() or Object.assign() when updating entities with data from request bodies. Instead, explicitly map allowed properties (such as chatflowId or apiMessageId) to the entity. This prevents security vulnerabilities like mass assignment and ensures that sensitive fields are not inadvertently modified.

Suggested change
const updateExecution = new Execution()
Object.assign(updateExecution, data)
Object.assign(updateExecution, sanitized)
await appServer.AppDataSource.getRepository(Execution).merge(execution, updateExecution)
const dbResponse = await appServer.AppDataSource.getRepository(Execution).save(execution)
const executionRepository = appServer.AppDataSource.getRepository(Execution)
execution.status = sanitized.status
execution.result = sanitized.result
const dbResponse = await executionRepository.save(execution)
References
  1. Avoid mass assignment from request bodies to entities; explicitly map allowed properties to prevent security issues like IDOR or mass assignment vulnerabilities.
  2. When constructing flowData objects or updating entities, explicitly map required properties instead of relying on spread operators or mass assignment to ensure consistency and security.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant