Skip to content

PER-17729: fix(deps): raise the PyJWT and GitPython floors past their published advisories - #976

Merged
EliMoshkovich merged 4 commits into
masterfrom
eli/per-17729-opal-calix-cve-report-on-opal-server-099-alpine-certifi
Oct 22, 2026
Merged

EliMoshkovich merged 4 commits into
masterfrom
eli/per-17729-opal-calix-cve-report-on-opal-server-099-alpine-certifi

Conversation

@EliMoshkovich

@EliMoshkovich EliMoshkovich commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Fixes Issue

Advisories published after 0.9.9 (2026-08-27) against the PyJWT and GitPython versions in the published images:

Package In opal-server:0.9.9-alpine Advisories First fixed
PyJWT 2.13.0 14, published 2026-09-29 to 10-05: 1 critical (GHSA-ffc3-869f-jxw9), 5 high, 8 medium 2.15.0
GitPython 3.1.60 GHSA-59cr-6r3x-644w, medium (fixed in 3.1.62); 6 more fixed in 3.2.0, incl. GHSA-f9j4-qggq-h239 (critical); GHSA-83vg-56qc-22m7 (high, CVE-2026-114145) fixed in 3.2.1 3.2.1

The 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-common and opal-server raise their floors in requires.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-server can land on an affected version.

  • app-tests/git-leak/seed/Dockerfile (the test seed container, not shipped) pinned GitPython==3.1.50 (26 advisories: 1 critical, 19 high) and requests==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.py pins 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.9 and GitPython 3.2.1 needs >=3.8.

PyJWT: OPAL signs and verifies with asymmetric keys through jwt.encode, jwt.decode and PyJWK. Across 9 algorithms and 8 key types, JWTSigner and JWTVerifier behave the same on 2.13.0 and 2.15.1, with one exception, the token encoding:

  • A non-canonical signature segment, one whose unused trailing base64url bits are set, is now rejected with a 401 (GHSA-hxm8-2xgr-2p9m, fixed in 2.14.0). Such a token decodes to the same signature as the canonical one, so a revocation list keyed on the raw token would miss it. PyJWT and other conforming issuers never produce one.
  • A signature segment with trailing = 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/push raise UnsafeOptionError for a remote name or refspec starting with - even with allow_unsafe_options=True, positional operands are protocol-checked, and kill_after_timeout kills the child's whole process tree. OPAL's call sites pass none of these. BranchTracker.pull() passes no refspec, checkout goes through repo.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:

Environment Result
master with the versions in 0.9.9 (pyjwt 2.13.0, gitpython 3.1.60) 1002 passed, 1 skipped (without the new test)
this branch at the floors, which are also the latest releases (pyjwt 2.15.1, gitpython 3.2.1) 1005 passed, 1 skipped

The built package metadata carries the new floors (Requires-Dist: pyjwt[crypto]<3,>=2.15.1, gitpython<4,>=3.2.1). The inline comments in requires.txt are stripped the same way the existing certifi one 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 library on server-alpine built locally from this branch merged with #974, compared with the published 0.9.9-alpine (unique package, version and advisory triples):

0.9.9-alpine this branch + #974
PyJWT / GitPython / certifi findings 17 (3 critical; certifi is counted twice, once for the copy pip vendors) 0
All Python-library findings 41 (3 critical, 17 high) 10 (0 critical, 7 high)

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, and opal-common caps ddtrace<4, so the fix is a major-version bump. It only matters when Datadog APM is enabled, which is off by default.
  • pip's vendored msgpack 1.1.2 and urllib3 2.7.0, plus setuptools 70.3.0 as 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 that pip install a custom fetch provider on top of ours.
  • The base image's setuptools 79.0.1 and its vendored jaraco.context 5.3.0 and wheel 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.
  • The image build pip-installs unpinned ranges, so each new advisory is fixed by raising a library floor, as this PR and fix(docker): distrust the compromised eMudhra CAs (CVE-2026-111943, PER-17672) #974 do. A constraints file for the image build, kept current by Dependabot, would keep scans clean without forcing new floors on everyone who installs opal-common.

Check List (Check all the applicable boxes)

  • I sign off on contributing this submission to open-source
  • My code follows the code style of this project.
  • My change requires changes to the documentation.
  • I have updated the documentation accordingly.
  • All new and existing tests passed.
  • This PR does not contain plagiarized content.
  • The title of my pull request is a short description of the requested changes.

🤖 Generated with Claude Code

…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>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 14:48
@linear-code

linear-code Bot commented Oct 22, 2026

Copy link
Copy Markdown

PER-17729

@netlify

netlify Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for opal-docs canceled.

Name Link
🔨 Latest commit bd7a0db
🔍 Latest deploy log https://app.netlify.com/projects/opal-docs/deploys/6ada569158a06b3a51f524d5

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…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>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 15:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@zeevmoney zeevmoney changed the title fix(deps): raise the PyJWT and GitPython floors past their published advisories PER-17729: fix(deps): raise the PyJWT and GitPython floors past their published advisories Oct 22, 2026

@zeevmoney zeevmoney left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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 in packages/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.

Comment thread packages/opal-common/requires.txt Outdated
… 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>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 15:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@zeevmoney zeevmoney left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approved — no issues found.

Comment thread packages/opal-common/requires.txt Outdated
Copilot AI balanced review requested due to automatic review settings October 22, 2026 18:31
@EliMoshkovich
EliMoshkovich merged commit 62d8887 into master Oct 22, 2026
15 checks passed
@EliMoshkovich
EliMoshkovich deleted the eli/per-17729-opal-calix-cve-report-on-opal-server-099-alpine-certifi branch October 22, 2026 18:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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>
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