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.

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

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions