From f47802d54ceafb29881506b5c5e9206cf5891aca Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sat, 28 Mar 2026 09:13:56 +0100 Subject: [PATCH 1/3] ci: add zizmor workflow hardening --- .github/workflows/main.yml | 24 ++++++++---- .github/workflows/publish-to-pypi.yml | 47 +++++++++++++---------- .github/workflows/update-plugin-list.yml | 14 +++---- .github/workflows/zizmor.yml | 48 ++++++++++++++++++++++++ .gitignore | 2 + justfile | 4 ++ zizmor.yml | 5 +++ 7 files changed, 108 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/zizmor.yml create mode 100644 zizmor.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 313cf2b17..9d218ed06 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,20 +14,26 @@ on: - '*' merge_group: +permissions: {} + jobs: run-type-checking: name: Run tests for type-checking runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v6 - - uses: astral-sh/setup-uv@v7 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 with: enable-cache: true - name: Install just - uses: extractions/setup-just@v3 + uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b # v3 - name: Install graphviz run: | sudo apt-get update @@ -38,6 +44,8 @@ jobs: name: Run tests for ${{ matrix.os }} on ${{ matrix.python-version }} runs-on: ${{ matrix.os }} + permissions: + contents: read strategy: fail-fast: false @@ -46,13 +54,15 @@ jobs: python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] steps: - - uses: actions/checkout@v6 - - uses: astral-sh/setup-uv@v7 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 with: enable-cache: true python-version: ${{ matrix.python-version }} - name: Install just - uses: extractions/setup-just@v3 + uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b # v3 - if: matrix.os == 'ubuntu-latest' run: | @@ -71,7 +81,7 @@ jobs: run: uv run --group test pytest --cov=src --cov=tests --cov-report=xml -n auto - name: Upload test coverage reports to Codecov with GitHub Action - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5 - name: Run tests with lowest resolution if: matrix.python-version == '3.10' && matrix.os == 'ubuntu-latest' diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 8614f87fe..a56134cc3 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -2,27 +2,28 @@ name: Publish Python 🐍 distribution 📦 to PyPI on: push +permissions: {} + jobs: build: name: Build distribution 📦 runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v6 - - name: Set up Python - uses: actions/setup-python@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - python-version: "3.x" - - name: Install pypa/build - run: >- - python3 -m - pip install - build - --user + persist-credentials: false + - name: Install uv + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 + with: + enable-cache: true + python-version: "3.13" - name: Build a binary wheel and a source tarball - run: python3 -m build + run: uv build - name: Store the distribution packages - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: python-package-distributions path: dist/ @@ -41,12 +42,14 @@ jobs: steps: - name: Download all the dists - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: python-package-distributions path: dist/ + - name: Install uv + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 - name: Publish distribution 📦 to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + run: uv publish github-release: name: >- @@ -62,12 +65,12 @@ jobs: steps: - name: Download all the dists - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: python-package-distributions path: dist/ - name: Sign the dists with Sigstore - uses: sigstore/gh-action-sigstore-python@v3.2.0 + uses: sigstore/gh-action-sigstore-python@a5caf349bc536fbef3668a10ed7f5cd309a4b53d # v3.2.0 with: inputs: >- ./dist/*.tar.gz @@ -75,17 +78,21 @@ jobs: - name: Create GitHub Release env: GITHUB_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ github.ref_name }} + REPOSITORY: ${{ github.repository }} run: >- gh release create - '${{ github.ref_name }}' - --repo '${{ github.repository }}' + "$RELEASE_TAG" + --repo "$REPOSITORY" --notes "" - name: Upload artifact signatures to GitHub Release env: GITHUB_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ github.ref_name }} + REPOSITORY: ${{ github.repository }} # Upload to GitHub Release using the `gh` CLI. `dist/` contains the built # packages, and the sigstore-produced signatures and certificates. run: >- gh release upload - '${{ github.ref_name }}' dist/** - --repo '${{ github.repository }}' + "$RELEASE_TAG" dist/** + --repo "$REPOSITORY" diff --git a/.github/workflows/update-plugin-list.yml b/.github/workflows/update-plugin-list.yml index 8a1ab205a..045ae928d 100644 --- a/.github/workflows/update-plugin-list.yml +++ b/.github/workflows/update-plugin-list.yml @@ -19,22 +19,18 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 + persist-credentials: false - - name: Setup Python - uses: actions/setup-python@v6 + - name: Install uv + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 with: python-version: 3.12 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install packaging httpx tabulate[widechars] tqdm - - name: Update Plugin List - run: python scripts/update_plugin_list.py + run: uv run --group plugin-list python scripts/update_plugin_list.py - name: Create Pull Request uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 000000000..9e959bc18 --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,48 @@ +name: zizmor + +on: + push: + branches: + - main + pull_request: + branches: + - '*' + schedule: + - cron: '0 7 * * 1' + workflow_dispatch: + +permissions: {} + +jobs: + zizmor: + name: Scan GitHub Actions + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + + - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 + with: + enable-cache: true + python-version: '3.13' + + - name: Run zizmor + run: uvx --from zizmor zizmor --format=github . + + - name: Generate SARIF report + if: always() + run: uvx --from zizmor zizmor --format=sarif --no-exit-codes . > zizmor.sarif + + - name: Upload SARIF report + if: > + always() && + (github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository) + uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4 + with: + sarif_file: zizmor.sarif diff --git a/.gitignore b/.gitignore index d23644a41..cc448a6f6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ coverage.* .ipynb_checkpoints .tox .vscode +.claude +.codex _build __pycache__ _generated diff --git a/justfile b/justfile index 933336838..3671b67f1 100644 --- a/justfile +++ b/justfile @@ -18,6 +18,10 @@ typing: lint: uvx prek run -a +# Run static analysis for GitHub Actions +zizmor: + uvx --from zizmor zizmor . + # Run all checks (format, lint, typing, test) check: lint typing test diff --git a/zizmor.yml b/zizmor.yml new file mode 100644 index 000000000..a42b3ba35 --- /dev/null +++ b/zizmor.yml @@ -0,0 +1,5 @@ +rules: + dependabot-cooldown: + ignore: + # Keep security update PRs immediate instead of delaying them with cooldowns. + - dependabot.yml From ff39b0d5fe24740b9d2bf489e2987174954d447b Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sat, 28 Mar 2026 09:16:17 +0100 Subject: [PATCH 2/3] ci: add dependabot cooldown --- .github/dependabot.yml | 4 ++++ zizmor.yml | 5 ----- 2 files changed, 4 insertions(+), 5 deletions(-) delete mode 100644 zizmor.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index be4ca5388..f0b9eafae 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,6 +5,8 @@ updates: directory: "/" schedule: interval: "weekly" + cooldown: + default-days: 7 groups: github-actions: patterns: @@ -13,3 +15,5 @@ updates: directory: "/" schedule: interval: "weekly" + cooldown: + default-days: 7 diff --git a/zizmor.yml b/zizmor.yml deleted file mode 100644 index a42b3ba35..000000000 --- a/zizmor.yml +++ /dev/null @@ -1,5 +0,0 @@ -rules: - dependabot-cooldown: - ignore: - # Keep security update PRs immediate instead of delaying them with cooldowns. - - dependabot.yml From 7c3e897df2fca4e873b5c4366debd572c6646f94 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sat, 28 Mar 2026 09:25:58 +0100 Subject: [PATCH 3/3] docs: update changelog for zizmor hardening --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32f0f7621..b9dc6988d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and ## Unreleased +- [#836](https://github.com/pytask-dev/pytask/pull/836) hardens GitHub Actions + workflows with zizmor, pinned action SHAs, explicit permissions, and a dedicated + code-scanning upload workflow. - [#830](https://github.com/pytask-dev/pytask/pull/830) replaces the internal `networkx` dependency with a pytask-owned DAG implementation, lazy-loads `networkx` only for DAG export and visualization, and makes the `networkx`