From 5c93489a967b6654bb0ce735596f43781f93befc Mon Sep 17 00:00:00 2001 From: Alex Abashev Date: Sun, 27 Sep 2026 21:59:35 +0300 Subject: [PATCH 1/6] Do not leave the checkout's token in .git/config zizmor's artipacked audit flagged 13 actions/checkout steps that keep the GITHUB_TOKEN in the repository's .git/config for the rest of the job, where every later step can read it, and an artifact that uploads the checkout would carry it. No job here pushes with git: the release attaches to a tag that is already pushed (skipTag), and the draft release, the dependency graph and the CodeQL results go through the API with their own tokens. Every checkout now sets persist-credentials: false, as the one in zizmor.yml already did. --- .github/workflows/ci.yml | 6 ++++++ .github/workflows/codeql-analysis.yml | 2 ++ .github/workflows/dependency-submission.yml | 2 ++ .github/workflows/release.yml | 8 ++++++++ .github/workflows/sha-pinning-check.yml | 2 ++ 5 files changed, 20 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbdedd806..61d70d3da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install JDK 21 uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 @@ -63,6 +65,8 @@ jobs: out until the wrapper's Gradle supports Java 27. steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install JDK ${{ matrix.jdk }} for the tests id: test-jdk @@ -126,6 +130,8 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false # The jars compile with a Java 21 toolchain, which gradle.properties looks up in JDK21_HOME. On # Linux and macOS Gradle also finds the runner's own JDK 21 by its location, on Windows it does not. diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d875a721d..04a84ac65 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,6 +27,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up JDK 21 uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 diff --git a/.github/workflows/dependency-submission.yml b/.github/workflows/dependency-submission.yml index f25a4ff13..e4c029c9c 100644 --- a/.github/workflows/dependency-submission.yml +++ b/.github/workflows/dependency-submission.yml @@ -12,6 +12,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up a bootstrap JDK uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dbe21dafa..f53532512 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,6 +39,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - name: Install JDK 21 uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 @@ -100,6 +101,8 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false # The Java 21 toolchain for the jars, through JDK21_HOME, as in ci.yml: Gradle does not find the # runner's own JDK 21 on Windows. Installed before GraalVM, so that Gradle still runs on GraalVM. @@ -143,6 +146,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - name: Install JDK 21 uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 @@ -207,6 +211,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - name: Install JDK 21 uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 @@ -244,6 +249,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - name: Install JDK 21 uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 @@ -286,6 +292,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - name: Install JDK 21 uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 @@ -325,6 +332,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - name: Install JDK 21 uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 diff --git a/.github/workflows/sha-pinning-check.yml b/.github/workflows/sha-pinning-check.yml index 16cfdd7fb..aac1aa030 100644 --- a/.github/workflows/sha-pinning-check.yml +++ b/.github/workflows/sha-pinning-check.yml @@ -11,5 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: zgosalvez/github-actions-ensure-sha-pinned-actions@62574f011e0d1967d555a862bd28a7abba8684fe # v5.0.9 From a5b690edab8deb57fd444bbd320c38e6bfdaa7fc Mon Sep 17 00:00:00 2001 From: Alex Abashev Date: Sun, 27 Sep 2026 22:00:18 +0300 Subject: [PATCH 2/6] Give the two workflows without a permissions block the least they need sha-pinning-check.yml and update-pr-branch.yml ran with the default GITHUB_TOKEN permissions, which zizmor (excessive-permissions) and CodeQL (actions/missing-workflow-permissions) both flagged. The pin check only checks the repository out and reads its workflow files, so it gets contents: read. update-pr-branch reads and updates the pull requests with BOT_PAT, the only token the action reads, so the workflow's own token gets no permissions at all. --- .github/workflows/sha-pinning-check.yml | 3 +++ .github/workflows/update-pr-branch.yml | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/.github/workflows/sha-pinning-check.yml b/.github/workflows/sha-pinning-check.yml index aac1aa030..181c6e7bb 100644 --- a/.github/workflows/sha-pinning-check.yml +++ b/.github/workflows/sha-pinning-check.yml @@ -6,6 +6,9 @@ on: push: branches: [ 'main' ] +permissions: + contents: read + jobs: pin-check: runs-on: ubuntu-latest diff --git a/.github/workflows/update-pr-branch.yml b/.github/workflows/update-pr-branch.yml index d0ef7a737..eff6c9c5b 100644 --- a/.github/workflows/update-pr-branch.yml +++ b/.github/workflows/update-pr-branch.yml @@ -7,6 +7,10 @@ on: # Run every hour to catch stuck PRs - cron: '0 * * * *' +# The action reads and updates the pull requests with BOT_PAT alone, so the workflow's own token needs +# no permissions. +permissions: {} + jobs: update: runs-on: ubuntu-latest From e89a83ac131494a3df6e55e29b3a35e8fd63bae4 Mon Sep 17 00:00:00 2001 From: Alex Abashev Date: Sun, 27 Sep 2026 22:00:49 +0300 Subject: [PATCH 3/6] Check who opened the pull request before auto-merging it The Dependabot auto-merge job ran when github.actor was dependabot[bot]. zizmor (bot-conditions) flags that as spoofable: the actor is whoever caused the event, so a run Dependabot triggers on a pull request someone else opened passes the check, and the job then approves the pull request and enables auto-merge. The job now checks github.event.pull_request.user.login, the author of the pull request, which is the condition GitHub's own documentation for Dependabot auto-merge uses. dependabot/fetch-metadata still checks the author and that the commits are Dependabot's before anything is approved. --- .github/workflows/dependabot-automerge.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index 7aab91003..bca4dec03 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -9,7 +9,9 @@ permissions: jobs: dependabot: runs-on: ubuntu-latest - if: github.actor == 'dependabot[bot]' + # Who opened the pull request, not who triggered this run: github.actor is whoever caused the event, + # and a run that Dependabot triggers on someone else's pull request would pass an actor check. + if: github.event.pull_request.user.login == 'dependabot[bot]' steps: - name: Fetch Dependabot metadata id: metadata From 711a99cf34b51ea4306eded7564e3b255ff84b88 Mon Sep 17 00:00:00 2001 From: Alex Abashev Date: Sun, 27 Sep 2026 22:01:10 +0300 Subject: [PATCH 4/6] Pass the test JDK's path to the build as a variable The JDK jobs in ci.yml put ${{ steps.test-jdk.outputs.path }}, the path actions/setup-java reports, straight into the build's shell script. zizmor (template-injection) flags that: a template expansion is pasted into the script before the shell runs it, so a value with shell syntax in it would run as code. The path now reaches the shell as TEST_JDK in the step's environment and is quoted there. The matrix values stay inline: they are fixed in the workflow itself, and zizmor does not flag them. --- .github/workflows/ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61d70d3da..9b2cfdd72 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,11 +89,14 @@ jobs: REASON: ${{ matrix.gradle_args_reason }} run: echo "::notice title=JDK ${{ matrix.jdk }} runs with $GRADLE_ARGS::$REASON" - # Gradle does not look into the runner's tool cache, so it is told where the test JDK is. + # Gradle does not look into the runner's tool cache, so it is told where the test JDK is. The path is + # a step's output, so it reaches the shell as a variable rather than being pasted into the script. - name: Build + env: + TEST_JDK: ${{ steps.test-jdk.outputs.path }} run: >- ./gradlew test -PjavaRuntime=${{ matrix.jdk }} ${{ matrix.gradle_args }} - -Porg.gradle.java.installations.paths=${{ steps.test-jdk.outputs.path }} + -Porg.gradle.java.installations.paths="$TEST_JDK" - name: Publish Test Report uses: mikepenz/action-junit-report@a9170d5795813c01ab4901ffb045b52bab4ab09d # v6.5.0 From 71c8c570dad3d30f51100a16755ceb2f1102b3b6 Mon Sep 17 00:00:00 2001 From: Alex Abashev Date: Sun, 27 Sep 2026 22:01:29 +0300 Subject: [PATCH 5/6] Name the CodeQL action release the pin points to The CodeQL workflow pins github/codeql-action to 1c5b6756, commented as v4. v4 is a moving tag and now points to 2892aa5e (v4.38.2), so zizmor (ref-version-mismatch) reports that the comment does not match the pin. 1c5b6756 is v4.38.1, and the comments now say so. The pinned commit stays the same; Dependabot updates it, and the comment with it. --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 04a84ac65..e4807befe 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -37,7 +37,7 @@ jobs: java-version: '21' - name: Initialize CodeQL - uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4 + uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -48,6 +48,6 @@ jobs: run: ./gradlew --no-build-cache clean compileJava compileTestJava - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4 + uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1 with: category: "/language:${{matrix.language}}" From 58fdede32b79683228461e5946f22982b4c8a168 Mon Sep 17 00:00:00 2001 From: Alex Abashev Date: Sun, 27 Sep 2026 22:02:03 +0300 Subject: [PATCH 6/6] Let Dependabot propose a release only once it is a week old zizmor (dependabot-cooldown) flagged both update entries in dependabot.yml: without a cooldown, Dependabot waits only its implicit three days before it proposes a new version of an action or a Gradle dependency. A compromised or broken release is usually pulled within days, and the auto-merge workflow approves minor and patch updates on its own, so both entries now wait seven days. The cooldown applies to version updates only; security updates are not delayed. --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index bcbfe28b0..1e2948d10 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,13 +1,19 @@ version: 2 +# A release is proposed only once it is a week old: a compromised or broken release is usually pulled +# within days. The cooldown holds back version updates only; security updates still come at once. updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" target-branch: "main" + cooldown: + default-days: 7 - package-ecosystem: "gradle" directory: "/" schedule: interval: "weekly" target-branch: "main" + cooldown: + default-days: 7