Repository navigation
PER-17729: fix(deps): raise the PyJWT and GitPython floors past their published advisories - #976
Merged
EliMoshkovich merged 4 commits intoOct 22, 2026
Conversation
…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>
✅ Deploy Preview for opal-docs canceled.
|
Copilot stopped reviewing on behalf of
EliMoshkovich due to an error
October 22, 2026 14:58
…oor 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>
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:
- MEDIUM
packages/opal-common/requires.txt:4— GitPython floor 3.1.62 still admits versions under later GitPython advisories (same line inpackages/opal-server/requires.txt:3) - LOW
packages/opal-common/opal_common/authentication/tests/jwt_segment_encoding_test.py:73— rejection assertion passes for any verification failure
Details are in the inline comments on each line.
… 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>
zeevmoney
approved these changes
Oct 22, 2026
zeevmoney
left a comment
Member
There was a problem hiding this comment.
Approved — no issues found.
…l-server-099-alpine-certifi
EliMoshkovich
deleted the
eli/per-17729-opal-calix-cve-report-on-opal-server-099-alpine-certifi
branch
October 22, 2026 18:36
5 of 7 tasks
EliMoshkovich
added a commit
that referenced
this pull request
Oct 22, 2026
…/GitPython floors to 0.9.x (#974, #976) (#978) * fix(docker): distrust the compromised eMudhra CAs (CVE-2026-111943, PER-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) * PER-17729: fix(deps): raise the PyJWT and GitPython floors past their 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) * test: keep the trust-store test importable on Python 3.9 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> * build: keep test certificate fixtures out of the images `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> --------- Co-authored-by: Claude Opus 5.5 (1M context) <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
Advisories published after 0.9.9 (2026-08-27) against the PyJWT and GitPython versions in the published images:
opal-server:0.9.9-alpineThe third package in the same report,
certifi(GHSA-6q48-6c7w-9gw2), is fixed in #974. This PR doesn't touch it, and the two merge cleanly in either order.Changes proposed
opal-commonandopal-serverraise their floors inrequires.txt:pyjwt[crypto]>=2.15.1,<3, up from 2.4.0 (common) and 2.1.0 (server). 2.15.0 is the first release with none of the 14 advisories. 2.15.1 decodes=-padded segments again, which 2.15.0 rejected.gitpython>=3.2.1,<4, up from 3.1.32. Seven of GitPython's own repository advisories affect 3.1.62 or 3.2.0: 1 critical, 3 high, 3 medium. They are fixed in 3.2.0 and 3.2.1, and aren't yet in the global GitHub Advisory Database or OSV, so scanners don't report them yet.The image build installs these ranges unpinned, so a fresh build already resolves the latest releases. What the floors add is a guarantee: neither a stale build cache nor a downstream
pip install opal-servercan land on an affected version.app-tests/git-leak/seed/Dockerfile(the test seed container, not shipped) pinnedGitPython==3.1.50(26 advisories: 1 critical, 19 high) andrequests==2.32.3(2 medium). Both are bumped to the current releases, which have no OSV entries.New test
opal_common/authentication/tests/jwt_segment_encoding_test.pypins the two decode behaviours the PyJWT floor brings (below).Behaviour changes that come with the floors
Both floors fit
python_requires>=3.10: PyJWT 2.15.1 needs>=3.9and GitPython 3.2.1 needs>=3.8.PyJWT: OPAL signs and verifies with asymmetric keys through
jwt.encode,jwt.decodeandPyJWK. Across 9 algorithms and 8 key types,JWTSignerandJWTVerifierbehave the same on 2.13.0 and 2.15.1, with one exception, the token encoding:=padding, as AWS ALB issues, is accepted. 2.14.0 and 2.15.0 rejected it, so the floor is 2.15.1, not 2.15.0.The new test fails on 2.13.0 (non-canonical token accepted) and on 2.14.0 and 2.15.0 (padded token rejected), and passes on 2.15.1. So lowering the floor, or a future PyJWT that brings either back, fails the suite.
GitPython: 3.2.x adds safety checks:
Remote.pull/fetch/pushraiseUnsafeOptionErrorfor a remote name or refspec starting with-even withallow_unsafe_options=True, positional operands are protocol-checked, andkill_after_timeoutkills the child's whole process tree. OPAL's call sites pass none of these.BranchTracker.pull()passes no refspec, checkout goes throughrepo.git.checkout, and remotes are plain URLs. The full suite passes on 3.2.1. Downstream code that passes an option-shaped refspec to GitPython would now get an error.Verification
Tests: the full suite on Python 3.10, in three environments:
pyjwt 2.13.0,gitpython 3.1.60)pyjwt 2.15.1,gitpython 3.2.1)The built package metadata carries the new floors (
Requires-Dist: pyjwt[crypto]<3,>=2.15.1,gitpython<4,>=3.2.1). The inline comments inrequires.txtare stripped the same way the existingcertifione is.Seed image: it builds, and its GitPython flow (
Repo.init(initial_branch=...),index.commit,create_remote,push(refspec="main:main")) runs inside it against a local bare repo on GitPython 3.2.1. The git-leak suite itself wasn't run.Image scan:
trivy image --pkg-types libraryonserver-alpinebuilt locally from this branch merged with #974, compared with the published0.9.9-alpine(unique package, version and advisory triples):0.9.9-alpinecertifiis counted twice, once for the copy pip vendors)The other drops come from a fresh build picking up newer aiohttp, starlette and multidict, not from this diff.
Not in this PR, still reported on the rebuilt image:
ddtrace 3.19.9, CVE-2026-50271 (W3C baggage header parsing DoS, high). It is fixed only in 4.8.2, andopal-commoncapsddtrace<4, so the fix is a major-version bump. It only matters when Datadog APM is enabled, which is off by default.msgpack 1.1.2andurllib3 2.7.0, plussetuptools 70.3.0as recorded in pip's SBOM. These are pip's own copies, and even the latest pip (26.2.2) ships them. Removing pip would break images thatpip installa custom fetch provider on top of ours.setuptools 79.0.1and its vendoredjaraco.context 5.3.0andwheel 0.45.1. That's an image-policy change in the Dockerfile, which fix(docker): distrust the compromised eMudhra CAs (CVE-2026-111943, PER-17672) #974 is already editing.opal-common.Check List (Check all the applicable boxes)
🤖 Generated with Claude Code