From 1d6d8704851f2d8ccbe71c1ae168fa6e49766a32 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Apr 2026 14:47:14 +0000 Subject: [PATCH 1/6] Add zizmor pre-commit hook for GitHub Actions security auditing https://claude.ai/code/session_011EyCivxz6Z1UJVsienHLQe --- .pre-commit-config.yaml | 4 ++++ project_name/.pre-commit-config.yaml.jinja | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 07a2a63..32d80ba 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -147,6 +147,10 @@ repos: rev: v1.7.12 hooks: - id: actionlint + - repo: https://github.com/woodruffw/zizmor-pre-commit + rev: v1.23.1 + hooks: + - id: zizmor # shell scripts - repo: https://github.com/shellcheck-py/shellcheck-py diff --git a/project_name/.pre-commit-config.yaml.jinja b/project_name/.pre-commit-config.yaml.jinja index 6704ac6..61ba497 100644 --- a/project_name/.pre-commit-config.yaml.jinja +++ b/project_name/.pre-commit-config.yaml.jinja @@ -219,6 +219,10 @@ repos: rev: hooks: - id: actionlint + - repo: https://github.com/woodruffw/zizmor-pre-commit + rev: + hooks: + - id: zizmor # Shell scripts - repo: https://github.com/shellcheck-py/shellcheck-py From d6826740c71089223e02a66fb1bf2a96e32d4543 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Apr 2026 15:23:48 +0000 Subject: [PATCH 2/6] fix: resolve zizmor security audit findings in CI workflows - Pin all GitHub Actions to SHA hashes (actions/checkout, setup-uv, cache, pre-commit/action, create-pull-request) - Fix template injection vulnerability by using env vars for github.actor - Add workflow-level permissions: {} to restrict default token scope - Add dependabot cooldown configuration https://claude.ai/code/session_013DBSzYMmxuKo9vsjMwXzJQ --- .github/dependabot.yml | 2 ++ .github/workflows/uv-tests.yml | 18 +++++++++++------- project_name/.github/dependabot.yml | 2 ++ project_name/.github/workflows/deps-update.yml | 6 +++--- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f6faee6..8e5296d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,6 +4,8 @@ updates: directory: "/" schedule: interval: "weekly" + cooldown: + default-days: 7 groups: github-actions: patterns: diff --git a/.github/workflows/uv-tests.yml b/.github/workflows/uv-tests.yml index f5437bd..2bf988e 100644 --- a/.github/workflows/uv-tests.yml +++ b/.github/workflows/uv-tests.yml @@ -10,6 +10,8 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: {} + env: COLUMNS: 200 # Many color libraries just need this variable to be set to any value. @@ -21,10 +23,10 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - - uses: pre-commit/action@v3.0.1 + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 copier-template-tester: name: copier-template-tester (uv ${{ matrix.uv-version }}) @@ -34,21 +36,23 @@ jobs: matrix: uv-version: ["0.7.3", "latest"] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7 with: version: ${{ matrix.uv-version }} - name: Use correct Python version in older uv version if: ${{ matrix.uv-version == '0.7.3' }} run: sed -i 's/14/13/g' ctt.toml copier.yml .python-version - name: Configure Git + env: + GH_ACTOR: ${{ github.actor }} run: | - git config --global user.email "${{ github.actor }}@users.noreply.github.com" - git config --global user.name "${{ github.actor }}" + git config --global user.email "${GH_ACTOR}@users.noreply.github.com" + git config --global user.name "${GH_ACTOR}" - name: Cache pre-commit - uses: actions/cache@v5 + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: path: ~/.cache/pre-commit key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml', 'project_name/.pre-commit-config.yaml.jinja') }} diff --git a/project_name/.github/dependabot.yml b/project_name/.github/dependabot.yml index e66bc1d..b614fc4 100644 --- a/project_name/.github/dependabot.yml +++ b/project_name/.github/dependabot.yml @@ -4,6 +4,8 @@ updates: directory: / schedule: interval: weekly + cooldown: + default-days: 7 groups: github-actions: patterns: ['*'] diff --git a/project_name/.github/workflows/deps-update.yml b/project_name/.github/workflows/deps-update.yml index 7b3d769..f6cfa76 100644 --- a/project_name/.github/workflows/deps-update.yml +++ b/project_name/.github/workflows/deps-update.yml @@ -11,10 +11,10 @@ jobs: pull-requests: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7 - name: Sync dependencies run: uv sync - name: Update dependencies @@ -32,7 +32,7 @@ jobs: uv run just deps-list-outdated 2>&1 || true echo 'EOF' >> "$GITHUB_OUTPUT" - name: Create Pull Request - uses: peter-evans/create-pull-request@v8 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8 with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: "⬆️ Update project dependencies" From a88f3b9c51f375350cec4744fbd77961c007369f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Apr 2026 15:30:40 +0000 Subject: [PATCH 3/6] fix: use explicit contents:read permission instead of empty block permissions: {} removes all scopes including contents:read, which breaks actions/checkout on private repos. Grant the minimal required permission explicitly. https://claude.ai/code/session_013DBSzYMmxuKo9vsjMwXzJQ --- .github/workflows/uv-tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/uv-tests.yml b/.github/workflows/uv-tests.yml index 2bf988e..eef91d9 100644 --- a/.github/workflows/uv-tests.yml +++ b/.github/workflows/uv-tests.yml @@ -10,7 +10,8 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -permissions: {} +permissions: + contents: read env: COLUMNS: 200 From 3457e8def8634e087d5ea5a7e53089060bbb5753 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Apr 2026 15:36:47 +0000 Subject: [PATCH 4/6] fix: resolve zizmor findings in generated project workflow templates - Pin all GitHub Actions to SHA hashes in ci.yml, weekly-ci.yml, and build-and-publish.yml templates - Move issues:write permission from workflow level to job level in weekly-ci.yml (least-privilege principle) https://claude.ai/code/session_013DBSzYMmxuKo9vsjMwXzJQ --- project_name/.github/workflows/ci.yml.jinja | 40 +++++++++---------- .../.github/workflows/weekly-ci.yml.jinja | 19 +++++---- ...i %}build-and-publish.yml{% endif %}.jinja | 12 +++--- 3 files changed, 37 insertions(+), 34 deletions(-) diff --git a/project_name/.github/workflows/ci.yml.jinja b/project_name/.github/workflows/ci.yml.jinja index 8e52aa1..84adaf2 100644 --- a/project_name/.github/workflows/ci.yml.jinja +++ b/project_name/.github/workflows/ci.yml.jinja @@ -24,16 +24,16 @@ jobs: format-python: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false {%- if format_tool == "black" %} - - uses: psf/black@stable + - uses: psf/black@35ea67920b7f6ac8e09be1c47278752b1e827f76 # stable with: jupyter: {{ "true" if contains_jupyter_files else "false" }} use_pyproject: true {%- elif format_tool == "ruff" %} - - uses: astral-sh/ruff-action@v3 + - uses: astral-sh/ruff-action@146cd51f235777a9bc491eead18e0d5a4b05406a # v3 with: args: format --check {%- endif %} @@ -41,28 +41,28 @@ jobs: ruff-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - - uses: astral-sh/ruff-action@v3 + - uses: astral-sh/ruff-action@146cd51f235777a9bc491eead18e0d5a4b05406a # v3 pre-commit: env: COLUMNS: 120 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - - uses: pre-commit/action@v3.0.1 + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 mypy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7 - name: Install packages run: >- uv sync @@ -80,10 +80,10 @@ jobs: dependencies: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7 - name: Install packages run: >- uv sync @@ -127,10 +127,10 @@ jobs: runs-on: ${{ matrix.os }} name: test-${{ matrix.python-version }}-${{ matrix.resolution }}-${{ matrix.os }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7 with: python-version: ${{ matrix.python-version }} cache-suffix: ${{ matrix.resolution }} @@ -160,12 +160,12 @@ jobs: --junitxml=junit.xml - name: Upload test results to Codecov if: ${{ !cancelled() && steps.run_tests.conclusion != 'skipped' }} - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6 with: token: ${{ secrets.CODECOV_TOKEN }} report_type: test_results - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6 with: token: ${{ secrets.CODECOV_TOKEN }} {%- endraw %} @@ -195,10 +195,10 @@ jobs: runs-on: ${{ matrix.os }} name: test-${{ matrix.python-version }}-${{ matrix.resolution }}-${{ matrix.os }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7 with: python-version: ${{ matrix.python-version }} cache-suffix: ${{ matrix.resolution }} @@ -233,10 +233,10 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7 - name: Install packages run: >- uv sync @@ -274,6 +274,6 @@ jobs: - pylint runs-on: ubuntu-latest steps: - - uses: re-actors/alls-green@release/v1 + - uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1 with: jobs: {{ "${{ toJSON(needs) }}" }} diff --git a/project_name/.github/workflows/weekly-ci.yml.jinja b/project_name/.github/workflows/weekly-ci.yml.jinja index 0a38ac3..8a3dd5c 100644 --- a/project_name/.github/workflows/weekly-ci.yml.jinja +++ b/project_name/.github/workflows/weekly-ci.yml.jinja @@ -7,7 +7,6 @@ on: permissions: contents: read - issues: write env: # Many color libraries just need this variable to be set to any value. @@ -19,11 +18,13 @@ jobs: test: runs-on: ubuntu-latest timeout-minutes: 30 + permissions: + issues: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7 with: python-version: '3.{{ python_max }}' enable-cache: false @@ -42,7 +43,7 @@ jobs: run: uv run --no-sync pytest - name: Create failure issue if: failure() - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | // Check for existing test failure issues @@ -92,11 +93,13 @@ jobs: pip-audit: runs-on: ubuntu-latest timeout-minutes: 10 + permissions: + issues: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7 - name: Export packages run: >- uv export @@ -106,13 +109,13 @@ jobs: -o requirements.txt --no-emit-local --locked - - uses: pypa/gh-action-pip-audit@v1.1.0 + - uses: pypa/gh-action-pip-audit@1220774d901786e6f652ae159f7b6bc8fea6d266 # v1.1.0 with: inputs: requirements.txt require-hashes: true - name: Create failure issue if: failure() - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | // Check for existing audit failure issues diff --git a/project_name/.github/workflows/{% if in_pypi %}build-and-publish.yml{% endif %}.jinja b/project_name/.github/workflows/{% if in_pypi %}build-and-publish.yml{% endif %}.jinja index d8f5d38..8a009df 100644 --- a/project_name/.github/workflows/{% if in_pypi %}build-and-publish.yml{% endif %}.jinja +++ b/project_name/.github/workflows/{% if in_pypi %}build-and-publish.yml{% endif %}.jinja @@ -30,10 +30,10 @@ jobs: needs: ci runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - - uses: hynek/build-and-inspect-python-package@v2 + - uses: hynek/build-and-inspect-python-package@e6f4945c542cb46284a23bdb29121f4593636894 # v2 publish-to-testpypi: if: github.event_name == 'push' @@ -48,12 +48,12 @@ jobs: id-token: write steps: - name: Download package distributions - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: Packages path: dist/ - name: Publish distribution to TestPyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 with: repository-url: https://test.pypi.org/legacy/ @@ -70,9 +70,9 @@ jobs: id-token: write steps: - name: Download package distributions - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: Packages path: dist/ - name: Publish distribution to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 From 677eea3726c9be40828b06b900a5e438ede67967 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Apr 2026 15:45:13 +0000 Subject: [PATCH 5/6] fix: suppress secrets-outside-env zizmor warning for CODECOV_TOKEN CODECOV_TOKEN is a non-sensitive upload token that doesn't warrant requiring GitHub Environments in every generated project. Explicitly suppress the zizmor finding with inline ignore comments. https://claude.ai/code/session_013DBSzYMmxuKo9vsjMwXzJQ --- project_name/.github/workflows/ci.yml.jinja | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project_name/.github/workflows/ci.yml.jinja b/project_name/.github/workflows/ci.yml.jinja index 84adaf2..eb13a3a 100644 --- a/project_name/.github/workflows/ci.yml.jinja +++ b/project_name/.github/workflows/ci.yml.jinja @@ -162,12 +162,12 @@ jobs: if: ${{ !cancelled() && steps.run_tests.conclusion != 'skipped' }} uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6 with: - token: ${{ secrets.CODECOV_TOKEN }} + token: ${{ secrets.CODECOV_TOKEN }} # zizmor: ignore[secrets-outside-env] report_type: test_results - name: Upload coverage reports to Codecov uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6 with: - token: ${{ secrets.CODECOV_TOKEN }} + token: ${{ secrets.CODECOV_TOKEN }} # zizmor: ignore[secrets-outside-env] {%- endraw %} From 10f868970ed84d15ff2ff68048a48b623016014a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Apr 2026 17:05:07 +0000 Subject: [PATCH 6/6] fix: use static git identity instead of github.actor in CI Avoids template injection risk entirely by using a fixed identity for the template tester instead of the dynamic github.actor context. https://claude.ai/code/session_013DBSzYMmxuKo9vsjMwXzJQ --- .github/workflows/uv-tests.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/uv-tests.yml b/.github/workflows/uv-tests.yml index eef91d9..5d4660a 100644 --- a/.github/workflows/uv-tests.yml +++ b/.github/workflows/uv-tests.yml @@ -47,11 +47,9 @@ jobs: if: ${{ matrix.uv-version == '0.7.3' }} run: sed -i 's/14/13/g' ctt.toml copier.yml .python-version - name: Configure Git - env: - GH_ACTOR: ${{ github.actor }} run: | - git config --global user.email "${GH_ACTOR}@users.noreply.github.com" - git config --global user.name "${GH_ACTOR}" + git config --global user.email "template-tester@example.com" + git config --global user.name "template-tester" - name: Cache pre-commit uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: