From 0ea9c95369fe5ad6f52fd3b21339c407373da74d Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Wed, 4 Nov 2026 09:53:52 -0800 Subject: [PATCH 1/3] quest: plan a per-job opencode data directory Co-Authored-By: Claude Opus 5.5 --- quest/m0/kix/sandbox/README.md | 1 + quest/m0/kix/sandbox/opencode-data.md | 47 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 quest/m0/kix/sandbox/opencode-data.md diff --git a/quest/m0/kix/sandbox/README.md b/quest/m0/kix/sandbox/README.md index 53f19d0e..489050a3 100644 --- a/quest/m0/kix/sandbox/README.md +++ b/quest/m0/kix/sandbox/README.md @@ -87,4 +87,5 @@ Shipped in [moq-dev/quest#212](https://github.com/moq-dev/quest/pull/212) - [No Unix sockets in opencode jobs](/quest/m0/kix/sandbox/seccomp.md) - srt's seccomp helper is packaged, so commands can't reach the Nix daemon, and the runner refuses to start without it - [Trust rechecked on queue](/quest/m0/kix/sandbox/trust-on-queue.md) - a job's head trust is reread whenever it queues, so the approval decision shows without a free runner +- [Per-job opencode data](/quest/m0/kix/sandbox/opencode-data.md) - each opencode job gets its own sessions and saved output, so no job reads another's - [Claude Code boundary test](/quest/m0/kix/sandbox/claude-boundary.md) - the same hostile-repository test for Claude Code jobs, with a fake model set in the account's settings diff --git a/quest/m0/kix/sandbox/opencode-data.md b/quest/m0/kix/sandbox/opencode-data.md new file mode 100644 index 00000000..1e9e1f52 --- /dev/null +++ b/quest/m0/kix/sandbox/opencode-data.md @@ -0,0 +1,47 @@ +# [M] Per-job opencode data + +## Goal + +Each opencode job runs with its own opencode data directory, holding its +sessions, saved tool output, and shell captures, with only the account's +credential shared in. The directory is deleted with the job's workspace, so +no job can read what another job saved, whether in another repository or the +same one. A job's own launches still resume its session. + +## Plan + +Decided 2026-11-04 (user, /quest-complete follow-up of +[moq-dev/quest#480](https://github.com/moq-dev/quest/pull/480)). On opencode +2.x, every job on an account shares the account's `data/opencode`, and the +job plugin lets the read-only file tools read all of its `tool-output` and +`shell` captures (`savedOutput` in `src/runner/opencode.ts`). A job can +therefore read output an earlier job saved, such as private moq-dev/moq.pro +output from a job on a public repository. + +- What should the quest's goal be? + - ✅ A per-job data directory + - Rejected: narrowing the plugin's read root to the current project's + subdirectory, which still lets jobs on the same repository read each + other's output +- Which milestone and epic? + - ✅ m0's job sandbox, since the leak already crosses m0's private and + public repositories + - Rejected: m1's contributor sandboxing + +Implementation guidance: + +- Split what `accountDirectories` puts under the account's `XDG_DATA_HOME`: + the credential store stays with the account, and each job's launches get + a data directory under the job's workspace with the credential linked or + copied in. Check which files opencode 2.x keeps there (`auth.json`, the + session database, `tool-output`, `shell`) and which it rewrites, such as + a refreshed OAuth token, which must reach the account's copy. +- `savedOutput` then names the job's own directories. +- Nothing is in production, so existing accounts' shared session data needs + no migration. +- Extend the runner's sandbox test with a second job that tries to read the + first job's saved output. + +## Required + +- [opencode 2.x](/quest/m0/opencode-sdk.md) - this builds on its client and job plugin From 991d01263079e0cfd49d657b698a831f940f92b9 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Wed, 4 Nov 2026 09:57:13 -0800 Subject: [PATCH 2/3] quest: ground the opencode data plan in opencode 2.1.1's storage opencode 2.x keeps credentials in opencode.db beside the sessions, so the credential cannot be linked in as a file. Put the data directory beside the checkout rather than in it, and leave where sessions live as a decision. Co-Authored-By: Claude Opus 5.5 --- quest/m0/kix/sandbox/README.md | 2 +- quest/m0/kix/sandbox/opencode-data.md | 72 +++++++++++++++++++++------ 2 files changed, 57 insertions(+), 17 deletions(-) diff --git a/quest/m0/kix/sandbox/README.md b/quest/m0/kix/sandbox/README.md index 489050a3..d384d438 100644 --- a/quest/m0/kix/sandbox/README.md +++ b/quest/m0/kix/sandbox/README.md @@ -87,5 +87,5 @@ Shipped in [moq-dev/quest#212](https://github.com/moq-dev/quest/pull/212) - [No Unix sockets in opencode jobs](/quest/m0/kix/sandbox/seccomp.md) - srt's seccomp helper is packaged, so commands can't reach the Nix daemon, and the runner refuses to start without it - [Trust rechecked on queue](/quest/m0/kix/sandbox/trust-on-queue.md) - a job's head trust is reread whenever it queues, so the approval decision shows without a free runner -- [Per-job opencode data](/quest/m0/kix/sandbox/opencode-data.md) - each opencode job gets its own sessions and saved output, so no job reads another's +- [Per-job opencode data](/quest/m0/kix/sandbox/opencode-data.md) - each opencode job saves its tool output and shell captures in its own data directory, so no job reads another's - [Claude Code boundary test](/quest/m0/kix/sandbox/claude-boundary.md) - the same hostile-repository test for Claude Code jobs, with a fake model set in the account's settings diff --git a/quest/m0/kix/sandbox/opencode-data.md b/quest/m0/kix/sandbox/opencode-data.md index 1e9e1f52..cedee85f 100644 --- a/quest/m0/kix/sandbox/opencode-data.md +++ b/quest/m0/kix/sandbox/opencode-data.md @@ -2,11 +2,12 @@ ## Goal -Each opencode job runs with its own opencode data directory, holding its -sessions, saved tool output, and shell captures, with only the account's -credential shared in. The directory is deleted with the job's workspace, so -no job can read what another job saved, whether in another repository or the -same one. A job's own launches still resume its session. +Each opencode job runs with its own opencode data directory, deleted with the +job's checkout, so no job can read the tool output, shell captures, or +sessions another job saved, whether in another repository or the same one. A +job's own launches still resume its session, and the account's credential, +including a refreshed OAuth token, stays the one every job on the account +uses. ## Plan @@ -28,19 +29,58 @@ output from a job on a public repository. public repositories - Rejected: m1's contributor sandboxing +What opencode 2.1.1 keeps under `$XDG_DATA_HOME/opencode`, from its source +at tag `v2.1.1`: + +- `opencode.db` holds both the sessions and the credentials (its + `credential` table). `auth.json` is only a legacy file a migration imports + once, so the credential cannot be linked in as a file. `OPENCODE_DB` + overrides the database's path (`packages/cli/src/database-path.ts`), and + the server honours it (`packages/cli/src/server-process.ts`). +- `tool-output/` is flat, with no directory per project + (`packages/core/src/tool-output.ts`). Only `shell/` has one + (`packages/core/src/shell.ts`). Both keep files for 7 days. `log/` and + `repos/` live beside them. +- A shared database's other sessions are out of a job's reach. The subagent + tool resumes only a child of the current session + (`packages/core/src/tool/plugin/subagent.ts`). Renaming or moving another + session needs `tools.opencode.*` through `execute`, which `jobConfig` + denies. + Implementation guidance: -- Split what `accountDirectories` puts under the account's `XDG_DATA_HOME`: - the credential store stays with the account, and each job's launches get - a data directory under the job's workspace with the credential linked or - copied in. Check which files opencode 2.x keeps there (`auth.json`, the - session database, `tool-output`, `shell`) and which it rewrites, such as - a refreshed OAuth token, which must reach the account's copy. -- `savedOutput` then names the job's own directories. -- Nothing is in production, so existing accounts' shared session data needs - no migration. -- Extend the runner's sandbox test with a second job that tries to read the - first job's saved output. +- Put the job's data directory beside its checkout, as `.review` + is, and remove it in `Workspace.remove`. It must not go inside the + checkout, which commands read and kix publishes. The runner's state + directory, which commands cannot read, already holds it. +- The launch sets `XDG_DATA_HOME` to that directory. `savedOutput`, the + `external_directory` rules, and the plugin's `reads` then name only this + job's `tool-output` and `shell`. +- Where sessions live is the open decision under Waiting. +- Account sign-in and status keep the account's own data directory. +- Nothing is in production, so existing accounts' shared data needs no + migration. +- Extend `test/live/runner-sandbox.test.ts` with a second job, in the same + repository, that tries to read the first job's saved tool output and shell + capture. + +## Waiting + +- Maintainer: where should a job's sessions live? A credential in opencode + 2.x is a row in the same `opencode.db` as the sessions, so "only the + credential shared in" means either sharing the account's database or + copying the credential. + - Recommended: set `OPENCODE_DB` to the account's database for every + launch. The credential and its refreshes stay live and shared, as they + are today. Only saved output moves per job, and the other sessions stay + out of reach of the job's tools, as described above. + - Alternative: give each job its own database, seeded from the account's + credential, and write a refreshed token back after each launch. Sessions + become per job too, but concurrent jobs on one account each refresh + their own copy. Where the provider rotates refresh tokens, one job's + refresh invalidates the others'. + - Check that it is done: the maintainer picks one, and the pick replaces + this item in the Plan. ## Required From 181ac740bb68d9cf1a9cb8bc500368ba7b8b3621 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Wed, 4 Nov 2026 10:16:48 -0800 Subject: [PATCH 3/3] quest: share the account database, move only saved output per job The maintainer picked the shared account database for sessions and the credential. Rewrite the goal around saved output and require an absolute OPENCODE_DB path. Co-Authored-By: Claude Opus 5.5 --- quest/m0/kix/sandbox/README.md | 2 +- quest/m0/kix/sandbox/opencode-data.md | 53 +++++++++++++-------------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/quest/m0/kix/sandbox/README.md b/quest/m0/kix/sandbox/README.md index d384d438..cb16994f 100644 --- a/quest/m0/kix/sandbox/README.md +++ b/quest/m0/kix/sandbox/README.md @@ -87,5 +87,5 @@ Shipped in [moq-dev/quest#212](https://github.com/moq-dev/quest/pull/212) - [No Unix sockets in opencode jobs](/quest/m0/kix/sandbox/seccomp.md) - srt's seccomp helper is packaged, so commands can't reach the Nix daemon, and the runner refuses to start without it - [Trust rechecked on queue](/quest/m0/kix/sandbox/trust-on-queue.md) - a job's head trust is reread whenever it queues, so the approval decision shows without a free runner -- [Per-job opencode data](/quest/m0/kix/sandbox/opencode-data.md) - each opencode job saves its tool output and shell captures in its own data directory, so no job reads another's +- [Per-job opencode saved output](/quest/m0/kix/sandbox/opencode-data.md) - each opencode job saves its tool output and shell captures beside its checkout, so no job reads another's, while sessions and the credential stay in the account's database - [Claude Code boundary test](/quest/m0/kix/sandbox/claude-boundary.md) - the same hostile-repository test for Claude Code jobs, with a fake model set in the account's settings diff --git a/quest/m0/kix/sandbox/opencode-data.md b/quest/m0/kix/sandbox/opencode-data.md index cedee85f..fda20b52 100644 --- a/quest/m0/kix/sandbox/opencode-data.md +++ b/quest/m0/kix/sandbox/opencode-data.md @@ -1,13 +1,12 @@ -# [M] Per-job opencode data +# [M] Per-job opencode saved output ## Goal -Each opencode job runs with its own opencode data directory, deleted with the -job's checkout, so no job can read the tool output, shell captures, or -sessions another job saved, whether in another repository or the same one. A -job's own launches still resume its session, and the account's credential, -including a refreshed OAuth token, stays the one every job on the account -uses. +No opencode job can read the tool output or shell captures another job +saved, whether in another repository or the same one. Each job saves them +in its own directory, deleted with the job's checkout. Sessions and the +account's credential, including a refreshed OAuth token, stay in the +account's database that every job on the account shares. ## Plan @@ -29,14 +28,28 @@ output from a job on a public repository. public repositories - Rejected: m1's contributor sandboxing +Decided 2026-11-04 (user, /quest-complete on +[moq-dev/quest#484](https://github.com/moq-dev/quest/pull/484)): + +- Where should a job's sessions live? + - ✅ The shared account database: every job keeps using the account's + `opencode.db` through `OPENCODE_DB`, so the credential and its token + refreshes stay shared. Only saved output (`tool-output`, `shell`) moves + to the per-job directory. + - Rejected: a per-job database with a copied credential, because + concurrent jobs would each refresh their own copy, and where the + provider rotates refresh tokens, one job's refresh invalidates the + others'. + What opencode 2.1.1 keeps under `$XDG_DATA_HOME/opencode`, from its source at tag `v2.1.1`: - `opencode.db` holds both the sessions and the credentials (its `credential` table). `auth.json` is only a legacy file a migration imports once, so the credential cannot be linked in as a file. `OPENCODE_DB` - overrides the database's path (`packages/cli/src/database-path.ts`), and - the server honours it (`packages/cli/src/server-process.ts`). + overrides the database's path, resolved against the data directory unless + it is absolute (`packages/cli/src/database-path.ts`), and the server + honours it (`packages/cli/src/server-process.ts`). - `tool-output/` is flat, with no directory per project (`packages/core/src/tool-output.ts`). Only `shell/` has one (`packages/core/src/shell.ts`). Both keep files for 7 days. `log/` and @@ -56,7 +69,9 @@ Implementation guidance: - The launch sets `XDG_DATA_HOME` to that directory. `savedOutput`, the `external_directory` rules, and the plugin's `reads` then name only this job's `tool-output` and `shell`. -- Where sessions live is the open decision under Waiting. +- The launch sets `OPENCODE_DB` to the absolute path of the account's + `opencode.db`. A relative name would resolve inside the job's directory + and give the job an empty database without the credential. - Account sign-in and status keep the account's own data directory. - Nothing is in production, so existing accounts' shared data needs no migration. @@ -64,24 +79,6 @@ Implementation guidance: repository, that tries to read the first job's saved tool output and shell capture. -## Waiting - -- Maintainer: where should a job's sessions live? A credential in opencode - 2.x is a row in the same `opencode.db` as the sessions, so "only the - credential shared in" means either sharing the account's database or - copying the credential. - - Recommended: set `OPENCODE_DB` to the account's database for every - launch. The credential and its refreshes stay live and shared, as they - are today. Only saved output moves per job, and the other sessions stay - out of reach of the job's tools, as described above. - - Alternative: give each job its own database, seeded from the account's - credential, and write a refreshed token back after each launch. Sessions - become per job too, but concurrent jobs on one account each refresh - their own copy. Where the provider rotates refresh tokens, one job's - refresh invalidates the others'. - - Check that it is done: the maintainer picks one, and the pick replaces - this item in the Plan. - ## Required - [opencode 2.x](/quest/m0/opencode-sdk.md) - this builds on its client and job plugin