Skip to content

op-stack-go: distrust the compromised eMudhra CAs in Go images - #561

Merged
alvarof2 merged 4 commits into
celo-rebase-19from
alvaro/op-stack-refresh-ca-bundle
Oct 29, 2026
Merged

alvarof2 merged 4 commits into
celo-rebase-19from
alvaro/op-stack-refresh-ca-bundle

Conversation

@alvarof2

@alvarof2 alvarof2 commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

The op-node, op-batcher, op-proposer and other alpine-based images accept certificates that chain to the eight eMudhra intermediates whose keys were compromised (keys recovered from weak generation; Mozilla removed emSign ECC Root CA - G3 from NSS on 2026-10-20, and Chrome removes it on 2026-10-27). Go does not check revocation. Until this is fixed, a MITM on any outbound TLS connection from these binaries, such as L1 RPC or the beacon API, can present a forged certificate and be accepted.

This PR applies two independent defences:

  • The trust store no longer contains the root. The digest pin on TARGET_BASE_IMAGE freezes ca-certificates-bundle at 20260909-r0, which still contains G3 (the deployed celo-v2.2.4 image has 20260413-r0). The alpine:3.23 tag still resolves to that digest today, so moving the pin is not enough. Alpine's 20261020 bundle (which removes G3, C1 and C3) is installed at build time with a >=20261020 version floor. If that version is not available the build fails, so an older bundle is never shipped, and a cached layer cannot keep a stale bundle. For a later root removal, raise the floor. The apk upgrade runs in a $BUILDPLATFORM stage, and only /etc/ssl/certs/ca-certificates.crt is copied into the shared target-base stage (/etc/ssl/cert.pem is a symlink to it). The release workflow builds linux/arm64 without QEMU, so running apk in a target-platform stage would fail there with exec format error. PR CI builds amd64 only and would not catch that. The bundle is architecture-independent. The image's apk database still lists the base image's ca-certificates-bundle version, so image scanners report the old version.
  • Go rejects the intermediates directly. Go 1.26.10 adds a crypto/x509 denylist of the eight intermediates (GO-2026-6809), which works even when a trust store is stale. Neither 1.25.14 nor 1.24.13 has it. The golang builder image sets GOTOOLCHAIN=local, so the go.mod toolchain line alone does not change the compiler used for the images (upstream security: Update go toolchain directive to v1.26.10 [SECURITY] ethereum-optimism/optimism#23546 changes only that line). The builder images and mise.toml are bumped as well.

There is no other effect on users. The cannon and kona prestate Dockerfiles stay on 1.26.5, because changing their Go version changes the reproducible prestate. op-challenger (Wolfi), cannon and op-deployer (dhi.io/alpine-base) runtime bases and ops/docker/deployment-utils are not changed. The pinned Wolfi base still ships ca-certificates-bundle-20260413-r0, which contains G3, C1 and C3, so op-challenger is protected only by the Go denylist. The cannon and op-deployer images use the unpinned dhi.io/alpine-base:3.23 tag, which currently (sha256:2049edbe…) ships a bundle without G3, C1 or C3.

A local op-node-target build gives a binary compiled with go1.26.10 that contains the denylist hashes, and the image has ca-certificates-bundle-20261020-r0 without emSign ECC Root CA - G3. None of the repo's review agents match this diff.

Related: celo-org/celo-kona#360 (the same fix for op-reth's compiled-in roots).

🤖 Generated with Claude Code

The digest-pinned alpine base freezes ca-certificates-bundle at 20260909,
which still trusts emSign ECC Root CA - G3. Its intermediates were
compromised (eMudhra, Oct 2026) and Mozilla removed the root from NSS on
2026-10-20. Go does not check revocation, so op-node, op-batcher and the
other alpine-based images keep accepting certificates forged under it
until their trust store changes.

Upgrade the bundle at build time in a shared target-base stage, so a
rebuild picks up root removals without waiting for a new base digest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-23T11:41:24.891202Z 377c068 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@karlb

karlb commented Oct 21, 2026

Copy link
Copy Markdown

There's also ethereum-optimism#23546, which hard-codes a blocklist of the eight compromised intermediate keys in crypto/x509. It probably makes sense to do both to be on the safe side.

Go 1.26.10 adds a crypto/x509 denylist of the eight compromised eMudhra
intermediates (GO-2026-6809), so Verify rejects chains through them even
when the system trust store still holds their root. Neither 1.25.14 nor
1.24.13 has it.

The golang builder image sets GOTOOLCHAIN=local, so the go.mod toolchain
line alone does not change the compiler used for the images; bump the
builder images too, and mise so local and CI tests use the same version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alvarof2 alvarof2 changed the title op-stack-go: refresh the CA bundle in alpine target images op-stack-go: distrust the compromised eMudhra CAs in Go images Oct 21, 2026
Comment thread ops/docker/op-stack-go/Dockerfile Resolved
Comment thread go.mod Resolved
apk upgrade does not guarantee a minimum version, and a cached build
layer would keep whatever bundle it first installed. A version floor
fails the build instead of shipping a bundle that still trusts the
compromised emSign root, and changes the layer's cache key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread ops/docker/op-stack-go/Dockerfile Outdated
Comment on lines +295 to +296
FROM $TARGET_BASE_IMAGE AS target-base
RUN n=0; until apk add --no-cache --upgrade 'ca-certificates-bundle>=20261020'; do n=$((n+1)); [ "$n" -ge 15 ] && exit 1; echo "apk add retry $n/15 in 20s" >&2; sleep 20; done

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This runs the arm64 base image's /bin/sh and apk. docker-publish-release.yaml builds linux/amd64,linux/arm64 without QEMU (Buildx lists only amd64/386 platforms in the v2.2.4 release log), so the arm64 leg will likely fail with exec format error. PR CI builds amd64 only, so it won't catch this.

Fix: add docker/setup-qemu-action@v3 before setup-buildx-action, or install the bundle in a --platform=$BUILDPLATFORM stage and COPY only ca-certificates.crt into target-base.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌

The release workflow cross-builds linux/arm64 without QEMU, so running
apk in the target-platform target-base stage would fail with
"exec format error" on the arm64 leg. PR CI builds amd64 only and would
not catch it.

Run the upgrade in a BUILDPLATFORM stage and copy only
/etc/ssl/certs/ca-certificates.crt into target-base. The bundle is
arch-independent and it is the only regular file the package ships;
/etc/ssl/cert.pem is a symlink to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alvarof2
alvarof2 merged commit 177e4ed into celo-rebase-19 Oct 29, 2026
52 checks passed
@alvarof2
alvarof2 deleted the alvaro/op-stack-refresh-ca-bundle branch October 29, 2026 08:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants