Authorize the manual workflow run endpoint
Committed by nawazdhandala
fix(workflow): authorize the manual workflow run endpoint
GHSA-6c3w-7xg4-4cf7 / CVE-2026-35053 reported GET/POST
/workflow/manual/run/:workflowId as having no authentication. The patch
added UserMiddleware.getUserMiddleware to both routes, but that
middleware is a context loader rather than a gate: with no cookie, no
bearer token and no apikey header it tags the request UserType.Public
and calls next(). The handler never inspected userType, and
QueueWorkflow.addWorkflowToQueue is not passed the request, so it could
not check the caller either. A credential-less request still reached the
queue and scheduled the workflow (GHSA-v8v9-p6ff-jwmm).
Authorize in the handler, following the pattern already used for the
other project-scoped custom routes (MonitorAPI /refresh-status,
WorkspaceNotificationRuleAPI /test):
- assertAuthenticatedProjectMember rejects Public callers.
- The owning project is read off the workflow row (as root) and matched
against the caller's project, so a member of project A cannot reach
project B's workflow id by sending their own tenantid header. A
workflow that does not exist is rejected identically, so the route is
not an id oracle.
- Running a workflow executes its components (arbitrary JavaScript,
notification sends, resource create/delete), so it is gated on the
Workflow model's own update permissions rather than mere membership.
Programmatic callers are unaffected: they trigger a workflow through its
own /workflow/trigger/:secretkey route, authenticated by the workflow's
secret key.
The dashboard's Run button used a raw API.post that sent no tenantid
header, so it now sends ModelAPI.getCommonHeaders().
Adds App/Tests/FeatureSet/Workflow/ManualWorkflowRunAuth.test.ts, which
had no coverage before. 17 of its 30 tests fail against the pre-fix
handler, including the advisory's credential-less POST proof of concept.Editor assessment
This commit patches CVE-2026-35053. The subject is short and accurate; the body is a model of security reasoning. It recounts that the first patch added a middleware which merely tags requests as Public and calls next, so a credential-less request still queued a workflow, then explains how the handler rejects Public callers, reads the owning project from the workflow row as root to prevent cross-tenant access by header, and rejects nonexistent workflows identically so the route is not an id oracle. It justifies gating on the workflow's update permissions because runs execute arbitrary JavaScript and notifications. The test evidence - seventeen of thirty failing pre-fix, including the advisory proof of concept - is unusually concrete.