Skip to content

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

Description

@objectstack-fleet

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/main 2c1011b01b itself. ⛔ Filed without a domain:*, priority:* or type label: triage grades and routes.

The defect

⇒ 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:

Dedup words: resume door caller identity · stranger resume paused run · runs resume trigger userId · resume ownership runAs user · refuseUnrelatedScreenRead resume twin

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions