Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0726e-5232-73f2-aaca-2c05ceb62efb
8.5 KiB
Image scan — 2026-09-06
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.
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
| Image | CRITICAL | HIGH | MEDIUM |
|---|---|---|---|
As pinned before this session (python:3.12-slim@sha256:d764629c…, Debian 13.5) |
3 | 81 | 103 |
slim-hardened — base bumped to @sha256:78387bc3… (Debian 13.6), pip removed |
3 | 51 | 56 |
alpine — python:3.12-alpine@sha256:b64631e0… (Alpine 3.24.1), pip removed |
0 | 7 | 1 |
Every finding is inherited from the base image or its distro packages. None is in
approval_engine code, and after removing pip the Python dependency layer
(cryptography 50.0.1, PyJWT 2.13.0, waitress 3.0.2, cffi, pycparser)
reports zero findings at MEDIUM and above in all three.
Two fixes applied
The base pin was stale. Containerfile pinned a Debian 13.5 build of
python:3.12-slim; current upstream is Debian 13.6. Bumping the pin removes 30
HIGH and 47 MEDIUM findings on its own. The pin is still a digest, not a tag —
the fix is a newer immutable pin, not a floating one.
pip is gone from the runtime image. All 10 MEDIUM Python findings were in
pip itself, which is a build-time tool with no business in a running approval
service. The build is now two-stage: pip installs the venv in the build stage
and is deleted from both the venv and the base's /usr/local in the runtime
stage. command -v pip pip3 pip3.12 returns nothing in both variants.
The three CRITICALs, and why the base choice is now open
The remaining CRITICALs on Debian are all perl-base 5.40.1-6:
CVE-2026-13221CVE-2026-42496CVE-2026-8376
All three have no upstream fix (trivy reports no fixed version), so no base
bump or package upgrade clears them. perl-base is Essential: yes on Debian
and is not safely removable. This service does not use perl at any point.
Alpine carries no perl at all, which is why it reports 0 CRITICAL. That makes the base a real decision rather than a preference:
- Alpine clears all three CRITICALs and drops HIGH from 51 to 7, but changes the C library from glibc to musl.
- Debian slim keeps glibc and ships three unfixable CRITICALs in a package the service never calls.
Evidence that Alpine is viable
- Image builds with no compiler toolchain:
cryptography50.0.1 installs from musllinux wheels. - The full test suite passes on musl: 111 passed, the same count as the
workstation. (An earlier run showed 102 passed / 1 skipped; the 9-test gap was
tests/test_examples.pyskipping on a missingjsonschematest dependency in the throwaway container, not a musl failure.) - Runtime identity
uid=10001(approval) gid=10001(approval), non-root. - Carries
LATEST_SCHEMA_VERSION = 3andEnginetenant defaulttenant:platform. - First-install migration on a fresh volume:
migratethenverifyboth reportschema_version: 3,schema_current: true,integrity: ["ok"],foreign_key_violations: 0,persistent: true. - Fail-closed gates hold in the image:
serve --production --db :memory:refuses with "production requires a persistent database"; production without audit configuration refuses with "production requires authenticated audit delivery".
Containerfile.alpine holds this variant. It is a candidate, not the
sanctioned base — the sanctioned base is still Containerfile.
Why nothing was released
glas-harness asked for a scanned immutable image and said to deploy only when
identity and audit requirements pass. The scan gate is not met by the Debian
variant, and pushing it would bake three unfixable CRITICALs into a release
digest that the manifest then pins by hash.
Choosing the runtime C library for a production approval service is also not a call this repo should make silently. So:
- No push to
forgejo.coulomb.social. deploy/approval-engine.yamlstill carriesREPLACE_WITH_RELEASE_DIGESTon both image references.APPROVAL-WP-0002-T03stayswait.
Local build digests, recorded for traceability and explicitly not release
digests: slim-hardened sha256:78b87e68e520…, alpine sha256:736647294706….
Both open questions were answered
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.yamlstill carriesREPLACE_WITH_RELEASE_DIGESTon 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-T03stayswait.
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.
Registry publication — 2026-09-06, Glas deployment session
The previously blocked push succeeded under the operator's standing production
deployment authorization. Registry tag 0.1.0 resolves to OCI index digest
sha256:73333f5ceb55e48192e3095cb2e2a741cdc6ff0be2f18128301072b4a6b6eb9d.
The linux/amd64 image manifest is
sha256:2064d5378e01801ecf9966ccec9c93aadd16d6b404ef4890983d89fa738c164c.
Both deployment images now pin the registry index digest, verified remotely
following publication. Its equality to the earlier local image identifier is
not the evidence of publication; the successful push and remote index lookup are.
No StatefulSet rollout yet: live KeyCape registrations and audit sender/token admission remain outstanding. APPROVAL-WP-0002-T03 stays wait for those gates.