From 52192c294da4f81a9f40526169acc8b1f1eb1a68 Mon Sep 17 00:00:00 2001 From: Greg Anderson Date: Tue, 20 Oct 2026 18:28:20 -0600 Subject: [PATCH 1/2] fix(docker): upgrade ca-certificates in its own layer Release builds restore layers from the GitHub Actions cache, so the apt-get upgrade / apk upgrade layer can be reused from an older build and an OS trust-store fix never reaches the image. A separate upgrade step after those layers picks up the eMudhra fix (Debian 20250419+deb13u1, Alpine ca-certificates-bundle 20261020-r0). Co-Authored-By: Claude Opus 5.5 --- Dockerfile.django-alpine | 4 ++++ Dockerfile.django-debian | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/Dockerfile.django-alpine b/Dockerfile.django-alpine index 5235fa5ec8e..577429aa59c 100644 --- a/Dockerfile.django-alpine +++ b/Dockerfile.django-alpine @@ -57,6 +57,10 @@ RUN \ && \ rm -rf /var/cache/apk/* && \ true +# Upgrade the system trust store in its own layer. Release builds reuse the layer +# above from the build cache, so an OS security fix to ca-certificates (here the +# eMudhra key compromise, 2026-10-20) would otherwise never reach the image. +RUN apk upgrade --no-cache ca-certificates-bundle ca-certificates RUN \ --mount=from=build,src=/tmp/wheels,target=/tmp/wheels \ --mount=from=build,src=/app/requirements.txt,target=/app/requirements.txt \ diff --git a/Dockerfile.django-debian b/Dockerfile.django-debian index 62552b82cce..d67527b20ab 100644 --- a/Dockerfile.django-debian +++ b/Dockerfile.django-debian @@ -70,6 +70,15 @@ RUN \ apt-get clean && \ rm -rf /var/lib/apt/lists && \ true +# Upgrade the system trust store in its own layer. Release builds reuse the layer +# above from the build cache, so an OS security fix to ca-certificates (here the +# eMudhra key compromise, 2026-10-20) would otherwise never reach the image. +RUN \ + apt-get -y update && \ + apt-get -y install --no-install-recommends --only-upgrade ca-certificates && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists && \ + true RUN \ --mount=from=build,src=/tmp/wheels,target=/tmp/wheels \ --mount=from=build,src=/app/requirements.txt,target=/app/requirements.txt \ From 5c97b4d103ec643debe5be13e028aab171907693 Mon Sep 17 00:00:00 2001 From: Greg Anderson Date: Tue, 20 Oct 2026 19:12:58 -0600 Subject: [PATCH 2/2] fix(docker): fail the build if the distrusted emSign root is still trusted The trust-store refresh layer can itself come from the build cache on a later release. Add a CA_REFRESH build arg that the layer echoes, so bumping it forces a rebuild, and a check after the pip install that fails the build if emSign ECC Root CA - G3 is in the system bundle or certifi. Co-Authored-By: Claude Opus 5.5 --- Dockerfile.django-alpine | 7 ++++++- Dockerfile.django-debian | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Dockerfile.django-alpine b/Dockerfile.django-alpine index 577429aa59c..13db4877ca9 100644 --- a/Dockerfile.django-alpine +++ b/Dockerfile.django-alpine @@ -60,7 +60,9 @@ RUN \ # Upgrade the system trust store in its own layer. Release builds reuse the layer # above from the build cache, so an OS security fix to ca-certificates (here the # eMudhra key compromise, 2026-10-20) would otherwise never reach the image. -RUN apk upgrade --no-cache ca-certificates-bundle ca-certificates +# Bump CA_REFRESH to force this layer to rebuild for the next trust-store fix. +ARG CA_REFRESH=2026-10-20 +RUN echo "trust store refresh ${CA_REFRESH}" && apk upgrade --no-cache ca-certificates-bundle ca-certificates RUN \ --mount=from=build,src=/tmp/wheels,target=/tmp/wheels \ --mount=from=build,src=/app/requirements.txt,target=/app/requirements.txt \ @@ -69,6 +71,9 @@ RUN \ --no-index \ --find-links=/tmp/wheels \ -r ./requirements.txt +# Fail the build if either trust store still carries the root distrusted after the +# eMudhra key compromise (2026-10-20), so a cached or stale layer can't ship it. +RUN python3 -c "import ssl, sys, certifi; bad = [f for f in ('/etc/ssl/certs/ca-certificates.crt', certifi.where()) if (lambda ctx: ctx.load_verify_locations(cafile=f) or any(dict(x[0] for x in c['subject']).get('commonName') == 'emSign ECC Root CA - G3' for c in ctx.get_ca_certs()))(ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT))]; sys.exit('distrusted root emSign ECC Root CA - G3 still in: ' + ', '.join(bad) if bad else 0)" COPY \ docker/entrypoint-celery-beat.sh \ diff --git a/Dockerfile.django-debian b/Dockerfile.django-debian index d67527b20ab..6011dcb446d 100644 --- a/Dockerfile.django-debian +++ b/Dockerfile.django-debian @@ -73,7 +73,10 @@ RUN \ # Upgrade the system trust store in its own layer. Release builds reuse the layer # above from the build cache, so an OS security fix to ca-certificates (here the # eMudhra key compromise, 2026-10-20) would otherwise never reach the image. +# Bump CA_REFRESH to force this layer to rebuild for the next trust-store fix. +ARG CA_REFRESH=2026-10-20 RUN \ + echo "trust store refresh ${CA_REFRESH}" && \ apt-get -y update && \ apt-get -y install --no-install-recommends --only-upgrade ca-certificates && \ apt-get clean && \ @@ -88,6 +91,9 @@ RUN \ --find-links=/tmp/wheels \ -r ./requirements.txt && \ apt-get -y purge --auto-remove git +# Fail the build if either trust store still carries the root distrusted after the +# eMudhra key compromise (2026-10-20), so a cached or stale layer can't ship it. +RUN python3 -c "import ssl, sys, certifi; bad = [f for f in ('/etc/ssl/certs/ca-certificates.crt', certifi.where()) if (lambda ctx: ctx.load_verify_locations(cafile=f) or any(dict(x[0] for x in c['subject']).get('commonName') == 'emSign ECC Root CA - G3' for c in ctx.get_ca_certs()))(ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT))]; sys.exit('distrusted root emSign ECC Root CA - G3 still in: ' + ', '.join(bad) if bad else 0)" COPY \ docker/entrypoint-celery-beat.sh \