pin the base image to a digest, with TTL refresh and audit labels
A tag is a moving pointer, and three things conspired to make the base immutable-in-practice while mutable-in-theory: image_hash() hashed the string "ubuntu:24.04" rather than what it resolves to, ensure_image() returns early when the tag exists, and podman build's pull policy is "missing" -- so even 'build --rebuild' rebuilt on the cached base. A Canonical patch simply never arrived, and nothing said so. Now the tag is resolved to a registry digest (skopeo, falling back to a pull unless AISB_BASE_PULL_FALLBACK=0), recorded per-ref under $AISB_CACHE_DIR/base/ at 0600, and used in FROM *and* in image_hash(): refreshing the base produces a new image instead of mutating one a sandbox may be running. Freshness is a TTL (AISB_BASE_TTL_DAYS=7, 0 every build, -1 never) plus an explicit 'update' / 'update --check'. Two rules keep it usable rather than merely strict. A stale pin beats a mutable tag: offline or with AISB_BASE_AUTO_UPDATE=0 the recorded digest is still used, and only a build with no digest at all is allowed -- announced as UNPINNED, labelled that way, warned about by doctor. And preview modes stay deterministic: --print-* never reach the network, inspect is the surface that asks the machine now. Both images carry aisb.base-digest / .node / .pi-package / .pi-version labels plus /etc/aisb/manifest.txt of resolved apt versions, which answers "which squid was enforcing this?" after the build log is gone. Tags are named after the base instead of the hardcoded u24- prefix. Bug found on the way: POL[base_digest]="$(base_pin ...)" silently produced unpinned labels, because an assignment inside a command substitution is discarded with the subshell. Helpers return text; callers assign. Also fixed: the manifest step used jq on npm's dependency objects as if they were strings, and a missing Node checksum used to skip verification quietly -- it now warns. 311 assertions pass (unit + e2e), shellcheck clean. LLM-Generated: true
This commit is contained in:
@@ -113,10 +113,42 @@ Files the agent writes land on your disk owned by **your** uid, not root
|
||||
|
||||
## Image
|
||||
|
||||
Built once per distinct policy from `ubuntu:24.04`, tagged by content hash, reused
|
||||
afterwards: git, curl, jq, build tools, python3, Node 22 (checksum-verified
|
||||
against nodejs.org) and the `pi` CLI. `--install PKG` adds packages and produces a
|
||||
different tag, so variants coexist. `--base-image` swaps the base.
|
||||
Both images come from one base, `docker.io/library/ubuntu:24.04` — including the gate,
|
||||
which is squid from Ubuntu's apt rather than a third-party image. Built once per
|
||||
distinct policy and tagged by content hash: git, curl, jq, build tools, python3, Node
|
||||
22 (tarball verified against nodejs.org `SHASUMS256.txt`) and the `pi` CLI. `--install
|
||||
PKG` adds packages and produces a different tag, so variants coexist, and
|
||||
`--base-image` swaps the base.
|
||||
|
||||
### The base is pinned by digest
|
||||
|
||||
A tag is a moving pointer: `FROM ubuntu:24.04` means "whatever Canonical points at
|
||||
this week", which makes a build unreproducible and a security patch invisible. So the
|
||||
tag is resolved to a digest, recorded under `~/.cache/ai-sandbox/base/`, and the
|
||||
digest goes into the `FROM` line *and* into the content hash — refreshing the base
|
||||
produces a new image instead of mutating one a sandbox may be running right now.
|
||||
|
||||
```bash
|
||||
ai-sandbox update --check # what upstream has, what you are pinned to, nothing built
|
||||
ai-sandbox update # re-resolve, rebuild sandbox + gate, keep the old image
|
||||
ai-sandbox build --refresh-base
|
||||
```
|
||||
|
||||
Freshness is a TTL: every `AISB_BASE_TTL_DAYS` (default 7) the digest is re-resolved,
|
||||
which is a manifest read (`skopeo`), not a download. Offline, or with
|
||||
`AISB_BASE_AUTO_UPDATE=0`, the recorded digest is kept — a stale pin beats a mutable
|
||||
one, and `AISB_BASE_TTL_DAYS=-1` pins until you say otherwise. A build with no digest
|
||||
is announced as `UNPINNED`, labelled that way, and warned about by `doctor`. Preview
|
||||
modes (`--dry-run`, `--print-*`) never touch the network, so they stay scriptable and
|
||||
report the pin you already have; `inspect` is the one that asks the machine now.
|
||||
|
||||
### What an image admits to being
|
||||
|
||||
`podman image inspect` on any ai-sandbox image shows `aisb.base-digest`, `aisb.node`,
|
||||
`aisb.pi-package`, `aisb.pi-version` (plus `aisb.role=gate` for the gate), and
|
||||
`/etc/aisb/manifest.txt` inside it lists every resolved apt package version — the
|
||||
answer to "which squid was doing the filtering" once the build log is long gone. The
|
||||
previous image is always kept after an update, so rollback is a tag away.
|
||||
|
||||
## Inspecting a run
|
||||
|
||||
|
||||
Reference in New Issue
Block a user