From 5543663a67bfd985b71ef811b628bc85623e4bb4 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Mon, 2 Nov 2026 20:54:09 -0800 Subject: [PATCH 1/3] chore(quest): claim quest/m0/host-land/host.md Quest-Claim: e7cc1675d366f450 From de8636f19394175be6e662e35adece9ae41c179a Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Mon, 2 Nov 2026 21:02:51 -0800 Subject: [PATCH 2/3] quest: gate host: symlink attack tests in kixelated/nixos#22, leaving the stack's merge Co-Authored-By: Claude Opus 5.5 --- quest/m0/host-land/host.md | 50 +++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/quest/m0/host-land/host.md b/quest/m0/host-land/host.md index c5343c18..d0133b97 100644 --- a/quest/m0/host-land/host.md +++ b/quest/m0/host-land/host.md @@ -1,4 +1,4 @@ -# [M] Run the gate on the maintainer's host +# [S] Run the gate on the maintainer's host ## Goal @@ -91,26 +91,42 @@ check output written to a file instead of a shared non-blocking pipe, initialized before the fixer's dev shell, and `KillMode=process` on the daemons so a restart keeps detached fixers. -What remains: +Done in [moq-dev/quest#443](https://github.com/moq-dev/quest/pull/443), +2026-11-02, leaving only the merges under Waiting: -- Merge #16 once - [kixelated/nixos#13](https://github.com/kixelated/nixos/pull/13) lands. -- The fixer's container access to CLIProxyAPI and the Codex login, which the - [fixer](/quest/m0/host-land/fixer.md) needs. -- VM tests for three more symlink attacks (an escaping chain, a link to a - directory, a write through a symlinked directory), which the extraction - already rejects. -- moq-bot's `organization_self_hosted_runners` write permission served only - the retired runner; the maintainer may drop it. -- The VM test's switch steps fail on about a third of runs with "Job for - quest-land-fixer.socket canceled" when several `*-socket-refresh` - services restart at once (user, 2026-10-31). Find the race and fix it, - without retrying the test. +- VM tests for three more symlink attacks: + [kixelated/nixos#22](https://github.com/kixelated/nixos/pull/22), stacked + on [kixelated/nixos#19](https://github.com/kixelated/nixos/pull/19). + tarfile's data filter refuses a link to an outside directory, and rewrites + the links of an escaping chain and of a write through a symlinked directory + so they stay inside; the tests assert that nothing lands outside the + destination. +- The switch race ("Job for quest-land-fixer.socket canceled" when several + `*-socket-refresh` services restart at once) is fixed in + kixelated/nixos#19 (`21a1bde`): the refresh services close their listeners + in activation's stop phase and reopen them in its start phase. +- Kept: moq-bot's `organization_self_hosted_runners` write permission, once + thought to serve only the retired runner, now mints the quest, rind and + rust2 queue runners' registration tokens (kixelated/nixos#19's + `scripts/ci-runner-token.sh`). Amended 2026-11-01 (maintainer, /quest-plan; see the [queue watcher](/quest/m0/queue-watcher/README.md)): host-gate work is frozen except fixes the switch needs. The `QUEST_GATE_PRIORITY=1` export in the generated check command (planned 2026-10-31) is dropped with it. The items above stay, since the fixer container and its launcher serve the watcher too, -and the gate keeps serving the repositories not yet switched. The watcher's -own unit is [Watcher on the host](/quest/m0/queue-watcher/host.md). +and the gate keeps serving the repositories not yet switched. The watcher +runs from the same module as each repository's daemon in watch mode +(`services.quest-land.repositories..watch`), live for moq-dev/moq +since 2026-11-02. + +## Waiting + +- Maintainer: merge the kixelated/nixos stack once + [kixelated/nixos#13](https://github.com/kixelated/nixos/pull/13) lands: + [#16](https://github.com/kixelated/nixos/pull/16), then + [#18](https://github.com/kixelated/nixos/pull/18), + [#19](https://github.com/kixelated/nixos/pull/19), and the PRs stacked on + it, including [#22](https://github.com/kixelated/nixos/pull/22). Only the + maintainer merges into the host's configuration. Check that + `git log origin/main` in kixelated/nixos contains each PR's head. From 122d458c623c2152c57c119d51bef14b09128305 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 3 Nov 2026 22:58:39 -0800 Subject: [PATCH 3/3] quest: delete the gate host quest, its kixelated/nixos stack merged Co-Authored-By: Claude Opus 5.5 --- quest/m0/host-land/README.md | 1 - quest/m0/host-land/host.md | 132 ------------------------------ quest/m0/queue-watcher/runners.md | 4 - 3 files changed, 137 deletions(-) delete mode 100644 quest/m0/host-land/host.md diff --git a/quest/m0/host-land/README.md b/quest/m0/host-land/README.md index 4ddad506..fcffaee2 100644 --- a/quest/m0/host-land/README.md +++ b/quest/m0/host-land/README.md @@ -123,6 +123,5 @@ exactly the tested tree. ## Required -- [Gate host](/quest/m0/host-land/host.md) - kixelated/nixos runs the daemon as its own user, checks in nspawn containers, and retires the Actions runner - [Fixer TLS](/quest/m0/host-land/fixer-tls.md) - the fixer's container trusts public certificates, so workerd and the Worker tests run there - [Merge commits](/quest/m0/host-land/merge-commits.md) - an instance can land with merge commits, keeping the merged branch's ancestry and the tested tree; squash stays the default diff --git a/quest/m0/host-land/host.md b/quest/m0/host-land/host.md deleted file mode 100644 index d0133b97..00000000 --- a/quest/m0/host-land/host.md +++ /dev/null @@ -1,132 +0,0 @@ -# [S] Run the gate on the maintainer's host - -## Goal - -The maintainer's NixOS host runs `quest land serve` for moq-dev/quest as a -dedicated unprivileged system user under a hardened systemd unit, from -[kixelated/nixos](https://github.com/kixelated/nixos). Each batch's check -and each fixer run happen in an ephemeral systemd-nspawn container whose -network reaches only the package registries and the host's CLIProxyAPI. -Only the gate's user can read moq-bot's App private key, and no container -can read it or the maintainer's Codex login. The Land workflow's -self-hosted `quest-land` runner is retired. Check it by landing a batch, and -by failing to read the bot key from another user and from inside a -container. - -## Plan - -Decided 2026-10-30 (user, /quest-plan): the host gate pushes as moq-bot, -which `main`'s ruleset lets bypass the `quest land` check. - -- Isolation (user, 2026-10-30): - - ✅ A dedicated user plus the sandbox - - A microVM per batch - - A separate machine -- No GitHub Actions (user, 2026-10-30). The host already runs the - `quest-land` Actions runner - ([kixelated/nixos@6e28c62](https://github.com/kixelated/nixos/commit/6e28c62)): - an ephemeral nspawn container that registers through moq-bot (App - 1039466). Moving land.yml onto it was recommended; the user asked what - Actions still provides. Its only real value was public logs, so the gate - drops it. - - land.yml on the quest-land runner - - ✅ The daemon, outside Actions -- Sandbox (user, 2026-10-30): - - ✅ An nspawn container per batch, following `modules/ci-runners.nix`: - a private network, mapped UIDs, and nothing of the host's home - directories, secrets, or SSH agent - - Bubblewrap through `src/runner/sandbox.ts` -- The daemon starts the container through a command the host provides, so - the quest code stays host-agnostic; this quest writes that command. -- Give the gate its own copy of moq-bot's key through systemd - `LoadCredential`, as `cliproxyapi.service` loads its keys. The runner's - copy goes away with the runner. -- Codex reaches its model through CLIProxyAPI - ([kixelated/nixos#13](https://github.com/kixelated/nixos/pull/13), deployed - but not merged), billed to the maintainer's ChatGPT Codex account and its - credits (user, 2026-10-30). That module loads only Claude accounts today, - so add the maintainer's Codex (ChatGPT) OAuth login to it as another - credential. It listens on the container network's host address too. Proxy over a key in the sandbox: - - Capped API key, no git creds - - ✅ Key via proxy, through the CLIProxyAPI already running here -- The gate gets its own CLIProxyAPI client key, so it can be revoked alone. - The container sees only that client key. -- Harden the unit: `NoNewPrivileges`, `ProtectSystem=strict`, - `ProtectHome=yes`, `PrivateTmp`, and a state directory for the checkout - and the logs. - - -Deployed 2026-10-30 from -[kixelated/nixos#16](https://github.com/kixelated/nixos/pull/16) (live as -`deploy/quest-land`): - -- `quest-land.service` runs the daemon as its own user with the bot key - through `LoadCredential`. Each check runs in an ephemeral nspawn container - started by a root-owned socket launcher that accepts uploads only from that - user. Egress follows the CI runners' public-IPv4 policy (user, 2026-10-30), - not a registry allowlist. -- moq-bot gained Checks read, Commit statuses read, Administration read, and - Issues write (user, 2026-10-30), since the daemon reads the queue as - moq-bot where land.yml used Actions' own token. - -- The check container boots systemd with a user manager for the check user - (user, 2026-10-30), as the gate's own tests need systemd scopes, and has - the CI runners' tools. nspawn keeps the image's public resolvers, since the - host's Tailscale MagicDNS is beyond egress. -- The first live batch landed on 2026-10-30: - [moq-dev/quest#299](https://github.com/moq-dev/quest/pull/299) and - [#300](https://github.com/moq-dev/quest/pull/300) together, then - [#301](https://github.com/moq-dev/quest/pull/301). moq-bot's API squash - merge bypasses the unreported `quest land` check. -- The `quest-land` Actions runner is retired, and the host pins quest at - `49cd094`, whose checks get a standalone shallow clone. - -Deployed 2026-10-31 for moq.pro and rust2 -([kixelated/nixos#16](https://github.com/kixelated/nixos/pull/16)): -`moq-pro` and `rust2` instances, remote-only daemon checkouts so private -repositories need no clone credential, check images that resolve -`*.localhost` through nsncd and ship the CI runners' `dejavu-fonts-minimal`, -check output written to a file instead of a shared non-blocking pipe, -`restartIfChanged = false` on check and fixer instances, submodules -initialized before the fixer's dev shell, and `KillMode=process` on the -daemons so a restart keeps detached fixers. - -Done in [moq-dev/quest#443](https://github.com/moq-dev/quest/pull/443), -2026-11-02, leaving only the merges under Waiting: - -- VM tests for three more symlink attacks: - [kixelated/nixos#22](https://github.com/kixelated/nixos/pull/22), stacked - on [kixelated/nixos#19](https://github.com/kixelated/nixos/pull/19). - tarfile's data filter refuses a link to an outside directory, and rewrites - the links of an escaping chain and of a write through a symlinked directory - so they stay inside; the tests assert that nothing lands outside the - destination. -- The switch race ("Job for quest-land-fixer.socket canceled" when several - `*-socket-refresh` services restart at once) is fixed in - kixelated/nixos#19 (`21a1bde`): the refresh services close their listeners - in activation's stop phase and reopen them in its start phase. -- Kept: moq-bot's `organization_self_hosted_runners` write permission, once - thought to serve only the retired runner, now mints the quest, rind and - rust2 queue runners' registration tokens (kixelated/nixos#19's - `scripts/ci-runner-token.sh`). - -Amended 2026-11-01 (maintainer, /quest-plan; see the -[queue watcher](/quest/m0/queue-watcher/README.md)): host-gate work is frozen -except fixes the switch needs. The `QUEST_GATE_PRIORITY=1` export in the -generated check command (planned 2026-10-31) is dropped with it. The items -above stay, since the fixer container and its launcher serve the watcher too, -and the gate keeps serving the repositories not yet switched. The watcher -runs from the same module as each repository's daemon in watch mode -(`services.quest-land.repositories..watch`), live for moq-dev/moq -since 2026-11-02. - -## Waiting - -- Maintainer: merge the kixelated/nixos stack once - [kixelated/nixos#13](https://github.com/kixelated/nixos/pull/13) lands: - [#16](https://github.com/kixelated/nixos/pull/16), then - [#18](https://github.com/kixelated/nixos/pull/18), - [#19](https://github.com/kixelated/nixos/pull/19), and the PRs stacked on - it, including [#22](https://github.com/kixelated/nixos/pull/22). Only the - maintainer merges into the host's configuration. Check that - `git log origin/main` in kixelated/nixos contains each PR's head. diff --git a/quest/m0/queue-watcher/runners.md b/quest/m0/queue-watcher/runners.md index 0d4b4b08..3113e4f1 100644 --- a/quest/m0/queue-watcher/runners.md +++ b/quest/m0/queue-watcher/runners.md @@ -85,7 +85,3 @@ What remains: - Maintainer: the next time an outside contributor opens a fork PR on moq-dev/quest, moq-dev/rind or moq-dev/rust2, check that its workflow runs wait for approval. - -## Related - -- [Gate host](/quest/m0/host-land/host.md) - the host module these runners and daemons share