From a50fe7f9667b89a0a54fda7acf0df84b18c3b9d3 Mon Sep 17 00:00:00 2001 From: Ralf Juengling Date: Wed, 28 Oct 2026 20:07:53 -0700 Subject: [PATCH 1/4] CI: install eMudhra-fixed Pythons with a pinned uv Replace every actions/setup-python + harden-pip pair with a new setup-hardened-python action. It installs CPython with uv 0.13.6 (via setup-uv v10.3.3), creates a venv, and puts it first on PATH. setup-uv checks the uv binary, and uv checks the python-build-standalone archive, against embedded checksums. The action then fails the job unless the interpreter's ssl module rejects the compromised eMudhra/emSign CA keys (CVE-2026-115557), and updates pip. This provides patched Windows interpreters for the 3.11-3.13 series, for which python.org publishes no installers and setup-python has no builds. It also removes the AGENT_TOOLSDIRECTORY workaround on self-hosted runners. - build-wheel: pass the hardened interpreter to cibuildwheel through python-path, so it no longer installs itself with its own unchecked setup-python interpreter; derive include paths from sysconfig. - ci-nightly, release verify jobs: these used the runner's system Python and now get a hardened one. - pre-commit, lychee: add "uv" to the cache key so hook environments built on setup-python interpreters are not restored. - Remove harden-pip and the lychee-links step that called it. --- .github/actions/harden-pip/action.yml | 28 ---- .github/actions/lychee-links/action.yml | 7 +- .../actions/setup-hardened-python/action.yml | 120 ++++++++++++++++++ .github/workflows/build-docs.yml | 2 +- .github/workflows/build-wheel.yml | 28 ++-- .github/workflows/ci-nightly.yml | 6 +- .github/workflows/coverage.yml | 44 +------ .github/workflows/lychee.yml | 6 +- .github/workflows/pre-commit.yml | 17 ++- .github/workflows/release.yml | 14 +- .github/workflows/test-sdist-linux.yml | 5 +- .github/workflows/test-sdist-windows.yml | 5 +- .github/workflows/test-wheel-linux.yml | 13 +- .github/workflows/test-wheel-windows.yml | 10 +- 14 files changed, 172 insertions(+), 133 deletions(-) delete mode 100644 .github/actions/harden-pip/action.yml create mode 100644 .github/actions/setup-hardened-python/action.yml diff --git a/.github/actions/harden-pip/action.yml b/.github/actions/harden-pip/action.yml deleted file mode 100644 index 868c30295f5..00000000000 --- a/.github/actions/harden-pip/action.yml +++ /dev/null @@ -1,28 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# -# SPDX-License-Identifier: Apache-2.0 - -name: Harden pip -description: >- - Install the security-fixed pip (26.2.2) from a hash-pinned wheel so that later - pip invocations do not fall back on the vendored certifi bundle that trusts the - compromised eMudhra/emSign intermediate CAs. The sha256 fragment guarantees the - bootstrap download's integrity even if the bootstrapping pip's trust store is - compromised. See https://github.com/NVIDIA/cuda-python/issues/3195 and - https://discuss.python.org/t/109576. - -inputs: - python: - description: Python interpreter whose pip should be hardened (e.g. a venv's python). - default: python - -runs: - using: composite - steps: - - name: Install hash-pinned pip 26.2.2 - shell: bash --noprofile --norc -xeuo pipefail {0} - env: - HARDEN_PIP_PYTHON: ${{ inputs.python }} - 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" diff --git a/.github/actions/lychee-links/action.yml b/.github/actions/lychee-links/action.yml index 8f8ff2284f1..4f345576d3d 100644 --- a/.github/actions/lychee-links/action.yml +++ b/.github/actions/lychee-links/action.yml @@ -26,8 +26,8 @@ outputs: description: "Whether the current PR label explicitly enables the HTTP 429 override" value: ${{ steps.lychee-policy.outputs.allow-429 }} -# Callers provide Python and the checked-out repository; the build of any -# rendered documentation must already be complete. +# Callers provide Python through setup-hardened-python and the checked-out +# repository; the build of any rendered documentation must already be complete. runs: using: composite steps: @@ -113,9 +113,6 @@ runs: printf '[Open the cache refresh workflow](%s).\n' "$WORKFLOW_URL" } >> "$GITHUB_STEP_SUMMARY" - - name: Harden pip - uses: ./.github/actions/harden-pip - - name: Check links through the configured pre-commit hook shell: bash --noprofile --norc -euo pipefail {0} env: diff --git a/.github/actions/setup-hardened-python/action.yml b/.github/actions/setup-hardened-python/action.yml new file mode 100644 index 00000000000..e901dc1db34 --- /dev/null +++ b/.github/actions/setup-hardened-python/action.yml @@ -0,0 +1,120 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +name: Set up hardened Python +description: >- + Install CPython with a pinned uv, create a venv from it, and put the venv + first on PATH. setup-uv verifies the uv binary and uv verifies the + python-build-standalone archive against embedded checksums, so the + interpreter's integrity does not depend on TLS. The job then fails unless + the interpreter's ssl module rejects the compromised eMudhra/emSign CA keys + (CVE-2026-115557; first fixed releases: 3.11.18, 3.12.16, 3.13.18, 3.14.10, + 3.15.2), and pip is updated through that ssl module. Unlike setup-python, + this provides patched Windows builds of maintenance series, for which + python.org publishes no installers. See + https://github.com/NVIDIA/cuda-python/issues/3195 and + https://discuss.python.org/t/109678. + +inputs: + python-version: + description: >- + uv Python request, e.g. "3.12", "3.14t" (free-threaded), or "3.16" + (resolves to a prerelease while no final release exists). The newest + patch known to the pinned uv is installed. + required: true + venv: + description: Directory for the venv. Defaults to a new directory under RUNNER_TEMP. + default: "" + +outputs: + python-path: + description: Absolute path of the venv's interpreter. + value: ${{ steps.venv.outputs.python-path }} + python-version: + description: Full version of the interpreter, e.g. "3.14.10". + value: ${{ steps.venv.outputs.python-version }} + +runs: + using: composite + steps: + - name: Install uv + uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3 + with: + # The uv release fixes the newest available patch of each series; + # 0.13.6 is the first with the eMudhra-fixed CPython builds. + version: "0.13.6" + enable-cache: false + # Some callers (e.g. release verification) have no checkout. + ignore-empty-workdir: true + + - name: Install Python and create venv + id: venv + shell: bash --noprofile --norc -euo pipefail {0} + env: + PY_REQUEST: ${{ inputs.python-version }} + VENV_DIR: ${{ inputs.venv }} + # A per-job install directory ensures the newest patch known to the + # pinned uv is used, never an older interpreter left on a persistent + # self-hosted runner. + UV_PYTHON_INSTALL_DIR: ${{ runner.temp }}/setup-hardened-python/interpreters + UV_NO_CONFIG: "1" + run: | + uv python install --no-bin --no-registry "${PY_REQUEST}" + base="$(uv python find --managed-python --no-project "${PY_REQUEST}")" + venv="${VENV_DIR}" + if [[ -z "${venv}" ]]; then + # A fresh directory per call, so a second call in the same job + # (e.g. build-wheel.yml) leaves the first venv intact. + venv="$("${base}" -c 'import sys, tempfile; print(tempfile.mkdtemp(prefix="venv-", dir=sys.argv[1]))' "${RUNNER_TEMP}")" + fi + # The stdlib venv seeds pip from the interpreter's bundled wheel, so + # nothing is downloaded before the ssl check below. + "${base}" -m venv --clear "${venv}" + if [[ "${RUNNER_OS}" == Windows ]]; then + py="${venv}/Scripts/python.exe" + else + py="${venv}/bin/python" + fi + # Let the interpreter report native absolute paths (Windows included). + python_path="$("${py}" -c 'import sys; print(sys.executable)')" + "${py}" -c 'import os, sys; print(os.path.dirname(sys.executable))' >> "${GITHUB_PATH}" + { + echo "python-path=${python_path}" + echo "python-version=$("${py}" -c 'import platform; print(platform.python_version())')" + } >> "${GITHUB_OUTPUT}" + + - name: Check Python interpreter is patched (CVE-2026-115557) + shell: bash --noprofile --norc -euo pipefail {0} + env: + PYTHON: ${{ steps.venv.outputs.python-path }} + run: | + "${PYTHON}" -c ' + import platform + import sys + + FIXED = {(3, 11): 18, (3, 12): 16, (3, 13): 18, (3, 14): 10, (3, 15): 2} + v = sys.version_info + series = (v.major, v.minor) + name = f"{v.major}.{v.minor}" + full = platform.python_version() + print("sys.version:", sys.version) + if series in FIXED: + floor = f"{name}.{FIXED[series]}" + if v.micro < FIXED[series]: + print(f"::error::Python {full} is older than {floor}, the first {name} release whose ssl module rejects the compromised eMudhra CA keys (CVE-2026-115557).") + sys.exit(1) + print(f"Python {full}: ssl module carries the fix (first fixed: {floor}).") + elif series > max(FIXED): + print(f"::warning::Python {full}: no fixed {name} release is known yet.") + else: + print(f"::error::Python {name} has no release whose ssl module rejects the compromised eMudhra CA keys (CVE-2026-115557).") + sys.exit(1) + ' + + - name: Update pip + shell: bash --noprofile --norc -xeuo pipefail {0} + env: + PYTHON: ${{ steps.venv.outputs.python-path }} + run: | + "${PYTHON}" -m pip install -U pip diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index e28fbd5dd87..b1c029f62cf 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -305,7 +305,7 @@ jobs: # interpreter instead of the conda environment used above. - name: Set up Python for link checks if: ${{ !inputs.is-release && startsWith(github.ref_name, 'pull-request/') }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: ./.github/actions/setup-hardened-python with: python-version: "3.14" diff --git a/.github/workflows/build-wheel.yml b/.github/workflows/build-wheel.yml index 96df1ac0e6b..9b2be767828 100644 --- a/.github/workflows/build-wheel.yml +++ b/.github/workflows/build-wheel.yml @@ -107,16 +107,12 @@ jobs: enable-apt: true - name: Set up Python - id: setup-python1 - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + id: host-python + uses: ./.github/actions/setup-hardened-python with: - # WAR: setup-python is not relocatable, and cibuildwheel hard-wires to 3.12... - # see https://github.com/actions/setup-python/issues/871 + # Also installs cibuildwheel (via python-path below), so the action + # does not fall back on its own unchecked setup-python interpreter. python-version: "3.12" - architecture: ${{ ((inputs.host-platform == 'linux-aarch64' || inputs.host-platform == 'win-arm64') && 'arm64') || 'x64' }} - - - name: Harden pip - uses: ./.github/actions/harden-pip - name: Set up MSVC if: ${{ startsWith(inputs.host-platform, 'win') && (env.BUILD_BINDINGS == 'true' || env.BUILD_CORE == 'true' || env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true') }} @@ -235,6 +231,7 @@ jobs: package-dir: ./cuda_bindings_13/ output-dir: ${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }} extras: uv + python-path: ${{ steps.host-python.outputs.python-path }} env: CIBW_BUILD: ${{ env.CIBW_BUILD }} # build[uv]: same PEP 517 orchestration, with uv instead of pip. @@ -332,6 +329,7 @@ jobs: package-dir: ./cuda_core/ output-dir: ${{ env.CUDA_CORE_ARTIFACTS_DIR }} extras: uv + python-path: ${{ steps.host-python.outputs.python-path }} env: CIBW_BUILD: ${{ env.CIBW_BUILD }} # See the cuda.bindings step above for UV_* vs PIP_*. @@ -442,16 +440,10 @@ jobs: if-no-files-found: error - name: Set up Python - id: setup-python2 if: ${{ env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true' }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: ./.github/actions/setup-hardened-python with: python-version: ${{ matrix.python-version }} - allow-prereleases: ${{ startsWith(matrix.python-version, '3.16') }} - - - name: Harden pip - if: ${{ env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true' }} - uses: ./.github/actions/harden-pip - name: verify free-threaded build if: ${{ (env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true') && endsWith(matrix.python-version, 't') }} @@ -460,10 +452,11 @@ jobs: - name: Set up Python include paths if: ${{ env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true' }} run: | + include="$(python -c "import sysconfig; print(sysconfig.get_paths()['include'])")" if [[ "${{ inputs.host-platform }}" == linux* ]]; then - echo "CPLUS_INCLUDE_PATH=${Python3_ROOT_DIR}/include/python${{ matrix.python-version }}" >> $GITHUB_ENV + echo "CPLUS_INCLUDE_PATH=${include}" >> $GITHUB_ENV elif [[ "${{ inputs.host-platform }}" == win* ]]; then - echo "CL=/I\"${Python3_ROOT_DIR}\include\python${{ matrix.python-version }}\"" >> $GITHUB_ENV + echo "CL=/I\"${include}\"" >> $GITHUB_ENV fi # For caching echo "PY_EXT_SUFFIX=$(python -c "import sysconfig; print(sysconfig.get_config_var('EXT_SUFFIX'))")" >> $GITHUB_ENV @@ -614,6 +607,7 @@ jobs: package-dir: ./cuda_core/ output-dir: ${{ env.CUDA_CORE_ARTIFACTS_DIR }} extras: uv + python-path: ${{ steps.host-python.outputs.python-path }} env: CIBW_BUILD: ${{ env.CIBW_BUILD }} # See the cuda.bindings step above for UV_* vs PIP_*. diff --git a/.github/workflows/ci-nightly.yml b/.github/workflows/ci-nightly.yml index d76e9a9cb7b..0abfdd626cb 100644 --- a/.github/workflows/ci-nightly.yml +++ b/.github/workflows/ci-nightly.yml @@ -58,8 +58,10 @@ jobs: with: fetch-depth: 1 - - name: Harden pip - uses: ./.github/actions/harden-pip + - name: Set up Python + uses: ./.github/actions/setup-hardened-python + with: + python-version: "3.12" - name: Run CI tools unit tests run: | diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 5e23106ae5f..577e08c93b3 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -102,12 +102,10 @@ jobs: echo "CUDA_PYTHON_COVERAGE=1" >> $GITHUB_ENV - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: ./.github/actions/setup-hardened-python with: python-version: ${{ env.PY_VER }} - env: - # we use self-hosted runners on which setup-python behaves weirdly... - AGENT_TOOLSDIRECTORY: "/opt/hostedtoolcache" + venv: .venv - name: Set up mini CTK if: ${{ env.LOCAL_CTK == '1' }} @@ -117,15 +115,6 @@ jobs: host-platform: ${{ env.HOST_PLATFORM }} cuda-version: ${{ env.CUDA_VER }} - - name: Create venv - run: | - python -m venv .venv - - - name: Harden pip - uses: ./.github/actions/harden-pip - with: - python: .venv/bin/python - - name: Build and install cuda-pathfinder wheel run: | .venv/bin/pip wheel -v --no-deps ./cuda_pathfinder -w ./wheels/ @@ -253,9 +242,10 @@ jobs: submodules: true - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: ./.github/actions/setup-hardened-python with: python-version: ${{ env.PY_VER }} + venv: .venv - name: Set up MSVC uses: step-security/msvc-dev-cmd@22c98154b708dbd743e6f27a933cf6ceba3305c4 # v1.13.1 @@ -267,15 +257,6 @@ jobs: host-platform: win-64 cuda-version: ${{ env.CUDA_VER }} - - name: Create venv - run: | - python -m venv .venv - - - name: Harden pip - uses: ./.github/actions/harden-pip - with: - python: .venv/Scripts/python - - name: Build cuda.pathfinder wheel run: | .venv/Scripts/python -m pip install wheel setuptools Cython @@ -392,9 +373,10 @@ jobs: nvidia-smi - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: ./.github/actions/setup-hardened-python with: python-version: ${{ env.PY_VER }} + venv: .venv - name: Set up mini CTK uses: ./.github/actions/fetch_ctk @@ -414,15 +396,6 @@ jobs: echo "=== Downloaded wheel artifacts ===" ls -lahR ./wheels/ - - name: Create venv - run: | - python -m venv .venv - - - name: Harden pip - uses: ./.github/actions/harden-pip - with: - python: .venv/Scripts/python - - name: Install wheels from build job run: | .venv/Scripts/pip install ./wheels/cuda_pathfinder*.whl @@ -502,13 +475,10 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: ./.github/actions/setup-hardened-python with: python-version: ${{ env.PY_VER }} - - name: Harden pip - uses: ./.github/actions/harden-pip - - name: Install coverage run: | # .coveragerc enables Cython.Coverage plugin; ensure it's available here too. diff --git a/.github/workflows/lychee.yml b/.github/workflows/lychee.yml index e73df49a34b..ecb609ef395 100644 --- a/.github/workflows/lychee.yml +++ b/.github/workflows/lychee.yml @@ -68,7 +68,7 @@ jobs: - name: Set up Python id: python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: ./.github/actions/setup-hardened-python with: python-version: ${{ env.PYTHON_VERSION }} @@ -79,8 +79,8 @@ jobs: uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ${{ env.PRE_COMMIT_HOME }} - key: pre-commit-${{ runner.os }}-py${{ steps.python.outputs.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }} - restore-keys: pre-commit-${{ runner.os }}-py${{ env.PYTHON_VERSION }} + key: pre-commit-${{ runner.os }}-uv-py${{ steps.python.outputs.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }} + restore-keys: pre-commit-${{ runner.os }}-uv-py${{ env.PYTHON_VERSION }} - name: Setup pixi if: ${{ matrix.kind == 'rendered' }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 7c7d78cb1da..40c4636eb2a 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -46,7 +46,7 @@ jobs: # install a Git commit hook. All other hooks, including Lychee, run. SKIP: check-precommit-installed PYTHONUTF8: "1" - # Minor version used by setup-python and as the cache restore-key prefix. + # Minor version used by setup-hardened-python and as the cache restore-key prefix. PYTHON_VERSION: "3.14" steps: - name: Harden OS certificate trust (eMudhra/emSign CA stopgap) @@ -66,7 +66,7 @@ jobs: - name: Set up Python id: python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: ./.github/actions/setup-hardened-python with: python-version: ${{ env.PYTHON_VERSION }} @@ -83,13 +83,12 @@ jobs: # that bump, so hooks rebuild from a warm cache instead of cold. The # prefix stops at the minor with no trailing separator, so it is a # prefix of "py3.14.-..." regardless of the separator that - # follows the resolved version. - key: pre-commit-${{ runner.os }}-py${{ steps.python.outputs.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }} + # follows the resolved version. Hook environments point at the + # interpreter that built them, so "uv" keeps environments built on + # setup-python interpreters from being restored. + key: pre-commit-${{ runner.os }}-uv-py${{ steps.python.outputs.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }} restore-keys: | - pre-commit-${{ runner.os }}-py${{ env.PYTHON_VERSION }} - - - name: Harden pip - uses: ./.github/actions/harden-pip + pre-commit-${{ runner.os }}-uv-py${{ env.PYTHON_VERSION }} - name: Install pre-commit run: python -m pip install pre-commit @@ -180,4 +179,4 @@ jobs: uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ${{ env.PRE_COMMIT_HOME }} - key: pre-commit-${{ runner.os }}-py${{ steps.python.outputs.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }} + key: pre-commit-${{ runner.os }}-uv-py${{ steps.python.outputs.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index acefd058a58..dc466827046 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -177,7 +177,9 @@ jobs: ref: ${{ inputs.git-tag }} - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + # $/ resolves at the workflow's commit. ./ would resolve against the + # release tag checkout, which can predate this action. + uses: $/.github/actions/setup-hardened-python with: python-version: "3.12" @@ -342,16 +344,16 @@ jobs: EXTRA_INDEX_URL: https://pypi.org/simple/ INDEX_NAME: TestPyPI steps: &verify-pathfinder-steps - - name: Create verification environment + - name: Require version output run: | [[ -n "$VERSION" ]] || { echo "::error::Empty version output"; exit 1; } - python3 -m venv "${RUNNER_TEMP}/verify" - - name: Harden pip + - name: Create verification environment # These jobs have no checkout; $/ loads the action from the workflow's commit. - uses: $/.github/actions/harden-pip + uses: $/.github/actions/setup-hardened-python with: - python: ${{ runner.temp }}/verify/bin/python + python-version: "3.12" + venv: ${{ runner.temp }}/verify - name: Install Pathfinder from index and verify run: | diff --git a/.github/workflows/test-sdist-linux.yml b/.github/workflows/test-sdist-linux.yml index 124a61966cb..13805b7fda3 100644 --- a/.github/workflows/test-sdist-linux.yml +++ b/.github/workflows/test-sdist-linux.yml @@ -47,13 +47,10 @@ jobs: submodules: true - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: ./.github/actions/setup-hardened-python with: python-version: "3.12" - - name: Harden pip - uses: ./.github/actions/harden-pip - - name: Install build tools run: python -m pip install build diff --git a/.github/workflows/test-sdist-windows.yml b/.github/workflows/test-sdist-windows.yml index 381fe23314b..bffcf5d2ea9 100644 --- a/.github/workflows/test-sdist-windows.yml +++ b/.github/workflows/test-sdist-windows.yml @@ -59,13 +59,10 @@ jobs: submodules: true - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: ./.github/actions/setup-hardened-python with: python-version: "3.12" - - name: Harden pip - uses: ./.github/actions/harden-pip - - name: Set up MSVC if: ${{ env.BUILD_BINDINGS == 'true' || env.BUILD_CORE == 'true' }} uses: step-security/msvc-dev-cmd@22c98154b708dbd743e6f27a933cf6ceba3305c4 # v1.13.1 diff --git a/.github/workflows/test-wheel-linux.yml b/.github/workflows/test-wheel-linux.yml index 0582901a975..c5266302400 100644 --- a/.github/workflows/test-wheel-linux.yml +++ b/.github/workflows/test-wheel-linux.yml @@ -334,18 +334,11 @@ jobs: ls -lahR $CUDA_CORE_TEST_BINARIES_DIR - name: Set up Python ${{ matrix.PY_VER }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: ${{ matrix.PY_VER }} - allow-prereleases: ${{ startsWith(matrix.PY_VER, '3.16') }} - env: - # we use self-hosted runners on which setup-python behaves weirdly (Python include can't be found)... - AGENT_TOOLSDIRECTORY: "/opt/hostedtoolcache" - - - name: Harden pip # $/ resolves at the workflow's commit. ./ would resolve against the # inputs.sha checkout, which can predate this action. - uses: $/.github/actions/harden-pip + uses: $/.github/actions/setup-hardened-python + with: + python-version: ${{ matrix.PY_VER }} - name: Set up mini CTK if: ${{ matrix.LOCAL_CTK == '1' }} diff --git a/.github/workflows/test-wheel-windows.yml b/.github/workflows/test-wheel-windows.yml index 375dae14ef4..0b8f2802f3b 100644 --- a/.github/workflows/test-wheel-windows.yml +++ b/.github/workflows/test-wheel-windows.yml @@ -316,15 +316,11 @@ jobs: Get-ChildItem -Recurse -Force $env:CUDA_CORE_TEST_BINARIES_DIR | Select-Object Mode, LastWriteTime, Length, FullName - name: Set up Python ${{ matrix.PY_VER }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: ${{ matrix.PY_VER }} - allow-prereleases: ${{ startsWith(matrix.PY_VER, '3.16') }} - - - name: Harden pip # $/ resolves at the workflow's commit. ./ would resolve against the # inputs.sha checkout, which can predate this action. - uses: $/.github/actions/harden-pip + uses: $/.github/actions/setup-hardened-python + with: + python-version: ${{ matrix.PY_VER }} - name: Verify LongPathsEnabled run: | From cd417c82b376a72da9b668ba8ae6987946f00aca Mon Sep 17 00:00:00 2001 From: Ralf Juengling Date: Thu, 29 Oct 2026 07:27:30 -0700 Subject: [PATCH 2/4] CI: run the setup-hardened-python guard with shell: python Drop the bash -c wrapper and its quoting constraints, and fail if PATH does not resolve python to the venv interpreter that later steps use. --- .github/actions/setup-hardened-python/action.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/actions/setup-hardened-python/action.yml b/.github/actions/setup-hardened-python/action.yml index e901dc1db34..cb73b1d90a5 100644 --- a/.github/actions/setup-hardened-python/action.yml +++ b/.github/actions/setup-hardened-python/action.yml @@ -85,14 +85,19 @@ runs: } >> "${GITHUB_OUTPUT}" - name: Check Python interpreter is patched (CVE-2026-115557) - shell: bash --noprofile --norc -euo pipefail {0} + # Runs the venv's python through the PATH entry added above. + shell: python {0} env: PYTHON: ${{ steps.venv.outputs.python-path }} run: | - "${PYTHON}" -c ' + import os import platform import sys + if os.path.normcase(sys.executable) != os.path.normcase(os.environ["PYTHON"]): + print(f"::error::PATH resolves python to {sys.executable}, not the venv interpreter {os.environ['PYTHON']}.") + sys.exit(1) + FIXED = {(3, 11): 18, (3, 12): 16, (3, 13): 18, (3, 14): 10, (3, 15): 2} v = sys.version_info series = (v.major, v.minor) @@ -110,7 +115,6 @@ runs: else: print(f"::error::Python {name} has no release whose ssl module rejects the compromised eMudhra CA keys (CVE-2026-115557).") sys.exit(1) - ' - name: Update pip shell: bash --noprofile --norc -xeuo pipefail {0} From a0e7a0758e4fa1084ce74e61e8295d8fa6bd9378 Mon Sep 17 00:00:00 2001 From: Ralf Juengling Date: Thu, 29 Oct 2026 07:49:18 -0700 Subject: [PATCH 3/4] CI: install the newest uv known to the pinned setup-uv Use version: latest-known so the uv binary is always verified against a checksum shipped inside the SHA-pinned setup-uv, never only against the download manifest, and so setup-uv bumps carry uv (and with it the CPython patch releases) along. --- .github/actions/setup-hardened-python/action.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/actions/setup-hardened-python/action.yml b/.github/actions/setup-hardened-python/action.yml index cb73b1d90a5..e5c8ed43f65 100644 --- a/.github/actions/setup-hardened-python/action.yml +++ b/.github/actions/setup-hardened-python/action.yml @@ -41,9 +41,11 @@ runs: - name: Install uv uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3 with: - # The uv release fixes the newest available patch of each series; - # 0.13.6 is the first with the eMudhra-fixed CPython builds. - version: "0.13.6" + # The newest uv whose checksum ships inside the pinned setup-uv, so + # the binary is verified against the SHA-pinned action itself. The uv + # release determines the newest patch of each CPython series, so + # bumping the setup-uv SHA also moves the CPython patch versions. + version: "latest-known" enable-cache: false # Some callers (e.g. release verification) have no checkout. ignore-empty-workdir: true From 045a9e161a5268b820d4c848fca69dad32cd23db Mon Sep 17 00:00:00 2001 From: Ralf Juengling Date: Thu, 29 Oct 2026 07:55:34 -0700 Subject: [PATCH 4/4] CI: fail setup-hardened-python for Python series without a known fix Python 3.16.0a1 was tagged before the CPython fix for CVE-2026-115557 merged, so warning on series newer than the fixed-release table let a known-vulnerable interpreter through. Treat any unlisted series as an error; a new series is accepted once its fixed release is added. --- .github/actions/setup-hardened-python/action.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/actions/setup-hardened-python/action.yml b/.github/actions/setup-hardened-python/action.yml index e5c8ed43f65..8afb6feef2b 100644 --- a/.github/actions/setup-hardened-python/action.yml +++ b/.github/actions/setup-hardened-python/action.yml @@ -19,9 +19,9 @@ description: >- inputs: python-version: description: >- - uv Python request, e.g. "3.12", "3.14t" (free-threaded), or "3.16" - (resolves to a prerelease while no final release exists). The newest - patch known to the pinned uv is installed. + uv Python request, e.g. "3.12" or "3.14t" (free-threaded). The newest + patch known to the pinned uv is installed. Only series with a fixed + release listed in the interpreter check (3.11-3.15) are accepted. required: true venv: description: Directory for the venv. Defaults to a new directory under RUNNER_TEMP. @@ -112,10 +112,9 @@ runs: print(f"::error::Python {full} is older than {floor}, the first {name} release whose ssl module rejects the compromised eMudhra CA keys (CVE-2026-115557).") sys.exit(1) print(f"Python {full}: ssl module carries the fix (first fixed: {floor}).") - elif series > max(FIXED): - print(f"::warning::Python {full}: no fixed {name} release is known yet.") else: - print(f"::error::Python {name} has no release whose ssl module rejects the compromised eMudhra CA keys (CVE-2026-115557).") + # Fail closed: a series is only accepted once its fixed release is listed. + print(f"::error::Python {full}: no {name} release is known to reject the compromised eMudhra CA keys (CVE-2026-115557).") sys.exit(1) - name: Update pip