From d37f37e63c594492a08d88413fa51c4713c6b501 Mon Sep 17 00:00:00 2001 From: Ryan Rishi Date: Tue, 2 Jun 2026 16:17:27 -0400 Subject: [PATCH 1/4] ci: harden CI/CD pipeline security - Pin all GitHub Actions to commit SHAs in CI workflow - Add top-level permissions: contents: read to CI - Add repository owner guard to deploy workflow - Add github-actions ecosystem to dependabot - Add --exclude-newer quarantine (2-day) to uv sync in CI Co-Authored-By: Claude Opus 4.6 --- .github/dependabot.yml | 5 ++++ .github/workflows/ci.yml | 44 +++++++++++++++++++++++------------- .github/workflows/deploy.yml | 2 ++ 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6483b1d..6b05233 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,3 +4,8 @@ updates: directory: "/" schedule: interval: "weekly" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fff1e3a..92779b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,28 +6,34 @@ on: pull_request: workflow_dispatch: +permissions: + contents: read + jobs: lint: name: Linting runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5 with: version: "0.9.26" - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: "3.10" + - name: Set package quarantine date + run: echo "QUARANTINE_DATE=$(date -u -d '2 days ago' +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_ENV" + - name: Install dependencies run: | uv venv . .venv/bin/activate - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --exclude-newer "$QUARANTINE_DATE" - name: Run ruff check run: | @@ -53,23 +59,26 @@ jobs: name: Type Check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5 with: version: "0.9.26" - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: "3.10" + - name: Set package quarantine date + run: echo "QUARANTINE_DATE=$(date -u -d '2 days ago' +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_ENV" + - name: Install dependencies run: | uv venv . .venv/bin/activate - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --exclude-newer "$QUARANTINE_DATE" - name: Run mypy run: | @@ -92,23 +101,26 @@ jobs: matrix: python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5 with: version: "0.9.26" - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: ${{ matrix.python-version }} + - name: Set package quarantine date + run: echo "QUARANTINE_DATE=$(date -u -d '2 days ago' +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_ENV" + - name: Install dependencies run: | uv venv . .venv/bin/activate - uv sync --all-extras --all-groups + uv sync --all-extras --all-groups --exclude-newer "$QUARANTINE_DATE" - name: Run tests run: | @@ -119,15 +131,15 @@ jobs: name: Build Package runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5 with: version: "0.9.26" - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: "3.10" @@ -150,7 +162,7 @@ jobs: python -c "import tac; print(f'Successfully imported tac version {tac.__version__}')" - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: dist path: dist/ diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 450c50a..fb35598 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -8,6 +8,7 @@ jobs: test: name: Test - Python ${{ matrix.python-version }} runs-on: ubuntu-latest + if: github.repository_owner == 'twilio' permissions: contents: read timeout-minutes: 20 @@ -33,6 +34,7 @@ jobs: name: Publish to PyPI needs: [test] runs-on: ubuntu-latest + if: github.repository_owner == 'twilio' environment: pypi permissions: contents: read From 9bc1fad8b034b8dcaaf25448eac0b00a92af4adc Mon Sep 17 00:00:00 2001 From: Ryan Rishi Date: Wed, 3 Jun 2026 12:00:50 -0400 Subject: [PATCH 2/4] ci: add --frozen for lockfile enforcement Co-Authored-By: Claude Opus 4.6 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92779b3..b0f1155 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: run: | uv venv . .venv/bin/activate - uv sync --all-extras --all-groups --exclude-newer "$QUARANTINE_DATE" + uv sync --frozen --all-extras --all-groups --exclude-newer "$QUARANTINE_DATE" - name: Run ruff check run: | @@ -78,7 +78,7 @@ jobs: run: | uv venv . .venv/bin/activate - uv sync --all-extras --all-groups --exclude-newer "$QUARANTINE_DATE" + uv sync --frozen --all-extras --all-groups --exclude-newer "$QUARANTINE_DATE" - name: Run mypy run: | @@ -120,7 +120,7 @@ jobs: run: | uv venv . .venv/bin/activate - uv sync --all-extras --all-groups --exclude-newer "$QUARANTINE_DATE" + uv sync --frozen --all-extras --all-groups --exclude-newer "$QUARANTINE_DATE" - name: Run tests run: | From e9958d6f7b7ae318c035a5f2c09ca67c8990acab Mon Sep 17 00:00:00 2001 From: Ryan Rishi Date: Fri, 5 Jun 2026 12:38:25 -0400 Subject: [PATCH 3/4] ci: move exclude-newer quarantine to pyproject.toml Record `exclude-newer = "2 days"` in [tool.uv] so the 2-day supply-chain cooldown is enforced at lock time. Remove the CI --exclude-newer flag since --frozen now respects the lockfile's recorded option without mismatch. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/ci.yml | 15 +++------------ pyproject.toml | 1 + uv.lock | 4 ++++ 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b0f1155..d4118d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,14 +26,11 @@ jobs: with: python-version: "3.10" - - name: Set package quarantine date - run: echo "QUARANTINE_DATE=$(date -u -d '2 days ago' +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_ENV" - - name: Install dependencies run: | uv venv . .venv/bin/activate - uv sync --frozen --all-extras --all-groups --exclude-newer "$QUARANTINE_DATE" + uv sync --frozen --all-extras --all-groups - name: Run ruff check run: | @@ -71,14 +68,11 @@ jobs: with: python-version: "3.10" - - name: Set package quarantine date - run: echo "QUARANTINE_DATE=$(date -u -d '2 days ago' +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_ENV" - - name: Install dependencies run: | uv venv . .venv/bin/activate - uv sync --frozen --all-extras --all-groups --exclude-newer "$QUARANTINE_DATE" + uv sync --frozen --all-extras --all-groups - name: Run mypy run: | @@ -113,14 +107,11 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: Set package quarantine date - run: echo "QUARANTINE_DATE=$(date -u -d '2 days ago' +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_ENV" - - name: Install dependencies run: | uv venv . .venv/bin/activate - uv sync --frozen --all-extras --all-groups --exclude-newer "$QUARANTINE_DATE" + uv sync --frozen --all-extras --all-groups - name: Run tests run: | diff --git a/pyproject.toml b/pyproject.toml index 7001467..96ea5b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,7 @@ examples = [ [tool.uv] default-groups = ["dev", "examples"] +exclude-newer = "2 days" [tool.uv.workspace] members = ["tac"] diff --git a/uv.lock b/uv.lock index c4e5481..fd0d52b 100644 --- a/uv.lock +++ b/uv.lock @@ -6,6 +6,10 @@ resolution-markers = [ "python_full_version < '3.15'", ] +[options] +exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. +exclude-newer-span = "P2D" + [[package]] name = "aiohappyeyeballs" version = "2.6.1" From efb1336ccdc26de543ab50f5aafb0b88ec83294d Mon Sep 17 00:00:00 2001 From: Ryan Rishi Date: Fri, 5 Jun 2026 12:43:36 -0400 Subject: [PATCH 4/4] ci: add comment explaining exclude-newer policy Co-Authored-By: Claude Opus 4.6 --- pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 96ea5b3..963d381 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,11 @@ examples = [ [tool.uv] default-groups = ["dev", "examples"] +# Supply-chain hardening: refuse to resolve any package version uploaded to +# PyPI within the last 2 days, quarantining freshly-published (potentially +# compromised) releases. Recorded in uv.lock, so `uv sync --frozen` installs +# the vetted locked versions without re-resolving; the cooldown only gates +# `uv lock` / `uv add` (i.e. intentional dependency updates). exclude-newer = "2 days" [tool.uv.workspace]