Found while working on #3800. Separate seam, same trust boundary — filing rather
than widening that PR's scope.
Detail is deliberately withheld here, same convention as #3800: the analysis
lives in the private draft advisory under the repo's Security tab. Note it is
reasoned from code, not demonstrated — no repro was written, so treat the
severity as strong-but-unproven until someone does.
Requirement
POST /api/v1/automation/:name/runs/:runId/resume
(packages/runtime/src/domains/automation.ts) forwards a caller-supplied
branchLabel into AutomationEngine.resume. resumeInternal validates
machine state only — the concurrent-resume latch, the run exists, the flow
exists, the suspended node still exists — and nothing about who is asking.
Approval nodes suspend and resume through exactly this mechanism, so the route
needs an authorization gate of its own; today the only thing standing between it
and the approvals rules is convention. The showcase spells that convention out in
a comment ("decide via the approvals API, never a raw engine resume"), and
route-ledger.ts lists the route as SDK-exposed — a comment in an example is not
an access control.
Beyond authorization, a resume that goes around the service also leaves no
sys_approval_action row and no status mirror, so the request row and the run
can end up permanently disagreeing.
Why it is not simply "remove the route"
The route is load-bearing for screen flows: the UI flow-runner posts
{ inputs } to advance a paused screen node, which is its whole reason to
exist. The gate has to discriminate by what the run is parked on, not by the
route — a run suspended on an approval node should be resumable only through
ApprovalService (which records the decision and enforces the slate), while a
screen node keeps working as it does now.
Sketch, for discussion rather than a settled design: have the engine tag a
suspension with the node type that produced it, and have the resume route refuse
a suspension whose owner declares itself service-gated, with the service-side
resume passing an internal marker the route cannot forge.
Refs #3800.
Found while working on #3800. Separate seam, same trust boundary — filing rather
than widening that PR's scope.
Detail is deliberately withheld here, same convention as #3800: the analysis
lives in the private draft advisory under the repo's Security tab. Note it is
reasoned from code, not demonstrated — no repro was written, so treat the
severity as strong-but-unproven until someone does.
Requirement
POST /api/v1/automation/:name/runs/:runId/resume(
packages/runtime/src/domains/automation.ts) forwards a caller-suppliedbranchLabelintoAutomationEngine.resume.resumeInternalvalidatesmachine state only — the concurrent-resume latch, the run exists, the flow
exists, the suspended node still exists — and nothing about who is asking.
Approval nodes suspend and resume through exactly this mechanism, so the route
needs an authorization gate of its own; today the only thing standing between it
and the approvals rules is convention. The showcase spells that convention out in
a comment ("decide via the approvals API, never a raw engine
resume"), androute-ledger.tslists the route as SDK-exposed — a comment in an example is notan access control.
Beyond authorization, a resume that goes around the service also leaves no
sys_approval_actionrow and no status mirror, so the request row and the runcan end up permanently disagreeing.
Why it is not simply "remove the route"
The route is load-bearing for screen flows: the UI flow-runner posts
{ inputs }to advance a pausedscreennode, which is its whole reason toexist. The gate has to discriminate by what the run is parked on, not by the
route — a run suspended on an
approvalnode should be resumable only throughApprovalService(which records the decision and enforces the slate), while ascreennode keeps working as it does now.Sketch, for discussion rather than a settled design: have the engine tag a
suspension with the node type that produced it, and have the resume route refuse
a suspension whose owner declares itself service-gated, with the service-side
resume passing an internal marker the route cannot forge.
Refs #3800.