From 7d68f5752dbaebf83c55fb142a5362d77c668d21 Mon Sep 17 00:00:00 2001 From: Dmitrii Gridnev Date: Wed, 13 May 2026 13:25:52 +0300 Subject: [PATCH] build: refuse PyPI packages uploaded less than 3 days ago MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Defense against the recent wave of PyPI hijack attacks where attackers publish a malicious version and have a few hours before maintainers notice. Mirrors npm's `min-release-age=3` setting. - Set PIP_UPLOADED_PRIOR_TO=P3D at workflow level so all pip subprocesses (integration test venvs, `python -m build`) refuse PyPI artifacts younger than 3 days. - Pin pip>=26.1 in every explicit upgrade step — the relative-duration form `P3D` was added in pip 26.1. - Wrap every pip self-upgrade in `env -u PIP_UPLOADED_PRIOR_TO`. GitHub runners ship pip 26.0, which knows --uploaded-prior-to but only accepts ISO datetimes; passing P3D fails validation before the upgrade can land. - In the publish job, split build-deps install into two pip calls so setuptools/wheel/build are installed under the cooldown rather than the bootstrap. - Add a repo-level pip.conf so local developers can opt in by pointing PIP_CONFIG_FILE at it. Known gap: the test job's tox-managed venvs install testing deps (pytest, pytest-cov, pytest-bdd) through pip seeded by virtualenv, which currently ships an older pip that does not honor the env var. Closing this fully would require either per-package tox config changes across 7 packages or a virtualenv bundle bump; deferred. --- .github/workflows/pythonpackage.yml | 19 ++++++++++++++++--- pip.conf | 10 ++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 pip.conf diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 2b3c6d5f..c2a6eab7 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -2,6 +2,11 @@ name: Python package on: [ push ] +env: + # Supply-chain protection: refuse PyPI packages uploaded less than 3 days ago. + # Honored by pip>=26.1 (relative durations); silently ignored by older pip. + PIP_UPLOADED_PRIOR_TO: "P3D" + jobs: test: runs-on: ubuntu-22.04 @@ -41,7 +46,10 @@ jobs: - name: Install dependencies if: steps.filter.outputs.changes == 'true' run: | - python -m pip install --upgrade pip + # Bootstrap with PIP_UPLOADED_PRIOR_TO unset: the seeded pip may be + # 26.0, which validates the value but only accepts ISO datetimes — + # the P3D form was added in 26.1. + env -u PIP_UPLOADED_PRIOR_TO python -m pip install --upgrade 'pip>=26.1' pip install tox tox-gh-actions - name: Run tox if: steps.filter.outputs.changes == 'true' @@ -60,7 +68,7 @@ jobs: - name: Install reporters-validator run: | - python -m pip install --upgrade pip + env -u PIP_UPLOADED_PRIOR_TO python -m pip install --upgrade 'pip>=26.1' pip install git+https://github.com/qase-tms/reporters-validator.git - name: Download report schemas @@ -69,6 +77,7 @@ jobs: - name: Validate Pytest reporter run: | python -m venv /tmp/venv-pytest + env -u PIP_UPLOADED_PRIOR_TO /tmp/venv-pytest/bin/python -m pip install --upgrade 'pip>=26.1' /tmp/venv-pytest/bin/pip install -q \ ./qase-api-client ./qase-api-v2-client ./qase-python-commons ./qase-pytest @@ -84,6 +93,7 @@ jobs: - name: Validate Behave reporter run: | python -m venv /tmp/venv-behave + env -u PIP_UPLOADED_PRIOR_TO /tmp/venv-behave/bin/python -m pip install --upgrade 'pip>=26.1' /tmp/venv-behave/bin/pip install -q \ ./qase-api-client ./qase-api-v2-client ./qase-python-commons ./qase-behave @@ -101,6 +111,7 @@ jobs: - name: Validate Robot Framework reporter run: | python -m venv /tmp/venv-robot + env -u PIP_UPLOADED_PRIOR_TO /tmp/venv-robot/bin/python -m pip install --upgrade 'pip>=26.1' /tmp/venv-robot/bin/pip install -q \ ./qase-api-client ./qase-api-v2-client ./qase-python-commons ./qase-robotframework \ robotframework @@ -119,6 +130,7 @@ jobs: - name: Validate Tavern reporter run: | python -m venv /tmp/venv-tavern + env -u PIP_UPLOADED_PRIOR_TO /tmp/venv-tavern/bin/python -m pip install --upgrade 'pip>=26.1' /tmp/venv-tavern/bin/pip install -q \ ./qase-api-client ./qase-api-v2-client ./qase-python-commons ./qase-tavern \ tavern @@ -161,7 +173,8 @@ jobs: - name: Install build dependencies if: contains(github.event.ref, matrix.prefix) run: | - python -m pip install --upgrade pip setuptools wheel build + env -u PIP_UPLOADED_PRIOR_TO python -m pip install --upgrade 'pip>=26.1' + pip install --upgrade setuptools wheel build - name: Build the package if: contains(github.event.ref, matrix.prefix) working-directory: ./${{ matrix.prefix }} diff --git a/pip.conf b/pip.conf new file mode 100644 index 00000000..d867b9d8 --- /dev/null +++ b/pip.conf @@ -0,0 +1,10 @@ +# Supply-chain protection: refuse PyPI packages uploaded less than 3 days ago. +# Defends against hijack attacks where a malicious version is published and +# stays unnoticed for a few hours. +# +# Requires pip>=26.1 (relative durations). Older pip silently ignores this key. +# +# To activate for local development, point pip at this file: +# export PIP_CONFIG_FILE=$(git rev-parse --show-toplevel)/pip.conf +[global] +uploaded-prior-to = P3D