Symptom
A workflow tool call from Session B can read and control a workflow whose durable session_id belongs to Session A. The status response exposes child Session IDs, and control(pause) publishes a durable dag.workflow.paused event. The same tool also accepts start.session_id for Session A and creates a new workflow attached to that foreign parent.
Reproduction
Baseline for existing-workflow access: dev@26e057fa8
cd packages/opencode
bun test test/dag/workflow-tool.test.ts --test-name-pattern "rejects reads and mutations from a session that does not own the workflow"
The public-seam test failed deterministically 3/3 times in about 1.07 seconds per run: status and extend succeeded, a child Session ID leaked, control succeeded, and a pause event was published.
The follow-up start seam also failed before the fix:
bun test test/dag/workflow-tool.test.ts --test-name-pattern "start rejects a parent session other than the calling session"
Observed: Exit.isFailure(exit) was false; the foreign-parent workflow was created.
Root cause
WorkflowTool.execute did not establish one parent-Session boundary. It resolved workflow_id for status, extend, and every control operation without comparing the durable workflow's sessionId with ctx.sessionID; start also trusted an arbitrary params.session_id.
The guard belongs at WorkflowTool, because internal Dag.Service methods intentionally lack caller context and are also used by runtime recovery. The project-scoped HTTP API already performs its own project ownership checks.
Expected contract
The workflow tool is parent-Session scoped. A foreign Session must receive a non-enumerating failure and must not read node details, create or extend graphs, publish events, or mutate workflows.
Acceptance criteria
- Add one shared parent-Session ownership guard for existing workflows at the
WorkflowTool seam.
- Apply it to
status, extend, and every control operation, including replan and step.
- Reject
start.session_id when it differs from the calling Session.
- Keep internal
Dag.Service runtime calls and project-scoped HTTP API semantics unchanged.
- Retain both red tests and verify no data disclosure or durable side effect occurs.
Verification
workflow-tool.test.ts: 34 passed, 100 expectations.
- DAG behavior suites: Core 90, OpenCode 401, Schema 3, TUI 50.
packages/opencode typecheck and repository typecheck passed.
- SDK generated output is unchanged; lint remains within the existing 4850-warning ratchet with 0 errors.
Symptom
A
workflowtool call from Session B can read and control a workflow whose durablesession_idbelongs to Session A. The status response exposes child Session IDs, andcontrol(pause)publishes a durabledag.workflow.pausedevent. The same tool also acceptsstart.session_idfor Session A and creates a new workflow attached to that foreign parent.Reproduction
Baseline for existing-workflow access:
dev@26e057fa8The public-seam test failed deterministically 3/3 times in about 1.07 seconds per run: status and extend succeeded, a child Session ID leaked, control succeeded, and a pause event was published.
The follow-up
startseam also failed before the fix:Observed:
Exit.isFailure(exit)wasfalse; the foreign-parent workflow was created.Root cause
WorkflowTool.executedid not establish one parent-Session boundary. It resolvedworkflow_idforstatus,extend, and everycontroloperation without comparing the durable workflow'ssessionIdwithctx.sessionID;startalso trusted an arbitraryparams.session_id.The guard belongs at
WorkflowTool, because internalDag.Servicemethods intentionally lack caller context and are also used by runtime recovery. The project-scoped HTTP API already performs its own project ownership checks.Expected contract
The workflow tool is parent-Session scoped. A foreign Session must receive a non-enumerating failure and must not read node details, create or extend graphs, publish events, or mutate workflows.
Acceptance criteria
WorkflowToolseam.status,extend, and everycontroloperation, includingreplanandstep.start.session_idwhen it differs from the calling Session.Dag.Serviceruntime calls and project-scoped HTTP API semantics unchanged.Verification
workflow-tool.test.ts: 34 passed, 100 expectations.packages/opencodetypecheck and repository typecheck passed.