From 6da754508d35800d25eaa1474feecc5e50f322b2 Mon Sep 17 00:00:00 2001 From: Claude Code Bot Date: Tue, 8 Sep 2026 18:30:01 -0700 Subject: [PATCH] ci: pin third-party actions to SHAs, persist-credentials: false, seed zizmor.yml; use the shell variable in the cask sed Wave 2 (zizmor) remediation. The checkout pin's "# v6" comment was wrong -- that SHA is v7.0.1 -- which fails the online ref-version-mismatch audit. Corrected, along with the gh-release pin's major-only comment. The Homebrew cask sed interpolated ${{ env.SHA256 }} directly into the run block (template-injection). SHA256 already reaches the step through GITHUB_ENV, so the shell variable does the same job without the expansion. Claude-Session: https://claude.ai/code/session_019HDRKLQNv82SEBd4zGpcXf --- .github/workflows/release.yml | 14 +++- zizmor.yml | 130 ++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 zizmor.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 93f0d15..4d2b5db 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,13 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # Nothing in this job consumes the credential checkout would persist: + # action-gh-release authenticates via the GITHUB_TOKEN env var, and + # the Homebrew tap push below uses HOMEBREW_TAP_TOKEN embedded in a + # clone URL for a different repo (zizmor: artipacked). + persist-credentials: false - name: Build universal binary run: ./build.sh @@ -33,7 +39,9 @@ jobs: echo "SHA256=$(shasum -a 256 ProgressIndicator-${VERSION}.tar.gz | cut -d' ' -f1)" >> $GITHUB_ENV - name: Create GitHub release - uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3 + # zizmor: ignore[superfluous-actions] `gh release` would not carry the + # asset upload and templated body this step relies on; out of scope here. + uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 with: files: ProgressIndicator-*.tar.gz body: | @@ -65,7 +73,7 @@ jobs: # Update the cask file sed -i '' "s/version \".*\"/version \"${VERSION}\"/" Casks/progress-indicator.rb - sed -i '' "s/sha256 \".*\"/sha256 \"${{ env.SHA256 }}\"/" Casks/progress-indicator.rb + sed -i '' "s/sha256 \".*\"/sha256 \"${SHA256}\"/" Casks/progress-indicator.rb # Commit and push changes git config user.name "GitHub Actions" diff --git a/zizmor.yml b/zizmor.yml new file mode 100644 index 0000000..0bcb326 --- /dev/null +++ b/zizmor.yml @@ -0,0 +1,130 @@ +# zizmor configuration for repos consuming the smartwatermelon workflow set. +# +# Copy this to the root of any repo that uses the caller stubs from this +# repo's README, alongside your pre-commit hook. Without it, zizmor's blanket +# hash-pin policy reports ~9 high findings against a byte-identical standard +# caller stub, and the only workaround is `SKIP=zizmor` on every commit. +# +# That workaround is the actual hazard this file exists to remove. Routinely +# skipping the security linter is what let anthropics/claude-code-action sit +# at v1.0.70 for 123 releases while carrying GHSA-8q5r-mmjf-575q (see #123). +# A linter people bypass by habit protects nothing. +# +# Every entry below is a *documented policy decision*, not a convenience +# mute. Each one is justified against the code. Findings that reflect a real +# gap are deliberately left visible — see the note on third-party actions. + +rules: + # --------------------------------------------------------------------- + # unpinned-uses + # --------------------------------------------------------------------- + # zizmor's default is a blanket hash-pin requirement: every `uses:` must + # name a commit SHA. That is correct for third-party actions and wrong for + # first-party reusable workflows, and this repo deliberately treats the two + # classes differently: + # + # Third-party actions -> SHA-pin, plus dependabot.yml to keep the + # (actions/checkout, ...) pins current. We do not control upstream, so + # a repointed tag would run arbitrary code on + # the next trigger. + # + # First-party reusable -> floating tag (@v3). We control this repo, + # workflows (this repo) its branch protection, and who moves the + # tag. Floating refs are what make coordinated + # fleet remediation possible at all. + # + # The second rule is not a relaxation — it is load-bearing. When + # GHSA-8q5r-mmjf-575q was patched here, the fix reached consumers by + # repointing one tag. The ~19 repos that had pinned an exact @v3.1.0 + # silently received nothing, because immutable tags cannot carry a fix + # published after they were cut. Hash-pinning a first-party ref has the + # same effect, permanently. + # + # `ref-pin` still requires *a* ref — `@main` or a bare repo reference is + # rejected. It only lifts the hash requirement. + unpinned-uses: + config: + policies: + # First-party: tag refs are the convention (see README "Versioning"). + smartwatermelon/github-workflows/*: ref-pin + # Everything else keeps the strict default. This line matters: it is + # what keeps genuine third-party findings visible. A blanket ignore + # here would also hide, e.g., `actions/checkout@v7` in a repo's own + # workflows — which is a real gap worth fixing, not policy. + "*": hash-pin + + # --------------------------------------------------------------------- + # excessive-permissions + # --------------------------------------------------------------------- + # The caller stubs declare workflow-level permissions because the reusable + # workflows require them. They are not aspirational or copy-pasted: + # + # contents: read - checkout and diff reading + # pull-requests: write - posting and minimizing review comments + # issues: write - the inline-comment API path + # id-token: write - OIDC exchange for the app token + # + # `dependabot-auto-merge.yml` is in the list for the same reason but needs + # a different pair — `contents: write` to merge and `pull-requests: write` + # to approve. Both are inherent to what that workflow does; a read-only + # auto-merger is a contradiction. Note it deliberately runs with no + # `actions/checkout` (enforced by this repo's `guard-no-checkout` job), so + # the write scopes never combine with executing PR-controlled code. + # + # Removing any of them does not narrow the blast radius; it produces a + # `startup_failure` before the job runs. GitHub also does not let a caller + # grant a called workflow more than the called workflow declares for + # itself, so the effective ceiling is set in the reusable workflow, not + # here. + # + # Scoped to the three standard caller filenames only. A repo's own + # workflows are still audited normally — if `validate.yml` or `release.yml` + # asks for more than it needs, that finding still fires. + # + # These match on FILENAME, not on which reusable workflow is called, so the + # coupling is by naming convention. Note especially that `claude.yml` is the + # caller for `claude-assistant.yml` — the names differ, which is easy to + # misread as an omission. A consumer who names a caller anything else (say + # `claude-assistant-caller.yml`) gets excessive-permissions findings with no + # explanation; the fix is to rename the caller to the standard filename, or + # to add the local name here. See #142 and the README's zizmor section. + excessive-permissions: + ignore: + - claude-blocking-review.yml # caller for claude-blocking-review.yml + - claude.yml # caller for claude-assistant.yml + - dependabot-auto-merge.yml # caller for dependabot-auto-merge.yml + - standards-check.yml # caller for standards-check.yml + + # A cooldown deliberately DELAYS applying action updates. This repo was + # burned by exactly that delay: claude-code-action sat at v1.0.70 for 123 + # releases and stayed vulnerable to GHSA-8q5r-mmjf-575q with no PR opened + # (see #123 and the note at the top of .github/dependabot.yml). Adding a + # cooldown here would re-introduce the lag that incident was about, so the + # absence of one is a decision, not an oversight. + # + # This file also serves as the fleet-wide CI fallback policy: + # standards-check.yml uses it for any consuming repo that has no zizmor.yml + # of its own, so this ignore applies fleet-wide by design, not only here. + dependabot-cooldown: + ignore: + - dependabot.yml + + # zizmor advises `$/...` for a same-repo reusable workflow call. GitHub and + # actionlint both reject that form — actionlint reports "not following the + # format owner/repo/path@ref nor ./path/to/workflow.yml" — so `./...` is the + # only syntax that actually runs. Verified against zizmor 1.30 / actionlint + # on 2026-09-08. + self-repository: + ignore: + - self-review.yml + - self-standards-check.yml + + # markdownlint-cli2 and yamllint have no lockfile to install from — they are + # single pinned CLI tools, not project dependencies. standards-check.yml + # pins an exact version of each (MARKDOWNLINT_CLI2_VERSION, + # YAMLLINT_VERSION), and the three binary downloads next to them are + # additionally SHA256-verified. A lockfile would add a second place for the + # version to drift without removing the install. + adhoc-packages: + ignore: + - standards-check.yml