Skip to content

chore(base): Go 1.24.1 → 1.26 (build-time patch resolve), Node 22 → 24 - #131

Merged
rappdw merged 2 commits into
mainfrom
chore/toolchain-go126-node24
Aug 11, 2026
Merged

chore(base): Go 1.24.1 → 1.26 (build-time patch resolve), Node 22 → 24#131
rappdw merged 2 commits into
mainfrom
chore/toolchain-go126-node24

Conversation

@rappdw

@rappdw rappdw commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Both Go pins were on an EOL toolchain: Go supports only the two newest majors (1.25, 1.26), so 1.24 receives no security fixes. Node 22 is Maintenance LTS. This is PR-1 of a two-part toolchain refresh.

What changed

Before After
Go (base image) ARG GO_VERSION=1.24.1 pins minor 1.26, resolves newest patch at build time
Go (egress proxy) FROM golang:1.24-bookworm FROM golang:1.26-trixie
proxy/go.mod go 1.24.1 go 1.26.0
Node.js NodeSource setup_22.x setup_24.x

Why the base image now resolves at build time

The tarball URL (go.dev/dl/go<x.y.z>.linux-<arch>.tar.gz) requires a full x.y.z, so the base cannot float via the URL alone — but it can resolve one at build time. ARG GO_VERSION now pins the minor line and doubles as the offline fallback.

This is a response to measured behavior, not theory: ARG GO_VERSION=1.24.1 was introduced 2026-02-21 and never touched again — through 20+ releases (v1.0.0 → v1.5.0) at roughly weekly cadence, while Go shipped 1.24.2–1.24.13 and then two entire minors. A frozen pin's failure mode is human diligence, and the observed diligence was zero. Every other toolchain in this image (NodeSource, rustup … stable, Bun, uv) already resolves at build time; Go was the odd one out only because of the URL shape.

Hardened beyond the naive version: sort -rV | head -1 selects the max patch explicitly rather than inheriting go.dev's array ordering (which is newest-first only incidentally), and a non-numeric resolved value falls back to the pin instead of 404-ing the build.

Verified under real dash (Docker RUN uses /bin/sh, no pipefail) — both paths:

real go.dev:   resolved=1.26.5 chain_exit=0
dead endpoint: resolved=1.26.5 chain_exit=0

The proxy fix restores a mechanism that had gone quiet

golang:1.24-bookworm is a floating minor tag, so it genuinely delivered 12 patch releases (through 1.24.13, pushed 2026-02-04). But once 1.24 left the support window there were no further pushes, so from ~Feb 2026 the documented monthly-freshness + --pull mechanism (HF-incident Issue 3) re-resolved to an unchanged digest — refreshing Debian but not the Go stdlib that implements the proxy's TLS/HTTP/CONNECT I/O. Pinning a supported minor is what makes that mechanism work at all. CLAUDE.md's description is corrected to match this timeline.

Separately, proxy/go.mod is consumed by CI via go-version-file, so the proxy's vet/race/fuzz/govulncheck jobs had been running on an EOL toolchain too.

New test coverage

The base Go block had no guard at all. Added five, including a pin-must-be-a-supported-minor assertion that would have caught this exact rot (negative control: rejects 1.24.1, accepts 1.30.0), plus build-time-resolution, offline-fallback, max-patch, and non-numeric guards.

Scope

Debian bookworm → trixie and Python 3.11 → 3.13 are deliberately not here (they carry apt renames and a persistent-pip consequence) and follow as their own change. The proxy build stage moves to trixie ahead of the base image on purpose: it compiles a static binary into FROM scratch, so its build distro never reaches the runtime image.

Verification

Static (no Docker available in the authoring environment): bash -n, both doc-drift checks, §49 replayed standalone incl. the no-stderr heredoc regression check, generated Dockerfiles inspected for expansion artifacts, Appendix A confirmed byte-identical to the heredoc in the changed region. Externally verified: golang:1.26-trixie exists for amd64+arm64 and includes git (via buildpack-deps:trixie-scm); NodeSource node_24.x serves both arches; setup-go's index contains 1.26.0. go vet/build/test pass locally under an auto-downloaded 1.26.0 toolchain.

Not verifiable without a build — the main residual risk: that all four npm-installed agents behave under Node 24. CI's cache-miss --build-only plus a smoke launch settle it.

🤖 Generated with Claude Code

rappdw and others added 2 commits August 11, 2026 16:50
Go 1.24 left Go's two-release support window, so both pins were on an EOL
toolchain receiving no security fixes:

- Base image pinned ARG GO_VERSION=1.24.1 -- a Mar-2025 patch, untouched for
  16 months across 20+ releases. The tarball URL needs a full x.y.z, so the
  base now pins the MINOR line and resolves the newest patch on it at build
  time from go.dev/dl/?mode=json, with the ARG as offline fallback (the same
  build-time-latest semantics NodeSource/rustup/Bun/uv already use here).
  Hardened: sort -rV picks the max patch explicitly rather than inheriting
  go.dev's array order, and a non-numeric resolved value falls back to the
  pin instead of 404-ing the build. Verified under dash: both the happy path
  and total network failure resolve 1.26.5 with chain exit 0.

- Egress proxy built FROM golang:1.24-bookworm. That floating minor tag did
  deliver 12 patches (through 1.24.13, pushed 2026-02-04), but once 1.24 left
  support the documented monthly-freshness + --pull mechanism (HF-incident
  Issue 3) re-resolved to an unchanged digest -- refreshing only Debian, not
  the Go stdlib that implements the proxy's TLS/HTTP/CONNECT I/O. Moving to
  golang:1.26-trixie restores actual stdlib patch delivery. CLAUDE.md's
  description is corrected to match this timeline.

- proxy/go.mod said go 1.24.1 and CI consumes it via go-version-file, so the
  proxy's vet/race/fuzz/govulncheck jobs were running on an EOL toolchain.

- Node 22 is Maintenance LTS (EOL Apr 2027); 24 is Active LTS (Apr 2028).
  NodeSource node_24.x ships the distro-agnostic nodistro suite (amd64 +
  arm64), so this is independent of the pending trixie base migration.

New run-tests.sh guards cover the base Go block, which had no test coverage:
the pin is asserted to be a supported minor (rejects a regression to 1.24),
plus build-time resolution, offline fallback, max-patch selection, and the
non-numeric guard.

Docs synced: README, CLAUDE.md, SPECIFICATION.md (Appendix A verbatim copy +
Appendix B version table).

Debian bookworm -> trixie and Python 3.11 -> 3.13 are deliberately NOT here;
they follow as their own change. The proxy build stage moves to trixie ahead
of the base image intentionally -- it compiles a static binary into FROM
scratch, so its distro never reaches the runtime image.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@rappdw
rappdw merged commit feed1b6 into main Aug 11, 2026
2 checks passed
rappdw added a commit that referenced this pull request Aug 11, 2026
…133)

Debian 12 bookworm's regular security support ended 2026-07-12 and it is now
on community LTS only. Trixie (Debian 13) is current stable. Follows #131
(Go 1.26 + Node 24); together these refresh every pinned toolchain.

Two apt packages were RENAMED in trixie and would have failed the build:

- libpango1.0-0 -> libpango-1.0-0 (base image apt list)
- libgdk-pixbuf2.0-dev -> libgdk-pixbuf-2.0-dev (all six agent generators)

Note libpango1.0-dev and libcairo2-dev keep their old-style names in trixie
and are deliberately unchanged. All 26 apt-installed packages across the base
image and the six agent generators were verified present in trixie via
Debian's madison API, not spot-checked.

--pull is added to the BASE image build so debian:trixie-slim re-resolves to
the current digest on rebuild -- the same HF-incident Issue 3 rationale the
proxy build already uses. Deliberately NOT added to the agent, skills, or
per-project builds: those build FROM a LOCAL sandy-base image, and --pull
would attempt a registry fetch and fail.

Python 3.11 -> 3.13 has one user-visible consequence: PYTHONUSERBASE stores
packages under lib/python3.<minor>, so persistent 'pip install --user' trees
from the 3.11 image are invisible to 3.13. user-setup.sh now detects a
mismatched tree and prints a warn-only notice with the path and the cleanup
command -- matching the existing environment-drift warnings (broken .venv
symlink, foreign native modules), which likewise warn rather than auto-fix,
since silently deleting a user's installed packages is the wrong default.

SANDY_SANDBOX_MIN_COMPAT is deliberately NOT moved: the sandbox still works,
so this is not a compat-floor event (the 1.x forward-compat promise caps it at
1.0.0 anyway, guarded by run-tests.sh §60). Existing sandboxes need no
recreation. The .venv overlay is unaffected -- it uses uv-managed interpreters,
independent of the container's system python3. PEP 668 is already neutralized
via PIP_BREAK_SYSTEM_PACKAGES=1.

Docs synced: README, CLAUDE.md (incl. a new Automatic Environment Detection
bullet), SPECIFICATION.md (Appendix A verbatim copy + Appendix B table), and a
stale 'Debian bookworm' reference in docs/security/nono-roadmap.md.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
rappdw added a commit that referenced this pull request Aug 12, 2026
…136)

Base-image modernization plus two additive features and a LAN-allowlist fix.
Every pinned toolchain was on an unsupported or maintenance-only release:

- Debian 12 bookworm left regular security support 2026-07-12 -> trixie (13),
  which brings system Python 3.11 -> 3.13. (#133)
- Go 1.24 left its two-release support window entirely, and the pin was a
  Mar-2025 patch untouched for 16 months -> 1.26, with the newest patch now
  resolved at build time instead of frozen. The egress proxy's documented
  monthly --pull refresh had been silently no-op since ~Feb 2026 (no further
  1.24.x pushes existed), so it was refreshing Debian but not the Go stdlib
  implementing its TLS/HTTP/CONNECT I/O; pinning a supported minor restores
  it. Node 22 (Maintenance LTS) -> 24 (Active LTS). (#131)
- SANDY_EFFORT pins Claude Code reasoning effort and records it in the session
  marker, so a run's effort is provable rather than inferred. (#115)
- SANDY_SESSION_NONCE lets an operator pin the attestation nonce so a harness
  can prove a run is the one it launched; env-only, so a committed workspace
  config cannot set it. (#118)
- SANDY_ALLOW_LAN_HOSTS no longer reports success when iptables rejected the
  rule — a silently missing hole is the worst direction to be wrong in. (#119)

Additive minor per the CLAUDE.md semver rule: new keys, no retiering or
renames, introspection schema_version stays 1. SANDY_SANDBOX_MIN_COMPAT stays
0.7.10 — the Python bump moves where pip --user packages live, but the sandbox
still works, so this is not a compat-floor event and no sandbox needs
recreating.

Four user-visible upgrade consequences are documented at the top of
RELEASE_NOTES.md: the first launch rebuilds every image; persistent pip --user
packages become invisible to 3.13 (sandy now detects and reports the stale
tree); native Node addons may need npm rebuild; and binaries built inside sandy
now link glibc 2.41.

Also lands the test-suite work: three macOS-only failures CI structurally
cannot see (#134) — including §68 executing the real sandy binary via a
backtick inside a double-quoted python3 -c string — and per-section timing,
section selection, a fast-model pin, and an image warm-up preflight for the
integration suite (#135).

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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