From adca1cf510f5a0cf611cfb88c6d540ba1d1136f2 Mon Sep 17 00:00:00 2001 From: Dimitri Yatsenko Date: Fri, 6 Nov 2026 08:42:54 -0600 Subject: [PATCH 1/3] ci: pin actions to commit SHAs and run workflows with read-only tokens Pin every action in the lint, test, and stale workflows to the commit its current tag resolves to, so a moved or compromised tag cannot change what runs. Lint and test get a read-only GITHUB_TOKEN, and their checkouts do not persist the token in the working copy. --- .../workflows/close_inactive_issues_prs.yaml | 2 +- .github/workflows/lint.yaml | 14 ++++++++----- .github/workflows/test.yaml | 20 +++++++++++++------ 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.github/workflows/close_inactive_issues_prs.yaml b/.github/workflows/close_inactive_issues_prs.yaml index bb2eba036..4c68e30e0 100644 --- a/.github/workflows/close_inactive_issues_prs.yaml +++ b/.github/workflows/close_inactive_issues_prs.yaml @@ -11,7 +11,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@v9 + - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} operations-per-run: 500 # API rate limit diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index e7e6dc2ae..c5cffa4ea 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -10,20 +10,24 @@ on: - "**" # every branch - "!gh-pages" # exclude gh-pages branch - "!stage*" # exclude branches beginning with stage +permissions: + contents: read jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 # enforce the same check as pre-commit # but only run important checks - - uses: pre-commit/action@v3.0.1 + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 with: extra_args: codespell --all-files - - uses: pre-commit/action@v3.0.1 + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 with: extra_args: ruff --all-files - - uses: pre-commit/action@v3.0.1 + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 with: extra_args: ruff-format --all-files diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a3f2bc5c8..934eebc16 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -24,6 +24,9 @@ on: - "pixi.lock" - ".github/workflows/test.yaml" +permissions: + contents: read + jobs: test: runs-on: ubuntu-latest @@ -40,10 +43,12 @@ jobs: mysql-image: ["datajoint/mysql:8.0", "mysql:8.4"] name: test (${{ matrix.environment }}, mysql=${{ matrix.mysql-image }}) steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false - name: Set up pixi - uses: prefix-dev/setup-pixi@v0.9.3 + uses: prefix-dev/setup-pixi@82d477f15f3a381dbcc8adc1206ce643fe110fb7 # v0.9.3 with: cache: true locked: false @@ -58,10 +63,12 @@ jobs: unit-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false - name: Set up pixi - uses: prefix-dev/setup-pixi@v0.9.3 + uses: prefix-dev/setup-pixi@82d477f15f3a381dbcc8adc1206ce643fe110fb7 # v0.9.3 with: cache: true locked: false @@ -84,12 +91,13 @@ jobs: python-version: ["3.10", "3.14", "3.15"] name: unit-tests-windows (py${{ matrix.python-version }}) steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: fetch-depth: 0 # hatch-vcs derives the version from git history/tags + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: ${{ matrix.python-version }} From b2b4b5a7a872964d1b4c6d8a53f2ed8325fdafe1 Mon Sep 17 00:00:00 2001 From: Dimitri Yatsenko Date: Fri, 6 Nov 2026 09:25:12 -0600 Subject: [PATCH 2/3] ci: run pre-commit directly so every lint action is pinned by SHA pre-commit/action calls actions/cache by tag and installs an unpinned pre-commit. Run the same steps directly: pre-commit pinned to a version and actions/cache pinned by SHA. --- .github/workflows/lint.yaml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index c5cffa4ea..bad589202 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -21,13 +21,14 @@ jobs: persist-credentials: false - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 # enforce the same check as pre-commit - # but only run important checks - - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 + # but only run important checks. + # These steps are what pre-commit/action does, written out so that every + # action is pinned by SHA (pre-commit/action calls actions/cache by tag). + - run: python -m pip install pre-commit==4.7.0 + - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: - extra_args: codespell --all-files - - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 - with: - extra_args: ruff --all-files - - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 - with: - extra_args: ruff-format --all-files + path: ~/.cache/pre-commit + key: pre-commit-3|${{ env.pythonLocation }}|${{ hashFiles('.pre-commit-config.yaml') }} + - run: pre-commit run --show-diff-on-failure --color=always codespell --all-files + - run: pre-commit run --show-diff-on-failure --color=always ruff --all-files + - run: pre-commit run --show-diff-on-failure --color=always ruff-format --all-files From 5b256aef930896adae7374feb9da189474f38b2e Mon Sep 17 00:00:00 2001 From: Dimitri Yatsenko Date: Fri, 6 Nov 2026 09:25:36 -0600 Subject: [PATCH 3/3] ci: declare workflow permissions after the name, clear of the triggers PR 1588 adds a workflow_call trigger just before jobs:, where the permissions block was, so the two PRs conflicted. --- .github/workflows/lint.yaml | 4 ++-- .github/workflows/test.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index bad589202..456b254c7 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -1,4 +1,6 @@ name: Lint +permissions: + contents: read on: push: branches: @@ -10,8 +12,6 @@ on: - "**" # every branch - "!gh-pages" # exclude gh-pages branch - "!stage*" # exclude branches beginning with stage -permissions: - contents: read jobs: lint: runs-on: ubuntu-latest diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 934eebc16..3794f1a2a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,5 +1,8 @@ name: Test +permissions: + contents: read + on: push: branches: @@ -24,9 +27,6 @@ on: - "pixi.lock" - ".github/workflows/test.yaml" -permissions: - contents: read - jobs: test: runs-on: ubuntu-latest