Repository navigation
🤖 tests: move the bug-bash sandbox image to Node 22.23.4 (CVE-2026-111497) - #7362
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
No job left output: |
|
The merge queue has been blocked for every PR since this PR merged at 18:35Z. Each merge group fails
(for example run 40012690165, #7364). The queue is empty now: #7360 and #7364 were dequeued for this. The two follow-up steps from this PR's body have not run yet:
@ibetitsmike, are you doing these? If not, the issue coordinator desk will do them once the maintainer approves the publish. Generated with |
…2.23.4 sandbox image (coder#7369) ## Summary This PR fixes the two problems that keep main red, in one PR, because each fix fails CI without the other. The first commit is Thomas's fix from coder#7364 (cherry-picked, his authorship kept). It moves the ask_user_question argument schemas out of `toolDefinitions.ts`, so the renderer entry chunk no longer pulls in every tool schema and `rendererFirstLoad.test.ts` passes again. The second commit pins the bug-bash sandbox image that main published from coder#7362 (base `node:22.23.4-slim`, the CVE-2026-111497 fix), so the model-driven story jobs run again on main and in the merge queue. Fixes coder#7363. ## Background - coder#6887 made `openAskUserQuestions.ts`, which the eager `StreamingMessageAggregator` loads, import `TOOL_DEFINITIONS`. That moved `toolDefinitions.ts` and its imports into `main-*.js` (+92 KB raw, +23.7 KB br) and fails `Test / Unit (5/6)`: "renderer first load > the entry's static imports do not reach the tool definitions module". - coder#7362 changed the sandbox Dockerfile, so the inputs key on main is now `2bf9e12d…`, but `image.json` still pins the old image. `make test-stories` refuses the old record ("sandbox refused: stale image: image.json has inputs key 08a81da3ddeb1ec1, this checkout 2bf9e12d3ac7dcb1"). `Test / Stories` and `Test / Stories (Haiku)` count in `Required` (`STORIES_MODE` and `STORIES_PR_MODE` are `block`), so the queue removed coder#7360 and coder#7364 on that failure. So coder#7364 alone fails the story jobs, and the pin alone fails the unit test. The queue groups entries with `ALLGREEN`, so neither can merge first. This PR carries both. ## Implementation - `perf: keep toolDefinitions out of the main chunk (coder#7363)` is fd6b719 from coder#7364 with no change (the same `git patch-id`). The new `src/common/utils/tools/askUserQuestionSchemas.ts` imports only zod and holds the three ask_user_question schemas. `toolDefinitions.ts` imports and re-exports them, and `openAskUserQuestions.ts` parses with `AskUserQuestionToolArgsSchema` directly. coder#7364 has the bundle measurements: the main chunk is back within 750 B br of the build before coder#6887. coder#7364 can close after this merges. - `tests/bugbash/sandbox/image.json` gets this record, written with the same `jq -n` command that `build.sh --push` uses: | Field | Value | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `digest` | `sha256:e3804b5418a8e6783698d807c75cc256efae45b81b03400644ae7f13364c9a03` | | `inputsKey` | `2bf9e12d3ac7dcb10397698df2792bf1b244b3fcbe68225378302ef695ba302a`, what `make -s bugbash-sandbox-key` prints on this commit | | `playwrightCore` | `1.63.0` (unchanged) | | `publishedFrom` | `2e2a2b769f417aedcecc09f33abb379d6c8be7b1` (main, the coder#7362 merge) | ## Validation - `rendererFirstLoad.test.ts` fails on the pin commit alone (21f9c3f) and passes with the cherry-pick. - Publish run on main: [40016150145](https://github.com/coder/xum/actions/runs/40016150145), from `2e2a2b769f`. The digest is the subject of its build provenance attestation. - `gh attestation verify oci://ghcr.io/coder/xum-bugbash-sandbox@sha256:e3804b54… --repo coder/xum` passes. The signer is `.github/workflows/bugbash-sandbox-image.yml@refs/heads/main` (workflow_dispatch, run 40016150145), and the source commit is `2e2a2b769f`. - I pulled the image by digest. It is linux/amd64, its `org.xum.bugbash.inputs` label is `2bf9e12d…a302a`, its `org.xum.bugbash.playwright-core` label is `1.63.0`, and `node -v` in it prints `v22.23.4`. Co-authored-by: Thomas Kosiewski <tk@coder.com> --------- Co-authored-by: Thomas Kosiewski <tk@coder.com>
…oder#7671) ## Summary The "Bug-bash sandbox image" workflow now publishes the image by itself. A push to main that changes an image input runs the publish job. The job creates the image only if the key has none yet: each inputs key gets one stable tag, `ghcr.io/coder/xum-bugbash-sandbox:inputs-<key>`. A key that has its tag pushes nothing. This is PR 1 of 2. Runners still trust only the digest in `image.json` until PR 2. ## Background An image change takes four steps today, and two of them need a person. coder#7362 changed the Dockerfile. Its story lanes refused the stale image, so it needed an admin merge, and the merge queue then stayed red for 3.5 hours. After that, a maintainer ran the manual publish on main, and coder#7369 pinned the new digest. Four images went through that process between Oct 8 and Oct 23. The plan, which the user approved: main publishes once per key with no person (this PR). Then every sandbox job pulls `inputs-<key>`, checks its build attestation (`gh attestation verify --source-ref refs/heads/main`), and builds locally when there is no verified image (PR 2). `image.json` and the `BUGBASH_SANDBOX_BUILD_STALE` opt-in go away in PR 2. ## Implementation 1. **New trigger:** `push` to main, filtered to the image inputs (`build.sh --key` hashes the Dockerfile, `build.sh` and the playwright-core version in `bun.lock`) and to this workflow file. A change to this file therefore publishes the current key once. `Makefile` is not in the push filter. A Makefile change does not change the key, and an existing key needs no publish. 2. **Create if missing:** the publish step looks up the stable tag with `docker buildx imagetools inspect`. If the tag exists, the step writes "exists" to the summary and stops. Only a `: not found` error counts as missing. Any other lookup error fails the job. If the tag is missing, the step runs the publish as before. 3. **Tag last:** Attest signs the new digest. Only then does a new step, "Tag inputs-<key>", point the stable tag at it with `imagetools create --prefer-index=false`. That flag copies the manifest as it is. The default wraps a single manifest in a new index with a new, unsigned digest. The step reads the tag's digest back and fails unless it equals the signed digest. The stable tag marks the key as done, so a failed Attest leaves no tag, and a re-run publishes again. 4. **No races:** the existing concurrency group runs one publish at a time and never cancels it. A second push of the same key waits, then finds the tag and stops. 5. **Docs:** AGENTS.md and the bug-bash skill now say that the merge push publishes the image. A manual run on main retries a failed publish. This PR does not change any image input, so the inputs key stays `2bf9e12d…`, the key in `image.json`. No old-style publish is needed for this PR. ## Validation - I ran the step's lookup branch against GHCR with an empty Docker config, with these results: - The current key's stable tag: `PUBLISH: … missing`. - An existing published tag: `SKIP: … exists`. - An unreachable registry: `FAIL` with exit 1, and the DNS error printed. - I ran the "Tag inputs-<key>" step's script against a local registry, with a single-platform Docker v2 manifest like the published image. It kept the source digest. Without `--prefer-index=false`, the tag got a different digest. - `actionlint` 1.7.13: clean. `make lint-zizmor` and `make static-check`: pass. - After the merge, I will check two runs. The merge push must publish `inputs-2bf9e12d3ac7dcb10397698df2792bf1b244b3fcbe68225378302ef695ba302a`. Then a manual run on main must report "exists" and push nothing. I will link both runs here. ## Risks Low. Publishing stays main-only, from the run's own commit, with a fresh build and an attestation. A new path is the one trigger: every `bun.lock` change on main starts a run that ends after the lookup, at about one minute of runner time each. A failed lookup fails the run and pushes nothing. <details> <summary>Decisions in the approved plan</summary> 1. A job can build the image itself in CI and on a developer machine. 2. Only main pushes images, after a merge. The merge queue does not push. 3. A job uses a registry image only if its attestation shows that main's publish workflow built it. Otherwise it builds locally. 4. Two PRs: this publish change first, then the runner, `build.sh` and docs. </details> --- _Generated with `xum` • Model: `anthropic:claude-opus-5-5` • Thinking: `xhigh` • Cost: `$263.47`_ <!-- mux-attribution: model=anthropic:claude-opus-5-5 thinking=xhigh costs=263.47 -->
…e build locally (coder#7672) ## Summary Every sandbox job now gets its image without a pin. In CI and on a developer machine, the runner looks up the stable tag `ghcr.io/coder/xum-bugbash-sandbox:inputs-<key>` for its checkout's inputs key. It checks the tag's build attestation with `gh`, and it uses the image only if main's publish workflow built it. In every other case it builds the image from its own checkout, about 40 s in CI, and warns. `tests/bugbash/sandbox/image.json`, the `BUGBASH_SANDBOX_BUILD_STALE` opt-in and the job-kind gate go away. With this PR, an image change takes one normal PR: no manual publish, no pin PR and no stale-key refusal. This is PR 2 of 2. It is stacked on coder#7671, which makes main publish the stable tag. ## Background The user approved the plan "Sandbox Image Upsert in CI". Today the runner trusts only the digest in `image.json` and refuses a checkout whose key differs from it. Only the self-check and the exact-step repros can build locally, and only in CI. So a Dockerfile change broke the story lanes until a person published the image and pinned it. After coder#7362, that kept the merge queue red for 3.5 hours. ## Implementation `Session.ensureImage()` (`tests/bugbash/sandbox/runner.ts`) resolves the image in this order: 1. It looks up the stable tag anonymously, through the runner's private Docker client config, with `docker buildx imagetools inspect`. 2. If the tag exists, it runs `gh attestation verify oci://IMAGE@digest --repo coder/xum --bundle-from-oci --source-ref refs/heads/main --signer-workflow coder/xum/.github/workflows/bugbash-sandbox-image.yml` on the host. `gh` gets only `PATH`, `HOME`, `GH_TOKEN` and `GH_CONFIG_DIR`. None of them reaches Docker or the job container. 3. If the check passes, the runner uses `IMAGE@digest` and pulls it when Docker does not have it. 4. In every other case, the runner logs the reason and reuses or builds the local image with `build.sh`: a missing tag, a lookup error, a bad digest, no `gh`, no gh token, a failed check or a timeout. 5. The label check (`org.xum.bugbash.inputs` must equal the key) still runs for both kinds of image. It still refuses on a mismatch. The launcher prints the LOCAL IMAGE warning with the reason, as a `::warning::` in Actions, for every job kind. A local build gives a job no new power. Each job already stages its checkout's git-listed files and mounts its `dist/` and `node_modules/` in the container. So a job runs either an image that main's workflow built and attested, or one built from its own checkout. A registry tag that someone else pushed is never used. CI gives the launcher steps `GH_TOKEN: ${{ github.token }}`: the story lanes in `stories.yml` and both steps of `Test / Bug-bash sandbox` in `pr.yml`. This PR changes `build.sh` and the Dockerfile, which are comment-only changes, so its key changes. Its own CI therefore runs the local-build path in every sandbox job. ## Validation - `bun test ./tests/bugbash ./tests/bugbash/sandbox`: 356 pass. - Each path in `ensureImage` has a test in `runner.test.ts`. The implementer broke each path in turn and saw its test fail. That was 10 breaks, plus 3 for the env allowlist. - `GH_TOKEN` and `GH_CONFIG_DIR` reach the fake `gh`. They never reach the fake `docker` or the `docker run` line. - `make static-check`, `make lint-eslint`, `make lint-zizmor`, actionlint and `make crap-check`: pass. - Real Docker, $0, no model: `make bug-bash-sandbox-check` passed, 54 of 54. It built the image once for the new key and reused it for all 3 jobs, with one warning per job ("… is not published"). It passed again with no gh token. - `gh` with the runner's exact arguments and env: - The current published digest: exit 0. - `--source-ref refs/heads/nope`: exit 1. - No login: exit 4, which the runner reports as "no gh token". Still to do after coder#7671 lands and publishes `inputs-2bf9…`: run the self-check from a checkout with this runner and main's key, and show that it pulls and verifies the image with no build. After this PR merges, its new key gets published, and the next PR's sandbox jobs must pull that image without a build. I will link both runs here. ## Risks - I did not verify that the job token is enough for `gh attestation verify --bundle-from-oci` in CI. If it is not, CI builds locally with a warning, about 40 s per job, and does not fail. I will check the first CI logs. - The preflight of `make bug-bash`, `make mcp-apps-e2e` and `make test-stories` now builds the image when no verified one exists. That can take up to 25 minutes. Before, it refused. - If a stable tag ever named a digest without main's attestation, every job would build locally and warn until a person deleted that tag. --- _Generated with `xum` • Model: `anthropic:claude-opus-5-5` • Thinking: `xhigh` • Cost: `$266.91`_ <!-- mux-attribution: model=anthropic:claude-opus-5-5 thinking=xhigh costs=266.91 -->
Summary
The bug-bash sandbox image pins
node:22-slimby digest, and that digest is Node 22.23.3, which is affected by CVE-2026-111497 (crafted Brotli data runs code). This moves the base to thenode:22.23.4-slimindex digest (sha256:de170b39…, Node 22.23.4 on amd64 and arm64). The sandbox runs the seededxum serveron this Node.Background
#7359 already makes the server refuse compressed request bodies on any Node, and the sandbox has no network, so the remaining risk in the sandbox is small. The bump keeps test infrastructure off an affected Node, like the release image.
Landing
A change to the Dockerfile changes the sandbox inputs key, so it lands in three steps: this PR, a run of the "Bug-bash sandbox image" workflow on main, then a PR that pins the published digest in
image.json.make test-storiesrefuses a stale key for model-driven jobs (tests/bugbash/sandbox/launch.ts), and bothTest / Stories(merge queue) andTest / Stories (Haiku)(PR pushes) now count inRequired(STORIES_MODEandSTORIES_PR_MODEareblock). The earlier bumps (#7030 with #7034, #6547 with #6623) landed before those gates blocked. Until theimage.jsonPR merges, story jobs on this PR and on every merge-queue entry refuse the stale key. This PR stays a draft until that is settled.Validation
tests/bugbash/sandbox/build.shbuilt the image locally;node -vin it prints v22.23.4, and itsorg.xum.bugbash.inputslabel is the new key2bf9e12d3ac7dcb10397698df2792bf1b244b3fcbe68225378302ef695ba302a.make static-check(including hadolint) passes.