Adopt Alpine as the sanctioned base; release candidate scans clean

Operator adopted Alpine/musl as the base and trivy as the scanner.
Containerfile.alpine is promoted to Containerfile and the Debian slim variant
is retired rather than kept as an option -- it shipped three perl-base
CRITICALs with no upstream fix, in a package this service never invokes.

Promoting Alpine left 7 HIGH and 1 MEDIUM, all libuuid 2.42.1-r0 as shipped by
the pinned Alpine 3.24.1, and all with fixes in 2.42.3. The runtime stage now
requires libuuid>=2.42.3-r1. That is a version floor, not a floating upgrade:
the base stays digest-pinned and reproducible, and a vulnerable libuuid fails
the build instead of shipping.

The candidate scans 0 CRITICAL / 0 HIGH / 0 MEDIUM / 0 LOW. Verified on that
exact artifact: non-root uid 10001, pip absent, schema v3, tenant default
tenant:platform, fresh-store migrate and verify clean, restart persistence via
re-verify on the same volume, both production fail-closed refusals, 111 tests
on musl, kubectl dry-run passing.

The gate is now reproducible instead of a one-off. make image-scan fails on any
CRITICAL or HIGH, and make image-release runs build then scan then push, so a
failing scan blocks the push by construction rather than by whoever remembers
to look.

Not released. docker push was attempted and refused by this session's sandbox
as an outward-facing publish, and was not worked around. No release digest
exists, so the manifest deliberately keeps REPLACE_WITH_RELEASE_DIGEST -- it
must be pinned to the registry manifest digest, never the tag and never the
local image id. T03 stays wait on that push plus the still-unmaterialized
KeyCape registrations and audit sender credential.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 715850@bnt-lap001
Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
This commit is contained in:
tegwick 2026-09-06 23:01:41 +02:00
parent f88a92fb37
commit d7a9fe53db
5 changed files with 154 additions and 75 deletions

View file

@ -1,9 +1,23 @@
# Image scan — 2026-09-06
Scanner: `aquasec/trivy:latest` (`--scanners vuln`), run against locally built
images. Requested by `glas-harness` under `GLAS-WP-0015` / `APPROVAL-WP-0002-T03`.
Scanner: **trivy** (`aquasec/trivy:latest`, `--scanners vuln`) — sanctioned by
the operator 2026-09-06. Requested by `glas-harness` under `GLAS-WP-0015` /
`APPROVAL-WP-0002-T03`.
**No image was pushed.** The scan is why — see "Why nothing was released".
## Outcome
**Base decision: Alpine/musl, adopted 2026-09-06** by operator, alongside the
scanner. `Containerfile` is now the Alpine build; the Debian slim variant is
retired rather than kept as an alternative.
**The release candidate scans completely clean: 0 CRITICAL, 0 HIGH, 0 MEDIUM,
0 LOW.**
It has **not** been pushed. The registry push is the one remaining step and it
requires an action this session was not permitted to take — see "Release status"
at the end.
The history below is kept because it records why the base changed.
## Results
@ -91,13 +105,70 @@ call this repo should make silently. So:
Local build digests, recorded for traceability and explicitly **not** release
digests: `slim-hardened` `sha256:78b87e68e520…`, `alpine` `sha256:736647294706…`.
## Open questions for owners
## Both open questions were answered
1. Is Alpine/musl acceptable as the sanctioned base for this service? If yes,
`Containerfile.alpine` becomes `Containerfile` and the release is scanned
clean of CRITICALs.
2. If glibc is required, is a documented exception for the three unfixable
`perl-base` CVEs acceptable, given the service never invokes perl? A
distroless glibc base is the third option and was not evaluated here.
3. Which scanner is sanctioned for the gate? This used trivy because it needed
no install; the estate may have a different standard.
The operator accepted Alpine/musl as the sanctioned base and trivy as the
sanctioned scanner (2026-09-06). `Containerfile.alpine` was promoted to
`Containerfile`; the Debian variant is retired. The distroless option was never
evaluated and is now moot.
## Clearing the last findings — libuuid
Promoting Alpine left 7 HIGH and 1 MEDIUM, all in `libuuid` 2.42.1-r0 as shipped
by the pinned Alpine 3.24.1, and **all with fixes available** in 2.42.3.
The runtime stage now installs `libuuid>=2.42.3-r1`. That is a version *floor*,
not a floating upgrade: the base stays digest-pinned, the build stays
reproducible, and a vulnerable libuuid fails the build rather than shipping.
After that patch the image scans clean at every severity including LOW.
## Release candidate
Tag `forgejo.coulomb.social/coulomb/approval-engine:0.1.0`, local image id
`sha256:73333f5ceb55e48192e3095cb2e2a741cdc6ff0be2f18128301072b4a6b6eb9d`.
**This is a local image id, not the release digest.** The release digest is the
registry manifest digest and does not exist until the image is pushed.
Verified on this exact artifact:
| Gate | Result |
| --- | --- |
| Vulnerability scan (trivy, all severities) | 0 findings |
| Runtime identity | `uid=10001(approval) gid=10001(approval)`, non-root |
| pip present | no — `command -v pip pip3` returns nothing |
| Schema | `LATEST_SCHEMA_VERSION = 3` |
| Store tenant default | `tenant:platform` |
| First install, no prior DB | `migrate` then `verify`: schema 3, `schema_current`, `integrity ["ok"]`, 0 FK violations, persistent |
| Restart persistence | re-`verify` on the same volume: `ok=True`, schema 3, integrity ok |
| Production without persistent DB | refused — "production requires a persistent database" |
| Production without audit config | refused — "production requires authenticated audit delivery" |
| Full test suite on musl | 111 passed |
| Manifest inputs | `kubectl apply --dry-run=client` passes |
## Release status — blocked on one permitted action
`docker push forgejo.coulomb.social/coulomb/approval-engine:0.1.0` was attempted
and **refused by this session's sandbox** as an outward-facing publish. It was
not retried or worked around.
Consequently:
- No release digest exists yet.
- `deploy/approval-engine.yaml` still carries `REPLACE_WITH_RELEASE_DIGEST` on
both image references. It must be pinned to the registry manifest digest
returned by the push, never to the tag and never to the local image id above.
- `APPROVAL-WP-0002-T03` stays `wait`.
To finish, an operator runs the push and returns the digest, or re-runs it in a
session permitted to publish:
```
docker push forgejo.coulomb.social/coulomb/approval-engine:0.1.0
docker inspect --format '{{index .RepoDigests 0}}' \
forgejo.coulomb.social/coulomb/approval-engine:0.1.0
```
Then pin both `image:` references in `deploy/approval-engine.yaml` to that
digest. Rollout, restart and restore evidence remain gated on the KeyCape
registrations and the audit sender credential, which are still unmaterialized.