approval-engine/Containerfile

60 lines
2.5 KiB
Text
Raw Normal View History

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
2026-09-06 23:01:41 +02:00
# Sanctioned base — musl/Alpine, adopted 2026-09-06.
#
# Alpine carries no perl, which is where the Debian slim variant's three
# unfixable CRITICALs lived (CVE-2026-13221, CVE-2026-42496, CVE-2026-8376 in
# perl-base, no upstream fix). Debian slim scanned 3 CRITICAL / 51 HIGH / 56
# MEDIUM against this image's 0 / 7 / 1.
#
# pip is deliberately absent from the runtime stage: it is a build-time tool,
# it held every Python-layer finding, and a running approval service has no
# business installing packages. Do not add it back.
#
# Base is pinned by digest, never a tag. See docs/image-scan-2026-09-06.md.
FROM python:3.12-alpine@sha256:b64631e04e4920160c50fbe8d8df828f7f35f06f425cb44aa09bca53e708a35a AS build
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH=/opt/venv/bin:$PATH
Run the scan gate; harden the image and stop short of release glas-harness asked for a scanned immutable image. The scan ran, and it failed on the base we had sanctioned, so no image was pushed. Every finding is inherited from the base image or its distro packages; none is in approval_engine code. Two fixes applied here: The base pin was stale. It named a Debian 13.5 build of python:3.12-slim while upstream is 13.6. Bumping the digest removes 30 HIGH and 47 MEDIUM on its own. Still a digest, not a floating tag. pip is gone from the runtime image. All 10 MEDIUM Python findings were in pip itself, a build-time tool with no business in a running approval service. The build is now two-stage, and pip is removed from both the venv and the base's /usr/local, so command -v pip returns nothing. What remains is a decision rather than a task. Three CRITICALs persist on Debian, all perl-base (CVE-2026-13221, CVE-2026-42496, CVE-2026-8376), none with an upstream fix, in a package this service never invokes and that Debian marks Essential. An Alpine variant carries no perl and scans 0 CRITICAL / 7 HIGH / 1 MEDIUM against Debian's 3 / 51 / 56. Alpine is proven viable rather than asserted: musl wheels resolve with no toolchain, the full suite passes on musl at 111 tests, and non-root uid 10001, schema v3, tenant:platform, fresh-store migrate/verify and both production fail-closed gates all hold in the built image. It is parked in Containerfile.alpine as a candidate; Containerfile remains sanctioned. Nothing was released. Pushing the Debian variant would pin three unfixable CRITICALs into a release digest, and choosing the runtime C library for this service is not a call to make silently. The manifest still carries REPLACE_WITH_RELEASE_DIGEST. Full record in docs/image-scan-2026-09-06.md. 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
2026-09-06 22:52:20 +02:00
RUN python -m venv /opt/venv
WORKDIR /app
COPY pyproject.toml README.md ./
COPY approval_engine ./approval_engine
Run the scan gate; harden the image and stop short of release glas-harness asked for a scanned immutable image. The scan ran, and it failed on the base we had sanctioned, so no image was pushed. Every finding is inherited from the base image or its distro packages; none is in approval_engine code. Two fixes applied here: The base pin was stale. It named a Debian 13.5 build of python:3.12-slim while upstream is 13.6. Bumping the digest removes 30 HIGH and 47 MEDIUM on its own. Still a digest, not a floating tag. pip is gone from the runtime image. All 10 MEDIUM Python findings were in pip itself, a build-time tool with no business in a running approval service. The build is now two-stage, and pip is removed from both the venv and the base's /usr/local, so command -v pip returns nothing. What remains is a decision rather than a task. Three CRITICALs persist on Debian, all perl-base (CVE-2026-13221, CVE-2026-42496, CVE-2026-8376), none with an upstream fix, in a package this service never invokes and that Debian marks Essential. An Alpine variant carries no perl and scans 0 CRITICAL / 7 HIGH / 1 MEDIUM against Debian's 3 / 51 / 56. Alpine is proven viable rather than asserted: musl wheels resolve with no toolchain, the full suite passes on musl at 111 tests, and non-root uid 10001, schema v3, tenant:platform, fresh-store migrate/verify and both production fail-closed gates all hold in the built image. It is parked in Containerfile.alpine as a candidate; Containerfile remains sanctioned. Nothing was released. Pushing the Debian variant would pin three unfixable CRITICALs into a release digest, and choosing the runtime C library for this service is not a call to make silently. The manifest still carries REPLACE_WITH_RELEASE_DIGEST. Full record in docs/image-scan-2026-09-06.md. 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
2026-09-06 22:52:20 +02:00
RUN pip install --no-cache-dir '.[serve]' \
&& rm -rf /opt/venv/bin/pip /opt/venv/bin/pip3 /opt/venv/bin/pip3.12 \
/opt/venv/lib/python3.12/site-packages/pip \
/opt/venv/lib/python3.12/site-packages/pip-*.dist-info \
/opt/venv/lib/python3.12/site-packages/setuptools \
/opt/venv/lib/python3.12/site-packages/setuptools-*.dist-info \
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
2026-09-06 23:01:41 +02:00
/opt/venv/lib/python3.12/site-packages/pkg_resources
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
2026-09-06 23:01:41 +02:00
FROM python:3.12-alpine@sha256:b64631e04e4920160c50fbe8d8df828f7f35f06f425cb44aa09bca53e708a35a
Run the scan gate; harden the image and stop short of release glas-harness asked for a scanned immutable image. The scan ran, and it failed on the base we had sanctioned, so no image was pushed. Every finding is inherited from the base image or its distro packages; none is in approval_engine code. Two fixes applied here: The base pin was stale. It named a Debian 13.5 build of python:3.12-slim while upstream is 13.6. Bumping the digest removes 30 HIGH and 47 MEDIUM on its own. Still a digest, not a floating tag. pip is gone from the runtime image. All 10 MEDIUM Python findings were in pip itself, a build-time tool with no business in a running approval service. The build is now two-stage, and pip is removed from both the venv and the base's /usr/local, so command -v pip returns nothing. What remains is a decision rather than a task. Three CRITICALs persist on Debian, all perl-base (CVE-2026-13221, CVE-2026-42496, CVE-2026-8376), none with an upstream fix, in a package this service never invokes and that Debian marks Essential. An Alpine variant carries no perl and scans 0 CRITICAL / 7 HIGH / 1 MEDIUM against Debian's 3 / 51 / 56. Alpine is proven viable rather than asserted: musl wheels resolve with no toolchain, the full suite passes on musl at 111 tests, and non-root uid 10001, schema v3, tenant:platform, fresh-store migrate/verify and both production fail-closed gates all hold in the built image. It is parked in Containerfile.alpine as a candidate; Containerfile remains sanctioned. Nothing was released. Pushing the Debian variant would pin three unfixable CRITICALs into a release digest, and choosing the runtime C library for this service is not a call to make silently. The manifest still carries REPLACE_WITH_RELEASE_DIGEST. Full record in docs/image-scan-2026-09-06.md. 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
2026-09-06 22:52:20 +02:00
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH=/opt/venv/bin:$PATH
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
2026-09-06 23:01:41 +02:00
# libuuid is patched explicitly rather than left to the base's pinned version:
# the pinned Alpine 3.24.1 ships 2.42.1-r0, which carries every remaining HIGH
# finding, all fixed in 2.42.3. The floor is a minimum version, not a floating
# upgrade, so the build stays reproducible while refusing a vulnerable libuuid.
RUN apk add --no-cache 'libuuid>=2.42.3-r1' \
&& addgroup -S -g 10001 approval \
&& adduser -S -u 10001 -G approval -H approval \
Run the scan gate; harden the image and stop short of release glas-harness asked for a scanned immutable image. The scan ran, and it failed on the base we had sanctioned, so no image was pushed. Every finding is inherited from the base image or its distro packages; none is in approval_engine code. Two fixes applied here: The base pin was stale. It named a Debian 13.5 build of python:3.12-slim while upstream is 13.6. Bumping the digest removes 30 HIGH and 47 MEDIUM on its own. Still a digest, not a floating tag. pip is gone from the runtime image. All 10 MEDIUM Python findings were in pip itself, a build-time tool with no business in a running approval service. The build is now two-stage, and pip is removed from both the venv and the base's /usr/local, so command -v pip returns nothing. What remains is a decision rather than a task. Three CRITICALs persist on Debian, all perl-base (CVE-2026-13221, CVE-2026-42496, CVE-2026-8376), none with an upstream fix, in a package this service never invokes and that Debian marks Essential. An Alpine variant carries no perl and scans 0 CRITICAL / 7 HIGH / 1 MEDIUM against Debian's 3 / 51 / 56. Alpine is proven viable rather than asserted: musl wheels resolve with no toolchain, the full suite passes on musl at 111 tests, and non-root uid 10001, schema v3, tenant:platform, fresh-store migrate/verify and both production fail-closed gates all hold in the built image. It is parked in Containerfile.alpine as a candidate; Containerfile remains sanctioned. Nothing was released. Pushing the Debian variant would pin three unfixable CRITICALs into a release digest, and choosing the runtime C library for this service is not a call to make silently. The manifest still carries REPLACE_WITH_RELEASE_DIGEST. Full record in docs/image-scan-2026-09-06.md. 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
2026-09-06 22:52:20 +02:00
&& rm -rf /usr/local/bin/pip /usr/local/bin/pip3 /usr/local/bin/pip3.12 \
/usr/local/lib/python3.12/site-packages/pip \
/usr/local/lib/python3.12/site-packages/pip-*.dist-info \
/usr/local/lib/python3.12/site-packages/setuptools \
/usr/local/lib/python3.12/site-packages/setuptools-*.dist-info \
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
2026-09-06 23:01:41 +02:00
/usr/local/lib/python3.12/site-packages/pkg_resources
Run the scan gate; harden the image and stop short of release glas-harness asked for a scanned immutable image. The scan ran, and it failed on the base we had sanctioned, so no image was pushed. Every finding is inherited from the base image or its distro packages; none is in approval_engine code. Two fixes applied here: The base pin was stale. It named a Debian 13.5 build of python:3.12-slim while upstream is 13.6. Bumping the digest removes 30 HIGH and 47 MEDIUM on its own. Still a digest, not a floating tag. pip is gone from the runtime image. All 10 MEDIUM Python findings were in pip itself, a build-time tool with no business in a running approval service. The build is now two-stage, and pip is removed from both the venv and the base's /usr/local, so command -v pip returns nothing. What remains is a decision rather than a task. Three CRITICALs persist on Debian, all perl-base (CVE-2026-13221, CVE-2026-42496, CVE-2026-8376), none with an upstream fix, in a package this service never invokes and that Debian marks Essential. An Alpine variant carries no perl and scans 0 CRITICAL / 7 HIGH / 1 MEDIUM against Debian's 3 / 51 / 56. Alpine is proven viable rather than asserted: musl wheels resolve with no toolchain, the full suite passes on musl at 111 tests, and non-root uid 10001, schema v3, tenant:platform, fresh-store migrate/verify and both production fail-closed gates all hold in the built image. It is parked in Containerfile.alpine as a candidate; Containerfile remains sanctioned. Nothing was released. Pushing the Debian variant would pin three unfixable CRITICALs into a release digest, and choosing the runtime C library for this service is not a call to make silently. The manifest still carries REPLACE_WITH_RELEASE_DIGEST. Full record in docs/image-scan-2026-09-06.md. 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
2026-09-06 22:52:20 +02:00
COPY --from=build /opt/venv /opt/venv
WORKDIR /app
USER 10001:10001
EXPOSE 8080
ENTRYPOINT ["approval-engine"]
CMD ["serve", "--help"]