Skip to content

fix(dag): prevent cross-session workflow tool access #210

Description

@LeXwDeX

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingready-for-agentFully specified and ready for an agent

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions