Skip to content

CI: enforce patched Python floor in setup-python and simplify harden-pip - #3259

Closed
juenglin wants to merge 9 commits into
NVIDIA:mainfrom
juenglin:ci-check-python-patched
Closed

juenglin wants to merge 9 commits into
NVIDIA:mainfrom
juenglin:ci-check-python-patched

Conversation

@juenglin

@juenglin juenglin commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Description

Refs #3195

The CPython releases 3.15.2, 3.14.10, 3.13.18, 3.12.16 and 3.11.18 (announcement, CVE-2026-115557) patch the ssl module to reject the compromised eMudhra/emSign intermediate keys. With the runner OS trust stores now confirmed fixed fleet-wide and pip 26.2.2 using truststore, the remaining exposure is any job that runs an unpatched interpreter: its ssl module silently keeps trusting the compromised keys, and nothing in CI notices today.

This PR closes that gap with three changes.

Enforce patched Python floors in setup-python

Every single-version setup-python step that feeds harden-pip now uses a tilde-range floor spec, so the tool cache can never resolve to a pre-fix patch release. Jobs that previously ran harden-pip on the system interpreter get a floored setup-python step:

Workflow (job) Before After
pre-commit.yml 3.14 ~3.14.10
coverage.yml (4 jobs) 3.14 ~3.14.10 via new PY_VER_FLOOR
release.yml (verify-testpypi, verify-pypi) system python3 new step, ~3.12.16
ci-nightly.yml (test-ci-tools-for-release) system python new step, ~3.12.16
test-sdist-linux.yml 3.12 ~3.12.16
test-sdist-windows.yml 3.12 ~3.12.16
build-wheel.yml (cibuildwheel host) 3.12 ~3.12.16

release.yml's check-release-notes job does not call harden-pip, but its setup-python is floored to ~3.12.16 as well for consistency.

setup-python with a tilde-range floor falls back to the manifest when the cached patch does not satisfy the floor, so no check-latest override is needed. In pre-commit.yml, PYTHON_VERSION: "3.14" stays minor-only because it is the cache restore-key prefix; in coverage.yml, PY_VER: "3.14" stays minor-only because it is also passed to ci/tools/env-vars. The floor is supplied to python-version separately in both. The release.yml verify jobs share a YAML anchor, so one new step covers both, and python3 -m venv builds the verification venv from the floored interpreter because setup-python puts its shim first on PATH.

Resolve the latest patch in the matrix jobs

The matrix jobs in test-wheel-linux.yml, test-wheel-windows.yml and build-wheel.yml run setup-python with matrix.PY_VER across several minor series (3.11 through 3.15t), each with a different first-fixed patch, so a single floor spec cannot cover them. These steps set check-latest: true instead, so setup-python resolves the newest patch from the manifest rather than a stale tool-cache entry. Every series in the matrix has a fixed release, so the newest patch always carries the fix.

Simplify harden-pip

Now that setup-python enforces the floor, the hash-pinned pip bootstrap is no longer needed: a plain pip install -U pip runs on an ssl module that rejects any forged certificate, so the download is safe, and it always picks up the latest pip (which uses truststore by default). The action is reduced to that single step, and its description documents the floor-spec convention that call sites must follow.

Checklist

  • New or existing tests cover these changes. (CI exercises every floored setup-python and harden-pip step; the floor spec itself is the enforcement mechanism.)
  • The documentation is up to date with these changes.

Print sys.version in the harden-pip action and fail when the interpreter
is older than the first release of its minor series that rejects the
compromised eMudhra/emSign intermediate CA keys.
@juenglin juenglin added this to the cuda.bindings next milestone Oct 27, 2026
@juenglin juenglin added the CI/CD CI/CD infrastructure label Oct 27, 2026
@juenglin juenglin self-assigned this Oct 27, 2026
@copy-pr-bot

copy-pr-bot Bot commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Now that check_python_patched.py confirms the interpreter carries the
ssl-module fix (CVE-2026-115557), the hash-pinned pip download is no
longer needed for security: Python's own ssl rejects any MITM'd cert,
so a plain 'pip install -U pip' is safe.

Replace the pinned-URL step with 'pip install -U pip' and update the
action description accordingly. The python input and all 13 call sites
are unchanged.
@juenglin juenglin changed the title CI: fail jobs that run an unpatched CPython interpreter CI: guard against unpatched CPython and simplify harden-pip Oct 28, 2026
The harden-pip interpreter guard rejected the pre-commit job's Python
3.14.9: with a minor-only spec and the default check-latest: false,
setup-python reused the hosted image's cached 3.14.9, which predates the
eMudhra ssl fix (3.14.10). Set check-latest: true so it resolves the
patched patch release from the manifest. Cache-key prefix logic is
unchanged (still keyed on the resolved version / PYTHON_VERSION minor).
With the setup-python call sites floored to the first patched release of
each minor series, the ssl module is guaranteed clean before harden-pip
runs, making the inline runtime check (check_python_patched.py) and its
unit test redundant. Drop both, revert the ruff.toml T201 exception that
accompanied them, and replace the check-latest workaround with explicit
tilde-range floor specs.

  pre-commit       3.14  ->  ~3.14.10
  release          3.12  ->  ~3.12.16
  test-sdist-linux 3.12  ->  ~3.12.16
  test-sdist-win   3.12  ->  ~3.12.16
  build-wheel:112  3.12  ->  ~3.12.16  (cibuildwheel host python)

setup-python resolves a tilde-range against the manifest even when a
stale cached patch does not satisfy the floor, so no check-latest is
needed. CVE-2026-115557.
@juenglin juenglin changed the title CI: guard against unpatched CPython and simplify harden-pip CI: enforce patched Python floor in setup-python and simplify harden-pip Oct 28, 2026
Cover the sites the tilde-range sweep missed:

- release.yml verify jobs (shared anchor): add setup-python ~3.12.16
  before the venv is created, so python3 -m venv inherits a patched ssl
  module. These jobs have no checkout and build the venv from the system
  python3 otherwise.
- ci-nightly.yml test-ci-tools-for-release: add setup-python ~3.12.16
  before harden-pip (the job previously ran on the system interpreter).
- coverage.yml: add PY_VER_FLOOR (~3.14.10) and use it as the
  setup-python spec in all four jobs; PY_VER stays minor-only for
  env-vars/naming.

CVE-2026-115557.
The matrix jobs in test-wheel-linux, test-wheel-windows and build-wheel
run setup-python with matrix.PY_VER across 3.11 through 3.15t, each with
a different first-fixed patch, so a single tilde-range floor cannot
cover them. Set check-latest: true so setup-python resolves the newest
patch from the manifest instead of a stale tool-cache entry; every
series in the matrix has a fixed release, so the newest patch always
carries the eMudhra ssl fix (CVE-2026-115557).
@juenglin

Copy link
Copy Markdown
Contributor Author

/ok to test 0e852e6

~3.12.16 resolves correctly on Linux but fails on Windows because the
Windows python-versions manifest lags behind Linux when a security batch
ships. Switch build-wheel (cibuildwheel host) and test-sdist-windows to
check-latest: true with the minor-only spec so they resolve the newest
available patch on any OS, consistent with the matrix setup-python steps.
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor
Doc Preview CI
Preview removed because the pull request was closed or merged.

@juenglin

Copy link
Copy Markdown
Contributor Author

/ok to test cb8baed

@juenglin
juenglin requested review from jpascucci-nv, lijinf2 and rwgk and removed request for lijinf2 October 28, 2026 23:47
@juenglin
juenglin marked this pull request as ready for review October 28, 2026 23:47
@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

📝 Summary

Summary by CodeRabbit

  • Chores
    • Updated build and test workflows to select the latest available Python patch releases, with minimum version floors where required for the SSL certificate fix.
    • Updated pip hardening to require a Python interpreter with that SSL fix and install the latest pip instead of a pinned version.
    • Updated release, coverage, pre-commit, and source-distribution workflows to use compatible Python versions.

The harden-pip action checks Python versions for the stated SSL fix before upgrading pip. CI workflows now select explicit Python version floors or check for the latest patch release.

Changes: Python SSL and CI interpreter updates

Layer / File(s) Summary
Harden-pip action contract
.github/actions/harden-pip/action.yml
The action checks the configured interpreter against listed Python patch thresholds. Its pip step now upgrades pip without a version or wheel hash pin.
Explicit Python version floors
.github/workflows/ci-nightly.yml, .github/workflows/coverage.yml, .github/workflows/pre-commit.yml, .github/workflows/release.yml, .github/workflows/test-sdist-linux.yml
These workflows now select Python versions at or above the stated SSL-fix floors.
Latest Python patch selection
.github/workflows/build-wheel.yml, .github/workflows/test-sdist-windows.yml, .github/workflows/test-wheel-linux.yml, .github/workflows/test-wheel-windows.yml
These workflows now check for the latest patch matching the requested Python version.

Possibly related PRs

Priority: Normal

Change: Other

Merge Risk: Moderate · up to dd043

Windows wheel-build and sdist CI jobs will fail at the harden-pip step until patched Windows interpreters are available or a secure bootstrap is retained. This should be resolved or explicitly accepted before merge. Some workflow jobs also still inherit default token permissions.

- [ ] Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (3)

Minor · important: Add an explicit least-privilege token scope to this job. · ci-nightly.yml:51-55

.github/workflows/ci-nightly.yml:51-55
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

important: Add an explicit least-privilege token scope to this job.

test-ci-tools-for-release has no permissions block. It checks out the repository and runs pip-installed and repository test code, so its GITHUB_TOKEN scope depends on repository defaults. Add permissions: contents: read to this job. GitHub applies repository defaults when the workflow or job does not override them. (raw.githubusercontent.com)

Sources: Path instructions, Linters/SAST tools

Minor · important: Set a least-privilege default for the coverage workflow. · coverage.yml:20-26

.github/workflows/coverage.yml:20-26
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

important: Set a least-privilege default for the coverage workflow.

coverage-vars has no permissions block and only checks out the repository to read ci/versions.yml. Add a restrictive workflow default and grant this job contents: read explicitly. Keep write and OIDC permissions limited to jobs that require them. GitHub derives initial permissions from repository or organization defaults unless the workflow or job overrides them. (raw.githubusercontent.com)

Sources: Path instructions, Linters/SAST tools

Minor · important: Limit the release-notes job token to read access. · release.yml:168-170

.github/workflows/release.yml:168-170
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

important: Limit the release-notes job token to read access.

check-release-notes has no job-level permissions block. It checks out inputs.git-tag and executes ci/tools/check_release_notes.py, so a repository default with write access would expose that token to code from the selected tag. Add permissions: contents: read here and define a restrictive workflow default. Keep write permissions only on publishing jobs. (raw.githubusercontent.com)

Sources: Path instructions, Linters/SAST tools


Review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 94625c1 and cb8baed.

Files selected for processing: 10
  • .github/actions/harden-pip/action.yml
  • .github/workflows/build-wheel.yml
  • .github/workflows/ci-nightly.yml
  • .github/workflows/coverage.yml
  • .github/workflows/pre-commit.yml
  • .github/workflows/release.yml
  • .github/workflows/test-sdist-linux.yml
  • .github/workflows/test-sdist-windows.yml
  • .github/workflows/test-wheel-linux.yml
  • .github/workflows/test-wheel-windows.yml

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 10 remain after this review.

run: |
"${HARDEN_PIP_PYTHON}" -m pip install \
"pip @ https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-26.2.2-py3-none-any.whl#sha256=fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70"
"${HARDEN_PIP_PYTHON}" -m pip install -U pip

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Critical · Security & Privacy · Quick win

important: Keep the verified bootstrap for unpatched Python. .github/workflows/build-wheel.yml (Line 106) and .github/workflows/test-sdist-windows.yml (Line 58) request 3.12 without a floor. The current manifest resolves Windows to 3.12.10, below the 3.12.16 fix. (raw.githubusercontent.com)

With an older bundled pip, a network attacker can use a compromised CA key to replace the unpinned download and execute code in CI. Restore the hash-pinned bootstrap and ensure callers use a patched interpreter or fail closed. (discuss.python.org)

As per path instructions: “focus on … security boundaries.”

Source: Path instructions

python-version: "3.12"
# Resolve newest patch from manifest (not stale tool cache); tilde-range
# (~3.12.16) is not used because the Windows manifest may lag behind Linux.
check-latest: true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Major · Security & Privacy · Heavy lift

important: Keep an explicit patched-version floor in these setup steps. check-latest: true selects the newest version available in the actions/python-versions manifest. It does not require 3.12.16 or the corresponding floor for each matrix series. If the Windows manifest lags during a security release, setup-python can install the previous patch, and harden-pip then runs with the pre-fix ssl module. Retain a floor range such as ~3.12.16 for fixed-version jobs, and add an offline version-floor assertion before harden-pip for matrix jobs and the other affected workflows. (github.com)

As per path instructions, this CI change must preserve the security boundary before networked package installation.

Also applies to: 454-454

Source: Path instructions

@rwgk rwgk 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.

codex GPT-6.1-Sol ultra:

  1. [P1] check-latest does not enforce the Windows security floor — build-wheel.yml:112–115. The manifest still selects Windows 3.11.9, 3.12.10, and 3.13.17, below the required fixes. This PR’s CI confirms the older versions execute. Removing the verified pip bootstrap therefore lacks its stated prerequisite. Retain that bootstrap until patched interpreters are available, or obtain patched builds and enforce explicit minimums.

  2. [P1] Nested Lychee callers still use unpatched Python — harden-pip/action.yml:28. lychee-links invokes this action, but lychee.yml:73 and build-docs.yml:310 still request unconstrained 3.14. The docs job selects 3.14.9 before running the simplified upgrade. Floor both callers to ~3.14.10, or preserve the verified bootstrap.

Reviewed cb8baed.

Re-add the version check dropped in the earlier simplification commit,
this time inlined in action.yml rather than in a separate .py file, so
no additional source file or test is needed. The guard fails the step
(and the job) when the interpreter predates the first release of its
minor series that rejects the compromised eMudhra CA keys
(CVE-2026-115557), emitting a ::error:: workflow annotation.

This surfaces the Windows python-versions manifest lag explicitly
(e.g. 3.12.10 instead of 3.12.16) so it shows as a red job rather
than a silent unpatched pip upgrade.
@juenglin
juenglin marked this pull request as draft October 29, 2026 02:32

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


Review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between cb8baed and dd043cd.

Files selected for processing: 1
  • .github/actions/harden-pip/action.yml

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

Comment on lines +37 to +39
if v.micro < FIXED[s]:
print("::error::Python "+p+" is older than "+fl+", the first "+str(v.major)+"."+str(v.minor)+" release that rejects the compromised eMudhra CA keys (CVE-2026-115557).")
sys.exit(1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Major · Stability & Availability · Heavy lift

Supported by static analysis

Script executed:

rg -n -C4 'harden-pip|python-version|check-latest|allow-prereleases' .github/workflows/build-wheel.yml .github/workflows/test-sdist-windows.yml .github/workflows/test-wheel-windows.yml
sed -n '1,70p' .github/actions/harden-pip/action.yml

Repository: NVIDIA/cuda-python

Length of output: 12178


Script executed:

set -u
printf '%s\n' '--- relevant commit messages ---'
git log -8 --format='%H%n%s%n%b' -- .github/actions/harden-pip/action.yml .github/workflows/build-wheel.yml .github/workflows/test-sdist-windows.yml
printf '%s\n' '--- actions/python-versions manifest: Windows 3.12 entries ---'
python3 - <<'PY'
import json
import urllib.request
url = "https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json"
try:
    with urllib.request.urlopen(url, timeout=15) as r:
        data = json.load(r)
except Exception as exc:
    print(f"manifest fetch failed: {exc}")
else:
    found = []
    for item in data:
        version = str(item.get("version", ""))
        if version.startswith("3.12"):
            files = item.get("files", [])
            windows = [f for f in files if str(f.get("platform", "")).lower().startswith("win")]
            if windows:
                found.append((version, windows))
    print(json.dumps(found, indent=2))
PY

Repository: NVIDIA/cuda-python

Length of output: 23827


important: Restore the Windows CI path.

Both Windows workflows request Python 3.12 with check-latest: true and run harden-pip. The Windows manifest resolves that request to Python 3.12.10, but the guard rejects every 3.12 interpreter below 3.12.16. These jobs therefore fail before their build or test steps. The fail-closed behavior is intentional, but it remains a mergeability problem.

Provide patched Windows interpreters for the required minor versions, or retain a secure hash-pinned bootstrap for affected versions.

Both workflows pass their Python interpreter to lychee-links, which
calls harden-pip. Neither had a patched-version floor, and the docs
CI run (job 123173870397) confirmed 3.14.9 was resolved before the
simplified pip upgrade ran (no hash-pinned bootstrap to compensate).

Apply the same ~3.14.10 tilde-range floor used in pre-commit.yml:
- lychee.yml: inline the floor in the python-version field; PYTHON_VERSION
  stays minor-only ("3.14") for the cache restore-key prefix.
- build-docs.yml: direct literal, no cache interaction to preserve.

3.14.10 is present in the manifest for all runner platforms, so the
tilde-range resolves cleanly. Raised by rwgk in review.
@juenglin

Copy link
Copy Markdown
Contributor Author

/ok to test 680f0dc

@rwgk

rwgk commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

codex has an explanation for the CI failures:

[P1] The new guard blocks all Windows builds — harden-pip/action.yml:35–39.

All 14 Windows build rows first select Python 3.12.10, then fail the new 3.12.16 minimum before building. Current x64 and ARM64 logs confirm this. Provision patched Windows interpreters for both host and legacy matrix jobs, or provide an explicit legacy path retaining verified bootstrapping. Simply changing the requested floor cannot supply unavailable Windows binaries.

The earlier unchecked-upgrade paths now fail closed, and both missed Lychee callers are fixed. No additional findings.

Reviewed 680f0dc. All 38 offline guard cases pass; pre-commit and docs CI pass. Full CI remains red from the Windows failures.

@juenglin

Copy link
Copy Markdown
Contributor Author

Closing because using setup-python is a dead end for this remediation.

@juenglin juenglin closed this Oct 29, 2026
juenglin added a commit that referenced this pull request Oct 29, 2026
CI: install eMudhra-fixed Pythons with a pinned uv (#3275)

Replaces `actions/setup-python` + `harden-pip` everywhere with a new
`.github/actions/setup-hardened-python` composite action.

The action uses `astral-sh/setup-uv` (SHA-pinned, `version: "latest-known"`)
to install CPython via python-build-standalone, whose archive integrity is
verified against embedded checksums rather than TLS. This unblocks Windows:
python.org no longer publishes installers for the 3.11–3.13 maintenance
series, so `setup-python` can never deliver 3.11.18/3.12.16/3.13.18 there.

A `shell: python` guard step fails the job if the `ssl` module predates the
eMudhra fix (CVE-2026-115557) or if the series has no known fixed release.

Notable call-site changes: build-wheel passes the host interpreter to
cibuildwheel via `python-path`; ci-nightly and release verify jobs now
get a hardened interpreter instead of the system Python; pre-commit and
lychee cache keys gain a `uv` component; `harden-pip` is removed.

Refs #3195. Supersedes #3259.
github-actions Bot pushed a commit that referenced this pull request Oct 29, 2026
Removed preview folders for the following PRs:
- PR #2494
- PR #3186
- PR #3259
- PR #3274
- PR #3275
- PR #3279
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI/CD CI/CD infrastructure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants