Repository navigation
PER-17672, PER-17729: fix: backport the eMudhra CA distrust and PyJWT/GitPython floors to 0.9.x (#974, #976) - #978
Merged
EliMoshkovich merged 4 commits intoOct 22, 2026
Conversation
…ER-17672) (#974) * fix(docker): distrust the compromised eMudhra CAs (CVE-2026-111943) The private keys of eight eMudhra intermediate CAs are recoverable from their certificates, so anyone can mint a certificate for any host that a client trusting their roots accepts. The published opal-client and opal-server images still trust both roots (emSign ECC Root CA - G3 and, through a Sectigo cross-sign, AAA Certificate Services) in the system store and the first in certifi's bundle. - opal-common floors certifi at 2026.10.19, which drops emSign ECC Root CA - G3. - Both common stages upgrade ca-certificates (Debian 20250419~deb12u2 from bookworm-security, Alpine 20261020-r0); nothing upgraded it before. - scripts/check_trust_store.py, bind-mounted into those layers, fails the build if any trust store still holds either root. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * review: harden check_trust_store.py and test it - Deny all four roots the compromised certificates anchor at (roots.json on the disclosure site), adding emSign Root TLS CA - G3 and emSign Root CS CA - G3, which are only in Microsoft's store today. - A missing certificate directory is skipped instead of falling back to Path("") and scanning the working directory. - OpenSSL's TRUSTED CERTIFICATE blocks are parsed too. - Paths are resolved and each file read once. - docker_trust_store_check_test.py feeds it stores that do hold the roots (the image's never do) and checks each fingerprint against the roots' certificates. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * review: match compromised keys; no BuildKit requirement; check server stages - Deny by SubjectPublicKeyInfo SHA-256 (roots.json and impacted-intermediates.json), including the eight intermediates' keys, so a re-issued root or cross-sign for a denied key is caught. The key comes from a walk over the TBSCertificate fields; the tests check it against cryptography on every certifi root. - Read the legacy X509 CERTIFICATE label; an unparsable block fails the build naming its file. - COPY the script instead of RUN --mount, which made BuildKit a hard requirement for every target. It stays at /opal/check_trust_store.py, so a built image can be re-checked. - Run it after the server stages' own package installs too. - The docstring no longer claims to catch a cached layer: a cached layer was checked when it was built. - The test skips where the script is not next to the package. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * review: same gate hardening as the PDP copy (Zeev on permitio/PDP#415) - main() returns the gate's status; a subprocess test runs the script with SSL_CERT_FILE/SSL_CERT_DIR stores and checks 1 compromised / 0 clean. - Each failing store gets its fix (the certifi floor in opal-common, or a rebuild of the ca-certificates layer); a parse error names its block. - _element rejects BER indefinite and oversized lengths; spki() checks the walk ends on a SEQUENCE. - Tests for v1 certificates, no default CA file and the directory walk; docstrings for what find_distrusted() returns and when it exits. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * review: strict DER and SPKI shape check, as in the PDP copy (Zeev, permitio/PDP#415 round 2) - _element rejects high-tag-number tags, leading zero length octets and the long form for a short length; spki() checks SubjectPublicKeyInfo's shape, so a crafted or misaligned certificate fails instead of hashing other bytes. - Tests for each non-DER encoding, each length rule on its own and a walk ending on the wrong field. - SYSTEM_FIX points at what can still trip the check (a package or file still carrying a key), not at the upgrade the Dockerfile already does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test: pin the primitive high-tag form and each SPKI condition (parity with permitio/PDP#415) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * review (Zeev): declare the ca-certificates floor; accurate fix text and readers; sturdier tests - Alpine: `apk add 'ca-certificates>=20261020-r0' 'ca-certificates-bundle>=…'` instead of `apk upgrade`, so a stale index fails at the package step with apk's own error and /etc/apk/world keeps the floor. Debian: the installed version is checked right after `apt-get install` (apt has no `>=`), naming bookworm-security as the likely cause. - SYSTEM_FIX no longer asserts a premise the build didn't check, and points at /usr/share/ca-certificates and /etc/ca-certificates.conf for the generated bundle, which `dpkg -S` can't attribute. - The docstring names the real system-store readers: pygit2 via the stdlib's default paths, git's HTTPS through libcurl (GnuTLS on Debian), OPA/EOPA. - Tests: _key_hash returns None for a root cryptography refuses (six certifi roots have non-positive serials) and ignores only that deprecation warning; a moved script fails collection in a source checkout instead of skipping all 68 tests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * review (Zeev): the fix text names /usr/local/share/ca-certificates too update-ca-certificates also adds every certificate there, and that is where a later step or a derived image normally adds a CA. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> (cherry picked from commit 73f989f)
… published advisories (#976) * fix(deps): raise the PyJWT and GitPython floors past their published advisories PyJWT 2.14.0 and earlier have 14 advisories published 2026-09-29..10-05 (one critical, GHSA-ffc3-869f-jxw9); the last are fixed in 2.15.0, and 2.15.1 decodes '='-padded segments again. GitPython 3.1.61 and earlier have GHSA-59cr-6r3x-644w, fixed in 3.1.62. The old floors (pyjwt 2.1/2.4, gitpython 3.1.32) let any install resolve an affected version. The git-leak test seed image pinned GitPython 3.1.50 and requests 2.32.3, both with known advisories; bump them to the current releases. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * review: pin the PyJWT decode behaviours behind the floor; accurate floor comment The 2.15.1 floor changes what JWTVerifier accepts: a non-canonical signature segment is now rejected (GHSA-hxm8-2xgr-2p9m, since 2.14.0) and a '='-padded one is accepted (2.14.0 and 2.15.0 rejected it). The new test fails on 2.13.0 and on 2.15.0, so lowering the floor or a future PyJWT regression shows up in the suite. The requires.txt comment said 2.14.0 had 14 advisories; it has two (both medium), the other 12 including the critical one were fixed in 2.14.0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * review (Zeev): GitPython floor 3.2.1; pin the rejection reason in the test GitPython's repository advisories (not yet in the global GitHub database or OSV) affect 3.1.62: GHSA-f9j4-qggq-h239 (critical) and five more are fixed in 3.2.0, and GHSA-83vg-56qc-22m7 (high, CVE-2026-114145) affects <= 3.2.0 and is fixed in 3.2.1. JWTVerifier.verify maps every failure to Unauthorized, so the non-canonical test now asserts the "Could not decode access token" detail, and asserts that the tampered segment decodes to the same signature bytes instead of the vacuous tampered != token. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> (cherry picked from commit 62d8887)
EliMoshkovich
requested review from
zeevmoney
and
a balanced review from Copilot
October 22, 2026 19:07
release/0.9.x still tests Python 3.9, which master no longer does, and the test backported from #974 used PEP 604 unions (str | None) in annotations that 3.9 evaluates at import. typing.Optional instead, as elsewhere in opal-common. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
zeevmoney
approved these changes
Oct 22, 2026
zeevmoney
left a comment
Member
There was a problem hiding this comment.
Approved — no CRITICAL or HIGH issues found.
Non-blocking:
- LOW
packages/opal-common/opal_common/tests/emudhra_compromised_cas.pem:1— the compromised-certificates fixture is copied into every client and server image
Details are in the inline comments on each line.
`COPY ./packages` put the trust-store test's fixture, which holds the compromised eMudhra certificates, in every client and server image, where an image scanner looking for those certificates finds it. Nothing loads it as a trust anchor, and the wheels never had it. The fixture stays in git for the test; only the Docker build context drops it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
5 of 7 tasks
zeevmoney
approved these changes
Oct 22, 2026
zeevmoney
left a comment
Member
There was a problem hiding this comment.
Approved — no CRITICAL or HIGH issues found.
Still open from the previous review:
- LOW
packages/opal-common/opal_common/tests/emudhra_compromised_cas.pem:1— the fixture still ships in the images CI and the release job build: they use the Git build context, which ignores.dockerignore
Details are in the review thread on that line.
5 of 7 tasks
EliMoshkovich
added a commit
that referenced
this pull request
Oct 23, 2026
…to 0.9.x) (#980) Backport of #979. The trust-store test's fixture holds the compromised eMudhra certificates (CVE-2026-111943). #978's .dockerignore rule doesn't apply to the Git context CI and the release build from, so docker/Dockerfile now uses COPY --exclude=**/tests/*.pem (dockerfile frontend 1.19, pinned by digest), .dockerignore keeps the rule for local builds, and both E2E jobs fail if any .pem is under /opal/packages in the images. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes Issue
Backports #974 and #976 to the 0.9 line (pydantic v1), so that 0.9.10 carries them. The fixes are on master (the 0.10 line) since today, but 0.9.9 users and the PDP need a 0.9.x release.
certifi>=2026.10.19, upgradedca-certificatesin all four images, and the build-time trust-store check.pyjwt[crypto]>=2.15.1andgitpython>=3.2.1floors past their published advisories.Changes proposed
Two
git cherry-pick -xof the squash commits from master, in merge order. Both apply cleanly torelease/0.9.x:73f989fe(#974) and62d8887d(#976). One follow-up commit,b2c7b7a3, writes the trust-store test'sstr | None/bytes | Noneannotations asOptional[...], becauserelease/0.9.xstill tests Python 3.9 (master no longer does). A second,f59a8f71, addspackages/**/tests/*.pemto.dockerignore, so the trust-store test's certificate fixture is left out of the build context of path-context builds such asmake(the same change as #979 on master). There are no other changes; the two PRs' descriptions cover the details.Verification
Run locally on this branch's exact tree, with the published
0.9.9images as the baseline:0.9.9pip checkcleanclient,server,client-alpine,server-alpinewith--no-cachepdp-v2:0.9.16) with this branch's opal-common/opal-client installedCheck List (Check all the applicable boxes)
Description updated during review to match f59a8f7 (the Python 3.9 test fix and
.dockerignorecommits).🤖 Generated with Claude Code