From 348ff736fe646ec3844ba269f730f9d84d3858f6 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sun, 25 Oct 2026 10:02:10 -0400 Subject: [PATCH 01/13] ci: run Python 3.8 and 3.9 jobs on `ubuntu-24.04` GitHub is rolling out Ubuntu 26.04 as `ubuntu-latest` gradually across repositories, from 2026-10-19 through 2026-11-19 (see https://github.com/actions/runner-images/issues/14748). This repository now receives it: on 2026-10-25, jobs started at 10:06 UTC got the `ubuntu-24.04` image, and jobs started at 13:33 UTC got `ubuntu-26.04`. `actions/setup-python` takes its interpreters from the `actions/python-versions` manifest, which has Linux x64 builds of Python 3.8 and 3.9 for Ubuntu 24.04 but none for 26.04. The lowest version it has for 26.04 is 3.10. So on 26.04, `setup-python` fails with "The version '3.8' with architecture 'x64' was not found for Ubuntu 26.04" (and likewise for 3.9). This broke six jobs: - `test (smmap, 3.8)`, `test (smmap, 3.9)`, `test (gitdb, 3.8)`, and `test (gitdb, 3.9)` in `dependencies.yml`, which test the vendored `smmap` and `gitdb` packages (both declare `python_requires=">=3.7"`). - `install/import (ubuntu-latest, 3.8)` and `install/import (ubuntu-latest, 3.9)` in `pythonpackage.yml`, which check that GitPython (`requires-python = ">=3.8"`) still installs and imports on those versions. Run just those Ubuntu jobs on `ubuntu-24.04`. Everything else, including the 3.10 jobs in the same matrices, stays on `ubuntu-latest`. In `dependencies.yml`, `include` adds an `os` key only to the 3.8 and 3.9 combinations, and `runs-on` falls back to `ubuntu-latest` where it is unset. In `pythonpackage.yml`, the `ubuntu-latest` 3.8 and 3.9 combinations are swapped for `ubuntu-24.04` ones, so the job names show the image they actually run on. No status checks are required on `main`, so the renamed jobs do not affect merging. Python 3.8 and 3.9 can be installed on 26.04 in other ways: the deadsnakes PPA publishes 3.8.20 and 3.9.25 for 26.04, and uv can install python-build-standalone builds. But these would add a different installation mechanism (and, for deadsnakes, a third-party apt source) for just these jobs, while pinning keeps the same `setup-python` builds that were tested before. This needs revisiting when GitHub retires the `ubuntu-24.04` image, if 3.8 and 3.9 are still tested then. `actionlint` reports no problems with either workflow. Co-authored-by: Claude Opus 5.5 --- .github/workflows/dependencies.yml | 8 +++++++- .github/workflows/pythonpackage.yml | 11 +++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index 4eff39b3c..7d509f969 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -11,12 +11,18 @@ permissions: jobs: test: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os || 'ubuntu-latest' }} strategy: fail-fast: false matrix: project: [smmap, gitdb] python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.13t"] + include: + # setup-python has no builds of these versions for Ubuntu 26.04. + - python-version: "3.8" + os: ubuntu-24.04 + - python-version: "3.9" + os: ubuntu-24.04 steps: - uses: actions/checkout@v7 diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 53e0e08cc..e063e6839 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -251,6 +251,17 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python: ["3.8", "3.9", "3.10"] + exclude: + # setup-python has no builds of these versions for Ubuntu 26.04. + - os: ubuntu-latest + python: "3.8" + - os: ubuntu-latest + python: "3.9" + include: + - os: ubuntu-24.04 + python: "3.8" + - os: ubuntu-24.04 + python: "3.9" env: GIT_PYTHON_BACKEND: cli steps: From b63cce04ca4c1502decbf00c1225e550e2115738 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sun, 25 Oct 2026 10:03:52 -0400 Subject: [PATCH 02/13] ci: pin all actions to full commit hashes Most workflow steps referred to actions by tag, usually a major version tag such as `actions/checkout@v7`. A tag can be moved to another commit at any time, so the code that runs, with the job's token and secrets, could change with no change to this repository. Only `actions/cache` and `astral-sh/setup-uv` were already pinned. Pin every remaining action to a full commit hash, followed by a `# vX.Y.Z` comment naming the exact release, the form Dependabot recognizes and keeps up to date. The hashes and comments were written by `pinact run` (pinact 5.0.1), which resolves each tag and chooses the most specific semver tag pointing to the same commit. So each step still runs the same commit it ran before. For example, `actions/checkout@v7` and `actions/setup-python@v7` became the `v7.0.1` and `v7.1.0` commits, the same commits that recent runs reported downloading for those tags. The existing pins were left as they were. `pinact run --check --verify-comment` now passes, which also confirms that every version comment, including those on the existing pins, names a tag that points to its hash. `actionlint` reports no problems. `zizmor --offline` no longer reports any of the 25 `unpinned-uses` errors it reported before, and reports no new findings. (Its one `unpinned-images` finding, about a container image, is unaffected.) Co-authored-by: Claude Opus 5.5 --- .github/workflows/alpine-test.yml | 2 +- .github/workflows/backend-benchmark.yml | 6 +++--- .github/workflows/codeql.yml | 6 +++--- .github/workflows/cygwin-test.yml | 4 ++-- .github/workflows/dependencies.yml | 6 +++--- .github/workflows/downstream.yml | 2 +- .github/workflows/git-cli.yml | 6 +++--- .github/workflows/lint.yml | 6 +++--- .github/workflows/pythonpackage.yml | 12 ++++++------ 9 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/alpine-test.yml b/.github/workflows/alpine-test.yml index b3558d214..8e33cd721 100644 --- a/.github/workflows/alpine-test.yml +++ b/.github/workflows/alpine-test.yml @@ -32,7 +32,7 @@ jobs: adduser runner docker shell: sh -exo pipefail {0} # Run this as root, not the "runner" user. - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 diff --git a/.github/workflows/backend-benchmark.yml b/.github/workflows/backend-benchmark.yml index d02dc73f1..49f0155a0 100644 --- a/.github/workflows/backend-benchmark.yml +++ b/.github/workflows/backend-benchmark.yml @@ -14,11 +14,11 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 persist-credentials: false - - uses: actions/setup-python@v7 + - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: "3.12" - name: Ensure Git 2.52 or newer @@ -75,7 +75,7 @@ jobs: >> "$GITHUB_STEP_SUMMARY" - name: Retain raw timings and comparison if: always() - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: backend-benchmark path: benchmark-results/ diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index eeb68cd0c..682fb4e44 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -48,7 +48,7 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # Add any setup steps before running the `github/codeql-action/init` action. # This includes steps like installing compilers or runtimes (`actions/setup-node` @@ -58,7 +58,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -90,6 +90,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/cygwin-test.yml b/.github/workflows/cygwin-test.yml index d01676f43..93b3431f4 100644 --- a/.github/workflows/cygwin-test.yml +++ b/.github/workflows/cygwin-test.yml @@ -28,12 +28,12 @@ jobs: git config --global core.autocrlf false # Affects the non-Cygwin git. shell: pwsh # Do this outside Cygwin, to affect actions/checkout. - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - name: Install Cygwin - uses: cygwin/cygwin-install-action@v6 + uses: cygwin/cygwin-install-action@3f0a3f9f988f7e96b8c18098ae05eaec175f5b52 # v6.0.2 with: packages: >- curl gcc-core git libcurl-devel libexpat-devel libiconv-devel diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index 7d509f969..cfb022d03 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -25,10 +25,10 @@ jobs: os: ubuntu-24.04 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - - uses: actions/setup-python@v7 + - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ matrix.python-version }} allow-prereleases: true @@ -49,7 +49,7 @@ jobs: uv-sync: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 9483b6586..ef09b3f76 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -19,7 +19,7 @@ jobs: project: [langchain, mlflow, bandit, swebench, datahub] backend: [gix, cli] steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Ensure Git 2.52 or newer diff --git a/.github/workflows/git-cli.yml b/.github/workflows/git-cli.yml index 567bc9373..3038e5e12 100644 --- a/.github/workflows/git-cli.yml +++ b/.github/workflows/git-cli.yml @@ -15,13 +15,13 @@ jobs: env: GIT_PYTHON_BACKEND: cli steps: - - uses: actions/checkout@v7 - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: git/git ref: v2.52.0 path: .git-source - - uses: actions/setup-python@v7 + - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: '3.12' - name: Build minimum supported Git diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d22640951..2702a97dc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,10 +14,10 @@ jobs: runs-on: ubuntu-slim steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-python@v7 + - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: "3.x" - - uses: pre-commit/action@v3.0.1 + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index e063e6839..79e8d1ff0 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -59,12 +59,12 @@ jobs: shell: bash --noprofile --norc -exo pipefail {0} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v7 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ matrix.python-version }} allow-prereleases: ${{ matrix.experimental }} @@ -97,7 +97,7 @@ jobs: - name: Set up WSL (Windows) if: matrix.os-type == 'windows' - uses: Vampire/setup-wsl@v7.2.0 + uses: Vampire/setup-wsl@4c48163d9f981914dd8caa0fc335fe19cca5bff4 # v7.2.0 with: wsl-version: 1 @@ -232,7 +232,7 @@ jobs: - name: Retain test results and backend operation counts if: always() - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: tests-${{ matrix.os-type }}-${{ matrix.python-version }}-${{ matrix.backend }} path: test-results/ @@ -265,8 +265,8 @@ jobs: env: GIT_PYTHON_BACKEND: cli steps: - - uses: actions/checkout@v7 - - uses: actions/setup-python@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ matrix.python }} - name: Install the supported runtime From 44edd0854696521715d317ca50db5885b30f3df1 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sun, 25 Oct 2026 10:04:13 -0400 Subject: [PATCH 03/13] ci(dependabot): remove obsolete `enable-beta-ecosystems` `enable-beta-ecosystems: true` was added along with the `pre-commit` ecosystem, which Dependabot only supported as a beta ecosystem at the time. Dependabot's `pre-commit` support is no longer in beta, and GitHub's Dependabot options reference now documents `enable-beta-ecosystems` as "Not currently in use". It also lists `pre-commit` among the supported ecosystems with no preview or beta caveat. So the option does nothing here, but suggests that something in this configuration still depends on a beta feature. Remove it. Co-authored-by: Claude Opus 5.5 --- .github/dependabot.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7f803ee43..44e715dd1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,5 +1,4 @@ version: 2 -enable-beta-ecosystems: true updates: - package-ecosystem: github-actions directory: "/" From 86bd1bf8142f9d90e16040f8134e9a19cd6e9c10 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sun, 25 Oct 2026 10:04:27 -0400 Subject: [PATCH 04/13] ci(dependabot): wait 7 days before proposing new versions Dependabot proposed new versions of actions and `pre-commit` hooks on its first scheduled run after their release, waiting only for the 3-day cooldown it now applies by default. A compromised or broken release is often found and pulled within days, so a longer wait gives a better chance of never being offered one. Set `cooldown.default-days: 7` for both ecosystems, so a release is not proposed until 7 days after it is published. GitHub's Dependabot options reference lists both GitHub Actions and `pre-commit` as supporting `default-days`. (Neither supports the per-semver-level `semver-*-days` settings, which are not needed here.) When `pre-commit` updates were first set up, its ecosystem did not support `cooldown`, so it was left out then; now it does. The cooldown applies only to version updates. Dependabot security updates, where enabled, are not delayed by it. Co-authored-by: Claude Opus 5.5 --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 44e715dd1..3a177c87f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,11 +4,15 @@ updates: directory: "/" schedule: interval: weekly + cooldown: + default-days: 7 - package-ecosystem: pre-commit directory: "/" schedule: interval: monthly + cooldown: + default-days: 7 groups: pre-commit: patterns: ["*"] From 6bceffa8c63390cefe62da5520c9850f4723602e Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sun, 25 Oct 2026 10:04:34 -0400 Subject: [PATCH 05/13] ci(dependabot): check for action updates monthly When workflows referred to actions by major version tag, such as `actions/checkout@v7`, Dependabot proposed a change only for a new major version, which is infrequent. Now that every action is pinned to a full commit hash with a `# vX.Y.Z` comment, Dependabot proposes a change for every new release, including minor and patch releases. With a weekly schedule, that would produce many more pull requests than before, most of them for small changes. Check for GitHub Actions updates monthly instead, as is already done for `pre-commit` hooks. Each scheduled run then proposes, for each action, its newest release that is at least 7 days old, so intervening releases of the same action are skipped rather than proposed one by one. Dependabot security updates, where enabled, do not follow this schedule. Co-authored-by: Claude Opus 5.5 --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3a177c87f..e7820bb8d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,7 +3,7 @@ updates: - package-ecosystem: github-actions directory: "/" schedule: - interval: weekly + interval: monthly cooldown: default-days: 7 From c38f750eb35dc3876f8256118a114251ee403a04 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sun, 25 Oct 2026 11:39:50 -0400 Subject: [PATCH 06/13] ci(dependabot): group GitHub Actions updates into one pull request Every action is now pinned to a full commit hash with a `# vX.Y.Z` comment, so Dependabot proposes an update for each new release of each action, not just each new major version. With no `groups` for the `github-actions` ecosystem, each monthly run would open a separate pull request for every action with a new release. About ten actions are in use, so one run could open about ten pull requests at once, each running the whole CI matrix, and each would have to be reviewed and merged on its own. Add a `github-actions` group matching every action, so each monthly run proposes all available action updates together in one pull request. This is how the `pre-commit` ecosystem is already set up here, and how gitoxide groups its `github-actions` updates. The group applies only to version updates, which is the default when `applies-to` is not set. Dependabot security updates, where enabled, are still proposed individually. `check-jsonschema` validates the configuration against the Dependabot schema, and zizmor 1.31.1 at the `pedantic` level reports no findings in it. Co-authored-by: Claude Opus 5.5 --- .github/dependabot.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e7820bb8d..111f77f9f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,6 +6,9 @@ updates: interval: monthly cooldown: default-days: 7 + groups: + github-actions: + patterns: ["*"] - package-ecosystem: pre-commit directory: "/" From 3d3fba990d9d2e3dd8a1a4229cdf94503cb60f11 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sun, 25 Oct 2026 11:09:13 -0400 Subject: [PATCH 07/13] ci: scan workflows with zizmor at the `pedantic` level Nothing checked the GitHub Actions workflows for security problems other than CodeQL's `actions` analysis. That analysis now comes from CodeQL default setup with the default query suite, which never reported the 25 action references pinned only by tag, and which does not run at all on pull requests from forks. Add a workflow that runs zizmor through `zizmorcore/zizmor-action` on pushes to `main`, on pull requests, and on manual dispatch, the same triggers as the other workflows. It uses the action's default GitHub Advanced Security mode, uploading SARIF so that findings appear in the Security tab as code scanning alerts. In that mode the action does not fail on findings, only when zizmor itself fails, including when it finds nothing to scan. This repository is public, so the upload needs no paid features. GitHub blocks CodeQL SARIF uploads while CodeQL default setup is enabled, but that applies only to CodeQL results, not to zizmor's. The job uses the `pedantic` persona, which adds code smells to the default persona's high-signal findings. The `auditor` persona was also considered. On this repository, its only extra findings are 22 `misfeature` findings, one for each step that runs in the custom shells that `alpine-test.yml` and `cygwin-test.yml` deliberately use. zizmor's documentation describes that persona as including findings that are likely false positives. `.github/zizmor.yml` turns off two `pedantic` audits whose findings would not lead to any change here: - `anonymous-definition` (8 findings): workflows and jobs without `name:` are only harder to identify in the Actions UI. zizmor's documentation says the audit has no security impact. - `concurrency-limits` (9 findings): no workflow here needs to cancel or serialize overlapping runs. This is the same zizmor setup as in gitoxide, which uses the `pedantic` persona and turns off the same two audits. It differs in following this repository's workflow triggers and style and in using the `ubuntu-slim` runner, which `zizmor-action` supports since v0.7.0. `zizmor-action` is pinned by `pinact` to v0.7.1, which runs zizmor 1.31.1, rather than to v0.8.0, which was released two days ago. That keeps to the 7-day cooldown now configured for Dependabot. With this configuration, zizmor 1.31.1 run locally at this commit reports 18 findings: 9 `artipacked`, 6 `template-injection`, and one each of `excessive-permissions`, `undocumented-permissions`, and `unpinned-images`. The `unpinned-images` finding is for `alpine:latest` in `alpine-test.yml`. It is correct, but there is not yet a good way to act on it, so it is left to be reported rather than suppressed. `actionlint` reports no problems with the new workflow. Co-authored-by: Claude Opus 5.5 --- .github/workflows/zizmor.yml | 27 +++++++++++++++++++++++++++ .github/zizmor.yml | 5 +++++ 2 files changed, 32 insertions(+) create mode 100644 .github/workflows/zizmor.yml create mode 100644 .github/zizmor.yml diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 000000000..9f4512712 --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,27 @@ +name: zizmor + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: {} # Expanded in the `zizmor` job. + +jobs: + zizmor: + runs-on: ubuntu-slim + + permissions: + security-events: write # Required for uploading SARIF to view in the Security tab. + contents: read # Not needed in public repos. (Kept for private forks/reuploads.) + actions: read # Not needed in public repos. (Kept for private forks/reuploads.) + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - uses: zizmorcore/zizmor-action@85b2b9fb8e3325e7a23d7d0a9d7ba5ae0f999e3e # v0.7.1 + with: + persona: pedantic diff --git a/.github/zizmor.yml b/.github/zizmor.yml new file mode 100644 index 000000000..f7515d023 --- /dev/null +++ b/.github/zizmor.yml @@ -0,0 +1,5 @@ +rules: + anonymous-definition: + disable: true + concurrency-limits: + disable: true From 63f42c926c451e3f5c0ee5fd70eee56eacd7d228 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sun, 25 Oct 2026 11:09:51 -0400 Subject: [PATCH 08/13] ci: stop `actions/checkout` from persisting credentials By default, `actions/checkout` persists the job's `GITHUB_TOKEN` so later Git commands can use it. Since v6, it writes the token to a credentials file under `$RUNNER_TEMP` and adds `includeIf` entries that include that file to the checked-out repository's `.git/config`. Nine checkout steps did this, in `alpine-test.yml`, `codeql.yml`, `cygwin-test.yml`, `dependencies.yml`, `git-cli.yml` (twice, including the `git/git` checkout in `.git-source`), `lint.yml`, and `pythonpackage.yml` (twice). zizmor reports these as `artipacked`. No step in these jobs needs the token after checkout. They push nothing, and their only network Git operations are reads of public repositories, which need no credentials. The other checkouts, in `backend-benchmark.yml`, `downstream.yml`, the `uv-sync` job in `dependencies.yml`, and `zizmor.yml`, already use `persist-credentials: false`. Keeping the token is also riskier here than in most projects. GitPython runs Git operations, and its test suite uses this checkout as a repository: the tests read it, clone it, and run Git commands against it. A test or library bug could then use the token, or expose the configuration that refers to it, whether through test output or a retained artifact such as `pythonpackage.yml`'s test results. The `includeIf` entries and the configuration they include can also change how Git behaves in it, coupling test results to how CI checked out the code. Set `persist-credentials: false` on every checkout that lacked it. It is added to the existing `with:` block where there is one, after the other inputs, as in `backend-benchmark.yml`. The disabled `codeql.yml` workflow is included, so it is safe if it is enabled again. zizmor 1.31.1 at the `pedantic` level no longer reports any `artipacked` findings. `actionlint` reports no problems. Co-authored-by: Claude Opus 5.5 --- .github/workflows/alpine-test.yml | 1 + .github/workflows/codeql.yml | 2 ++ .github/workflows/cygwin-test.yml | 1 + .github/workflows/dependencies.yml | 1 + .github/workflows/git-cli.yml | 3 +++ .github/workflows/lint.yml | 2 ++ .github/workflows/pythonpackage.yml | 3 +++ 7 files changed, 13 insertions(+) diff --git a/.github/workflows/alpine-test.yml b/.github/workflows/alpine-test.yml index 8e33cd721..14506a30a 100644 --- a/.github/workflows/alpine-test.yml +++ b/.github/workflows/alpine-test.yml @@ -35,6 +35,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - name: Set workspace ownership run: | diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 682fb4e44..f0c531667 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -49,6 +49,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false # Add any setup steps before running the `github/codeql-action/init` action. # This includes steps like installing compilers or runtimes (`actions/setup-node` diff --git a/.github/workflows/cygwin-test.yml b/.github/workflows/cygwin-test.yml index 93b3431f4..938a5b740 100644 --- a/.github/workflows/cygwin-test.yml +++ b/.github/workflows/cygwin-test.yml @@ -31,6 +31,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - name: Install Cygwin uses: cygwin/cygwin-install-action@3f0a3f9f988f7e96b8c18098ae05eaec175f5b52 # v6.0.2 diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index cfb022d03..3e04f09de 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -28,6 +28,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/git-cli.yml b/.github/workflows/git-cli.yml index 3038e5e12..b104fd4f4 100644 --- a/.github/workflows/git-cli.yml +++ b/.github/workflows/git-cli.yml @@ -16,11 +16,14 @@ jobs: GIT_PYTHON_BACKEND: cli steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: git/git ref: v2.52.0 path: .git-source + persist-credentials: false - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: '3.12' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2702a97dc..a3a04e078 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,6 +15,8 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 79e8d1ff0..8fce793b5 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -62,6 +62,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 @@ -266,6 +267,8 @@ jobs: GIT_PYTHON_BACKEND: cli steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ matrix.python }} From f130dd3f2b6934194b9148235f17bcf1f553293c Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sun, 25 Oct 2026 11:10:54 -0400 Subject: [PATCH 09/13] ci: fail if any `actions/checkout` step persists credentials Every `actions/checkout` step now sets `persist-credentials: false`. zizmor reports any that do not as `artipacked`, but only as code scanning alerts: it does not fail CI. A checkout that persists the `GITHUB_TOKEN` should fail CI, because it is riskier in this repository than in most. GitPython performs Git operations, and its test suite reads, clones, and runs Git commands against the repository it is tested in. That coupling may be intended or not, and it can let a test or library bug use or expose a persisted token. Add a `check-no-persist-credentials` job to the `Lint` workflow. It sparse-checks out `.github/workflows`, without persisting credentials itself, and uses `yq` to list every step in every workflow file that uses `actions/checkout` without `persist-credentials: false`. This includes a step with no `with:` block, a `with:` block without the input, and any other value. The job fails if anything is listed and prints each match as `file:line: name`. This is the same code as the job of the same name in gitoxide's CI (`.github/workflows/ci.yml`), copied from its current version. The only change is indentation, to match the step style of `lint.yml`. gitoxide's job also gates its `tests-pass` job, but this repository has no such aggregate job. The check does not replace zizmor's `artipacked` audit, which also looks for ways persisted credentials could end up in uploaded artifacts, and whose findings are tracked as code scanning alerts. Run locally with `yq` 4.54.1, the scan reports nothing for the workflows in this commit. In a scratch copy, removing the setting from both checkouts in `pythonpackage.yml` (one with other inputs and one with no `with:` block) and setting it to `true` in `lint.yml` made it report exactly those four steps and fail. A quoted `"false"` is not reported, which is correct, since action inputs are strings and `actions/checkout` treats it the same as `false`. `actionlint` reports two `info`-level SC2086 notes from `shellcheck`, for the unquoted `$GLOB`. Pathname expansion there is intended, as the inline comments say, so they are left as they are in gitoxide. Co-authored-by: Claude Opus 5.5 --- .github/workflows/lint.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a3a04e078..496f4b093 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -23,3 +23,30 @@ jobs: python-version: "3.x" - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 + + # Check that all `actions/checkout` in CI jobs have `persist-credentials: false`. + check-no-persist-credentials: + runs-on: ubuntu-slim + + env: + GLOB: .github/workflows/*.@(yaml|yml) + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + sparse-checkout: ".github/workflows" + - name: List workflows to be scanned + run: | + shopt -s extglob + printf '%s\n' $GLOB # Pathname expansion in $GLOB intended. + - name: Scan workflows + run: | + shopt -s extglob + yq '.jobs.*.steps[] + | select(.uses == "actions/checkout@*" and .with.["persist-credentials"]? != false) + | {"file": filename, "line": line, "name": (.name // .uses)} + | .file + ":" + (.line | tostring) + ": " + .name + ' -- $GLOB >query-output.txt # Pathname expansion in $GLOB intended. + cat query-output.txt + test -z "$( Date: Sun, 25 Oct 2026 11:12:26 -0400 Subject: [PATCH 10/13] ci(codeql): set empty workflow permissions and document the job's zizmor reports two findings in `codeql.yml`: - `excessive-permissions`: the workflow has no top-level `permissions:`, so any job without its own would get the repository's default `GITHUB_TOKEN` permissions. - `undocumented-permissions`: the `analyze` job's `security-events: write` has no comment saying why it is needed. The only job already sets its own permissions, so the first finding describes no current excess. But a job added later without its own `permissions:` would silently get the defaults, and every other workflow here sets top-level permissions. Add `permissions: {}` at the top level, noting that the `analyze` job expands it, and explain that `security-events: write` is needed to upload SARIF results for the Security tab. The job's effective permissions do not change. The wording matches gitoxide's `codeql.yml`, which made the same changes for the same zizmor findings. Unlike there, no comment is added about permissions that the job does not need; those were removed here earlier as unnecessary. The workflow is disabled while CodeQL default setup is enabled, but it is still tracked and may be enabled again. zizmor 1.31.1 at the `pedantic` level no longer reports either finding. `actionlint` reports no problems. Co-authored-by: Claude Opus 5.5 --- .github/workflows/codeql.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f0c531667..143c2de52 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -18,6 +18,8 @@ on: schedule: - cron: '27 10 * * 3' +permissions: {} # Expanded in the `analyze` job. + jobs: analyze: name: Analyze (${{ matrix.language }}) @@ -28,7 +30,7 @@ jobs: # Consider using larger runners or machines with greater resources for possible analysis time improvements. runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} permissions: - security-events: write + security-events: write # Required for uploading SARIF to view in the Security tab. strategy: fail-fast: false From 7a7c288d3e745fc001c0501d4a50887acc48301e Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sun, 25 Oct 2026 11:13:23 -0400 Subject: [PATCH 11/13] ci: pass matrix values to shell scripts through the environment zizmor's `pedantic` persona reports six `template-injection` findings, where `${{ matrix.project }}` and `${{ matrix.backend }}` are expanded directly into `run:` scripts: three steps of the `test` job in `dependencies.yml`, and the test step in `downstream.yml`. These values are constants defined in the workflows' own matrices, so nothing here is attacker-controllable. But template expansion inserts text into the script before the shell parses it, so quotation marks around a `${{ }}` expression do not neutralize quotation marks or other shell syntax inside its value. A shell expansion of an environment variable is not reparsed that way, so when it is double quoted it is always a single word. Changing the style is also preferable to turning off or suppressing the diagnostic, which would then no longer catch an expansion that does carry untrusted data. Pass each value through an environment variable named for its matrix key in upper case (`PROJECT`, `BACKEND`), and expand it double quoted, since neither word splitting nor pathname expansion is intended. Scope each variable by how many steps use it: a value used by more than one step goes in a job-level `env:` just before `steps:`, and a value used by one step goes in that step's `env:`, just before `run:`. So `PROJECT` is set for the whole `test` job in `dependencies.yml`, where three steps use it, while `PROJECT` and `BACKEND` are set only for the one step in `downstream.yml` that uses them. These are the naming, quoting, and scoping conventions gitoxide adopted for the same diagnostic. The commands receive the same arguments as before. No file in this repository reads `PROJECT` or `BACKEND`. `test/downstream/run.py` passes most of its environment through to the downstream projects' test suites, so they will also see these two variables, along with the other variables already set in CI. zizmor 1.31.1 at the `pedantic` level no longer reports any `template-injection` findings. The only remaining finding is the known `unpinned-images` finding for `alpine:latest`. `actionlint` reports no problems with either workflow. Co-authored-by: Claude Opus 5.5 --- .github/workflows/dependencies.yml | 9 ++++++--- .github/workflows/downstream.yml | 5 ++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index 3e04f09de..0e53c1d56 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -24,6 +24,9 @@ jobs: - python-version: "3.9" os: ubuntu-24.04 + env: + PROJECT: ${{ matrix.project }} + steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -37,12 +40,12 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install pytest flake8 ./smmap - if test "${{ matrix.project }}" = gitdb; then python -m pip install ./gitdb; fi + if test "$PROJECT" = gitdb; then python -m pip install ./gitdb; fi - name: Lint - run: flake8 "${{ matrix.project }}/${{ matrix.project }}" --count --select=E9,F63,F7,F82 --show-source --statistics + run: flake8 "$PROJECT/$PROJECT" --count --select=E9,F63,F7,F82 --show-source --statistics - name: Test run: | - if test "${{ matrix.project }}" = gitdb; then + if test "$PROJECT" = gitdb; then GITDB_TEST_GIT_REPO_BASE="$(git rev-parse --git-common-dir)" pytest -o addopts= -v gitdb/gitdb/test else pytest -o addopts= -v smmap/smmap/test diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index ef09b3f76..d3069bab9 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -48,4 +48,7 @@ jobs: version: '0.12.13' enable-cache: false - name: Test latest release against this checkout - run: uv run test/downstream/run.py "${{ matrix.project }}" --backend "${{ matrix.backend }}" + env: + PROJECT: ${{ matrix.project }} + BACKEND: ${{ matrix.backend }} + run: uv run test/downstream/run.py "$PROJECT" --backend "$BACKEND" From 5f44b7eee5bb89ea295deb945622bde179ec10c4 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sun, 25 Oct 2026 11:21:33 -0400 Subject: [PATCH 12/13] ci: run the `uv-sync` job on `ubuntu-slim` Most GitHub-hosted runners are full VMs. `ubuntu-slim` runners are instead single-CPU containers with 5 GB of RAM and a hard 15-minute job timeout. They run unprivileged, so they cannot run Docker containers, which rules out job containers and container actions. GitHub describes them as a lower-cost option for short, lightweight jobs, not for typical CI builds. Being containers, they may also be created and torn down faster than VMs. The jobs here were checked for whether they could reasonably move to `ubuntu-slim`. Only one could: the `uv-sync` job in `dependencies.yml`. It checks out the repository, installs `uv` with `astral-sh/setup-uv` (a Node action), and runs `uv sync --all-extras --all-groups --dry-run` to check that the dependencies and dependency groups resolve. It runs no tests, uses no containers, and took 6 seconds in the most recent run on `main`. The other Linux jobs stay where they are: - Jobs that run test suites, which take minutes and can use more than one CPU: the `pythonpackage.yml` tests (about 7 to 10 minutes), `downstream.yml` (3 to 5 minutes), `git-cli.yml` (which also builds Git), and the `smmap` and `gitdb` tests in `dependencies.yml`. - `alpine-test.yml`, which runs in a job container. - `backend-benchmark.yml`, whose timings would be distorted by a single CPU. - The `install/import` jobs in `pythonpackage.yml`. They are quick, but they run GitPython, and `ubuntu-slim` has Git 2.43.0, below the 2.52 that GitPython requires. - The disabled `codeql.yml`, since CodeQL analysis is not lightweight. `lint.yml` and `zizmor.yml` already use `ubuntu-slim`. On the fork, the job passed on `ubuntu-slim` in 12 seconds. It resolved 110 packages and would download and install 55, the same as in the run on `ubuntu-latest` just before this change. The interpreter `uv` found differs, as expected: CPython 3.12.3 on `ubuntu-slim` and 3.14.4 on `ubuntu-latest` (Ubuntu 26.04). `actionlint` reports no problems. Co-authored-by: Claude Opus 5.5 --- .github/workflows/dependencies.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index 0e53c1d56..1b5f39c83 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -51,7 +51,7 @@ jobs: pytest -o addopts= -v smmap/smmap/test fi uv-sync: - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: From a938d65a5cdc3e1b757abc341eeb98a9e9a76d22 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sun, 25 Oct 2026 11:47:58 -0400 Subject: [PATCH 13/13] ci: remove dead Python 3.8 condition from the mypy step The `Check types with mypy` step in the `test` job of `pythonpackage.yml` ran only `if: matrix.python-version != '3.8'`. That condition was added in #2087, alongside a `python_version >= "3.9"` marker on mypy in `test-requirements.txt`. Since mypy was not installed on older Pythons, the step had to be skipped there. (The same pull request also pinned mypy to 1.18.2, which itself requires Python 3.9 or later.) The condition originally excluded 3.7 as well, and that part was removed along with 3.7 support. Since Python 3.8 through 3.10 were moved out of the `test` job's matrix into the `install/import` job, which does not run mypy, the `test` job runs only on 3.11 through 3.15t. No row of its matrix, and no `include` entry, uses 3.8, so the condition is always true. It suggested that the job still covers 3.8 and did nothing else. Remove it. The step runs in exactly the same jobs as before. The `python_version >= "3.9"` marker on the mypy requirement is left as it is. It is packaging metadata for the `test` extra, not CI configuration. `actionlint` reports no problems, and zizmor 1.31.1 at the `pedantic` level reports no new findings. Co-authored-by: Claude Opus 5.5 --- .github/workflows/pythonpackage.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 8fce793b5..1ca50a599 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -207,7 +207,6 @@ jobs: continue-on-error: true - name: Check types with mypy - if: matrix.python-version != '3.8' run: | mypy --python-version="${PYTHON_VERSION%t}" # Version only, with no "t" for free-threaded. env: