You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
security: POST /automation/:name/runs/:runId/resume never checks WHO is resuming — an authenticated stranger with a run id continues another user's paused screen run, which then runs under the starter's stored context #19987
Gate category ① — a product defect with a named landing point and a named failing probe. ⚠️Self-reported P0 suspect (security, NORTH-STAR 优先级 1): asking for the emergency triage path.
Filed by the domain:cli execution PM seat #6024 (session session_01TnPAC1UsTGfHPXVUCL6iLn). The #15705 dev found it (report os-dev-report on #15705) while building the MCP resume_run verb. The seat re-read the landing sites on origin/main2c1011b01b itself. ⛔ Filed without a domain:*, priority:* or type label: triage grades and routes.
The defect
The write door has no caller gate. In packages/runtime/src/domains/automation.ts, the resume arm opens at :2141 (parts[3] === 'resume' && m === 'POST') and reaches automationService.resume(parts[2], signal) about 145 lines later. Between the two it validates the body shape and closed key set only. The arm reads no executionContext, userId or getRun, and has no refusal helper.
The run then continues under the STORED context.packages/services/service-automation/src/engine.ts:6783 reads const context = run.context. So a runAs: 'user' flow's downstream data nodes run as the user who STARTED the run, with values the stranger submitted.
⇒ One pause, two doors: the read refuses a stranger and the write admits one.
Named failing probe (the dev's, relayed: ⛔ not committed, ⛔ not re-driven by this seat)
An HttpDispatcher test in which caller u2 POSTs flow_a/runs/run_1/resume for a run whose getRun().trigger.userId is u1. Expected: a refusal. Today: 200, and resume is called.
Reachability: a caller needs another user's run id. GET /:name/runs requires the sys_automation_run grant since #7900, so operators can list them. Run ids are not guessable.
Why this may not be a plain fix, stated for triage
The docblock above isFlowAuthoringWrite (automation.ts, around :410-416) says the resume door is deliberately left out of the #7900 metadata gate: 「already fail-closed on the suspended node's resumeAuthority … a second, unrelated gate in front of it would refuse the very user the flow paused for」. An identity gate shaped like #7968's Option B (trigger identity OR grant) would ⛔ not refuse that user. The #7968 docblock (:1028-1032) records per-run resume authority ("Option A") as 「the coherent end state and ADR-0019 class design work」. ⇒ Triage decides: apply Option B's shape to the write (execution), or treat the write as the Option A design question (the decision box). Both refuse this card's stranger.
Already closed on the MCP door, ⛔ not here
PR #19985 (#15705, draft, awaiting its contract review) gives MCP resume_run its own ownership check: getRun(runId).trigger.userId must equal the caller. It deliberately leaves the REST door unchanged, and it is ⛔ not this card's carrier.
Who acts on it
After grading: the lane that owns packages/runtime/src/domains/automation.ts, which is domain:cli by the domain table, or the maintainer if triage routes it as the Option A decision.
Dedup (closed included)
search_issues repo-scoped, taken by this filing act:
Gate category ① — a product defect with a named landing point and a named failing probe.⚠️ Self-reported P0 suspect (security, NORTH-STAR 优先级 1): asking for the emergency triage path.
Filed by the
domain:cliexecution PM seat #6024 (sessionsession_01TnPAC1UsTGfHPXVUCL6iLn). The #15705 dev found it (reportos-dev-reporton #15705) while building the MCPresume_runverb. The seat re-read the landing sites onorigin/main2c1011b01bitself. ⛔ Filed without adomain:*,priority:*or type label: triage grades and routes.The defect
packages/runtime/src/domains/automation.ts, the resume arm opens at:2141(parts[3] === 'resume' && m === 'POST') and reachesautomationService.resume(parts[2], signal)about 145 lines later. Between the two it validates the body shape and closed key set only. The arm reads noexecutionContext,userIdorgetRun, and has no refusal helper.resumeAuthority(automation: the generic run-resume route needs an authorization gate keyed on the suspended node #3801 / automation:resumeAuthoritydefaults to'any', so every future pausing node ships fail-open — ADR-0044 says this is "tracked separately" and nothing tracks it #5561). The MCPrun_actionon a screen flow dead-ends: the screen pauses even when every input is bound, andlist_actionsnever surfaces flow input variables #15705 dev reports that a screen node answersanythere; this seat has ⛔ not re-read that value.packages/services/service-automation/src/engine.ts:6783readsconst context = run.context. So arunAs: 'user'flow's downstream data nodes run as the user who STARTED the run, with values the stranger submitted.GET /:name/runs/:runId/screenrefuses a stranger throughrefuseUnrelatedScreenRead(:1067): the run's owntrigger.userId, OR thesys_automation_runread grant as an operator override. That is maintainer ruling Option B on finding:GET /automation/:name/runs/:runId/screenstill discloses record-derived values to any authenticated caller who knows a run id #7968, whose acceptance is 「stranger with valid auth + run id ⇒ denied; triggering user ⇒ screen」. The write has no counterpart gate.⇒ One pause, two doors: the read refuses a stranger and the write admits one.
Named failing probe (the dev's, relayed: ⛔ not committed, ⛔ not re-driven by this seat)
An
HttpDispatchertest in which calleru2POSTsflow_a/runs/run_1/resumefor a run whosegetRun().trigger.userIdisu1. Expected: a refusal. Today: 200, andresumeis called.Reachability: a caller needs another user's run id.
GET /:name/runsrequires thesys_automation_rungrant since #7900, so operators can list them. Run ids are not guessable.Why this may not be a plain fix, stated for triage
The docblock above
isFlowAuthoringWrite(automation.ts, around:410-416) says the resume door is deliberately left out of the #7900 metadata gate: 「already fail-closed on the suspended node'sresumeAuthority… a second, unrelated gate in front of it would refuse the very user the flow paused for」. An identity gate shaped like #7968's Option B (trigger identity OR grant) would ⛔ not refuse that user. The #7968 docblock (:1028-1032) records per-run resume authority ("Option A") as 「the coherent end state and ADR-0019 class design work」. ⇒ Triage decides: apply Option B's shape to the write (execution), or treat the write as the Option A design question (the decision box). Both refuse this card's stranger.Already closed on the MCP door, ⛔ not here
PR #19985 (#15705, draft, awaiting its contract review) gives MCP
resume_runits own ownership check:getRun(runId).trigger.userIdmust equal the caller. It deliberately leaves the REST door unchanged, and it is ⛔ not this card's carrier.Who acts on it
After grading: the lane that owns
packages/runtime/src/domains/automation.ts, which isdomain:cliby the domain table, or the maintainer if triage routes it as the Option A decision.Dedup (closed included)
search_issuesrepo-scoped, taken by this filing act:restoreConsumedSuspensioncannot reach a nested run — a stranded child's cascade-failed ancestors are consumed without a snapshot, so restoring the child continues into a dead parent #15222, service-automation: two concurrent resumes of one run on two replicas can both advance it — the idempotency guard is per-process #14333, automation resume: a known top-level key with a type-mismatched value is silently dropped — the submission is still treated as empty #9416, A durable PAUSED run is invisible toGET /automation/:name/runsand to run-detail after a cold restart — while remaining fully resumable #8050, automation: the generic run-resume route needs an authorization gate keyed on the suspended node #3801, service-automation: runAs:'user' runs data ops with a credential-less user — trigger context never carries the actor's permission sets/positions (follow-up to #1888) #3356.waitpause is service-owned but type-keyed gating can't see it #3823, automation: the generic run-resume route needs an authorization gate keyed on the suspended node #3801.GET /automation/:name/runs/:runId/screenstill discloses record-derived values to any authenticated caller who knows a run id #7968 gated the read twin. ⇒ 0 open cards cover the write door's caller identity.Dedup words: resume door caller identity · stranger resume paused run · runs resume trigger userId · resume ownership runAs user · refuseUnrelatedScreenRead resume twin