Skip to content

chore(infra): pin the Claude Code CLI and nginx, move the runner off EOL node - #73

Merged
mariuspruvot merged 1 commit into
mainfrom
chore/pin-runtime-images
Aug 1, 2026
Merged

chore(infra): pin the Claude Code CLI and nginx, move the runner off EOL node#73
mariuspruvot merged 1 commit into
mainfrom
chore/pin-runtime-images

Conversation

@mariuspruvot

Copy link
Copy Markdown
Owner

The last of the unpinned-inputs findings. The CLI one is the only one that could take production down silently.

The Claude Code CLI

npm install -g @anthropic-ai/claude-code had no version, on the image whose output the entire SSE pipeline parses. The five stream-json event types documented in CLAUDE.md are that CLI's contract, not ours — an upstream format change would break production the next time the image was rebuilt, with no code change, no diff to review, and nothing in CI that would notice.

Pinned to 2.1.220 through an ARG, with the bump procedure written beside it and in CLAUDE.md: raise the ARG, rebuild, run a session end to end, confirm the five event types still arrive.

node 20 is end of life

Since April 2026 — on the one image in the system that runs claude --dangerously-skip-permissions over attacker-authored PR content, so no longer receiving upstream security patches. The web build was already on 22.

Because this changes the base image under the shell fix from #65, I re-ran the injection payload against the new base rather than assuming Debian is Debian:

title  : benign|;e touch /tmp/PWNED #
render : **PR #1**: benign|;e touch /tmp/PWNED # by oct|ocat
result : clean on node:22

nginx

nginx:alpine had no version component, so a new nginx major would arrive in production on a rebuild with nothing to review. Pinned to 1.29-alpine.

Left unpinned on purpose

gh. Apt pins break as soon as the pinned version leaves the repository, which turns a reproducibility measure into a build that fails on a Tuesday. It is installed from GitHub's own signed keyring, and nothing in helPRs parses its output format the way it does the CLI's.

Verification

Built the runner image and checked the toolchain inside it: node v22.23.2, claude 2.1.220, gh 2.97.0. shellcheck clean. No application code touched, so the test suites are unchanged.

…EOL node

`npm install -g @anthropic-ai/claude-code` was unpinned, on the image whose
output the entire SSE pipeline parses. The five stream-json event types the
backend depends on are that CLI's contract, not ours, so an upstream format
change would have broken production on the next image rebuild -- no code
change, no diff to review, nothing in CI to catch it. Pinned to 2.1.220 via
an ARG, with the bump procedure written down next to it.

node:20-slim reached end of life in April 2026, on the one image that
executes untrusted PR content, while the web build was already on 22. Now
node:22-slim: verified the runner still builds and that the entrypoint's
awk substitution keeps neutralising the injection payload on the new base.

`nginx:alpine` had no version component, so a new nginx major would land in
production on a rebuild with nothing to review. Pinned to 1.29-alpine.

`gh` is left unpinned on purpose: apt pins break as soon as the old version
leaves the repository, and it is installed from GitHub's own signed keyring.

Verified inside the built image: node v22.23.2, claude 2.1.220, gh 2.97.0.
@helprs-prod

helprs-prod Bot commented Aug 1, 2026

Copy link
Copy Markdown

helPRs session created for this PR.

Skill: challenge-me | Open session

@mariuspruvot
mariuspruvot merged commit 95cfc73 into main Aug 1, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant