From ad994e86c936e54a4069fcb86a9dbe0673534004 Mon Sep 17 00:00:00 2001 From: kt Date: Thu, 17 Sep 2026 16:52:22 -0700 Subject: [PATCH] actions: audit workflows with zizmor, and fix what it found Adds zizmor alongside actionlint: actionlint covers syntax, expressions and shell, zizmor covers credential persistence, cache poisoning and unpinned actions. Drops the checkout credential nothing was using, stops the release job restoring a cache that PR builds can write to, and gives dependabot a week of cooldown. The SLSA builder stays on a tag, which it requires. --- .github/dependabot.yml | 6 +++++ .github/workflows/build.yaml | 2 ++ .github/workflows/codeql-analysis.yml | 2 ++ .github/workflows/pr-depscheck.yaml | 2 ++ .github/workflows/pr-golangci-lint.yaml | 2 ++ .github/workflows/pr-tests.yaml | 2 ++ .github/workflows/release.yaml | 8 ++++++- .github/workflows/shellcheck.yaml | 2 ++ .github/workflows/typos.yaml | 2 ++ .github/workflows/workflow-actionlint.yml | 2 ++ .github/workflows/workflow-yamllint.yml | 2 ++ .github/workflows/workflow-zizmor.yml | 27 +++++++++++++++++++++++ makefile | 20 ++++++++++++++--- 13 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/workflow-zizmor.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4ff6898..e73a1ae 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,12 +11,16 @@ updates: codeql-action: patterns: - "github/codeql-action*" + cooldown: + default-days: 7 # dependabot auto-detects the vendor/ directory and runs `go mod tidy` + `go mod vendor` on updates - package-ecosystem: "gomod" directory: "/" schedule: interval: "weekly" + cooldown: + default-days: 7 # dev tools (actionlint, golangci-lint, gofumpt) pinned in .tools/go.mod; tools move slowly, # so only check quarterly @@ -24,3 +28,5 @@ updates: directory: "/.tools" schedule: interval: "quarterly" + cooldown: + default-days: 7 diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e7fa143..f8b2d99 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -19,6 +19,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: ./.go-version diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 5e93cfb..51dcfdd 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -46,6 +46,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/pr-depscheck.yaml b/.github/workflows/pr-depscheck.yaml index d6410cb..b9966e2 100644 --- a/.github/workflows/pr-depscheck.yaml +++ b/.github/workflows/pr-depscheck.yaml @@ -20,6 +20,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: ./.go-version diff --git a/.github/workflows/pr-golangci-lint.yaml b/.github/workflows/pr-golangci-lint.yaml index c947c37..00dd4f1 100644 --- a/.github/workflows/pr-golangci-lint.yaml +++ b/.github/workflows/pr-golangci-lint.yaml @@ -20,6 +20,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: ./.go-version diff --git a/.github/workflows/pr-tests.yaml b/.github/workflows/pr-tests.yaml index 2282c87..4016cf2 100644 --- a/.github/workflows/pr-tests.yaml +++ b/.github/workflows/pr-tests.yaml @@ -22,6 +22,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: ./.go-version diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 49b872f..b32307e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -20,9 +20,11 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 # goreleaser derives the changelog and version from tag history + persist-credentials: false - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: ./.go-version + cache: false - uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 - uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3 with: @@ -44,7 +46,7 @@ jobs: id-token: write # sign the provenance contents: write # upload multiple.intoto.jsonl to the release # the slsa builder must be referenced by tag, not commit hash; it verifies its own ref at runtime - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0 + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0 # zizmor: ignore[unpinned-uses] with: base64-subjects: ${{ needs.goreleaser.outputs.hashes }} upload-assets: true @@ -58,6 +60,8 @@ jobs: attestations: write # actions/attest-build-provenance stores the attestation steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0 - uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 @@ -108,6 +112,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: update tap formula (source build) env: TAG: ${{ github.ref_name }} diff --git a/.github/workflows/shellcheck.yaml b/.github/workflows/shellcheck.yaml index 29c39f2..fcdc8ce 100644 --- a/.github/workflows/shellcheck.yaml +++ b/.github/workflows/shellcheck.yaml @@ -17,5 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false # shellcheck is preinstalled on ubuntu-latest - run: make shellcheck diff --git a/.github/workflows/typos.yaml b/.github/workflows/typos.yaml index bbc99a1..9c561db 100644 --- a/.github/workflows/typos.yaml +++ b/.github/workflows/typos.yaml @@ -21,4 +21,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - run: make typos diff --git a/.github/workflows/workflow-actionlint.yml b/.github/workflows/workflow-actionlint.yml index ad01b29..8ddfe95 100644 --- a/.github/workflows/workflow-actionlint.yml +++ b/.github/workflows/workflow-actionlint.yml @@ -27,6 +27,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: ./.go-version diff --git a/.github/workflows/workflow-yamllint.yml b/.github/workflows/workflow-yamllint.yml index d709057..97f190b 100644 --- a/.github/workflows/workflow-yamllint.yml +++ b/.github/workflows/workflow-yamllint.yml @@ -27,4 +27,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - run: make yamllint diff --git a/.github/workflows/workflow-zizmor.yml b/.github/workflows/workflow-zizmor.yml new file mode 100644 index 0000000..ffc160c --- /dev/null +++ b/.github/workflows/workflow-zizmor.yml @@ -0,0 +1,27 @@ +name: workflow security audit + +# Audits .github/workflows/ with zizmor (template injection, credential persistence, excessive +# permissions, dangerous triggers). Complements actionlint, which covers syntax, expressions and +# shell but not these. make downloads the version pinned in the makefile. + +on: + push: + branches: [ 'main' ] + pull_request: + types: ['opened', 'synchronize'] + +permissions: + contents: read + +concurrency: + group: 'workflow-zizmor-${{ github.head_ref || github.ref }}' + cancel-in-progress: true + +jobs: + workflow-zizmor: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - run: make zizmor diff --git a/makefile b/makefile index 890bf9e..c22dcc5 100644 --- a/makefile +++ b/makefile @@ -13,14 +13,16 @@ GOFUMPT=$(TOOLS_BIN)/gofumpt GOLANGCI_LINT=$(TOOLS_BIN)/golangci-lint # non-Go tools also live in .tools/bin at pinned versions, but the pins are here (dependabot -# cannot bump them): shellcheck and typos are static binaries downloaded from their github releases, +# cannot bump them): shellcheck, typos and zizmor are static binaries downloaded from their github releases, # yamllint is python installed into a repo-local venv. all rebuild when this makefile changes. SHELLCHECK_VERSION=v0.11.0 TYPOS_VERSION=v1.50.1 YAMLLINT_VERSION=1.38.0 +ZIZMOR_VERSION=v1.30.1 SHELLCHECK=$(TOOLS_BIN)/shellcheck TYPOS=$(TOOLS_BIN)/typos YAMLLINT=$(TOOLS_BIN)/yamllint +ZIZMOR=$(TOOLS_BIN)/zizmor # golangci-lint with the azproviderlint module plugin compiled in (.tools/.custom-gcl.yml); # lint runs use this binary, the plain go.mod one exists to bootstrap `golangci-lint custom` @@ -58,6 +60,14 @@ $(YAMLLINT): makefile @mkdir -p $(TOOLS_BIN) @python3 -m venv .tools/venv && .tools/venv/bin/pip install -q yamllint==$(YAMLLINT_VERSION) && ln -sf ../venv/bin/yamllint $@ +$(ZIZMOR): makefile + @echo "==> downloading zizmor $(ZIZMOR_VERSION)..." + @mkdir -p $(TOOLS_BIN) + @case "$$(uname)" in Darwin) target=apple-darwin;; *) target=unknown-linux-gnu;; esac; \ + arch=$$(uname -m); [ "$$arch" = "arm64" ] && arch=aarch64; \ + curl -sSfL "https://github.com/zizmorcore/zizmor/releases/download/$(ZIZMOR_VERSION)/zizmor-$$arch-$$target.tar.gz" \ + | tar -xz -O zizmor > $@ && chmod +x $@ + default: fmt build all: fmt build @@ -78,7 +88,7 @@ docker: ## Build the ghp-sync docker image docker build --network=host --tag ghp-sync --build-arg GO_VERSION=$(shell cat .go-version) \ --build-arg VERSION=${GIT_VERSION} --build-arg GIT_COMMIT=${GIT_COMMIT} . -tools: $(ACTIONLINT) $(GOFUMPT) $(GOLANGCI_LINT) $(GOLANGCI_LINT_MODULES) $(SHELLCHECK) $(TYPOS) $(YAMLLINT) ## Install all pinned dev tools into .tools/bin +tools: $(ACTIONLINT) $(GOFUMPT) $(GOLANGCI_LINT) $(GOLANGCI_LINT_MODULES) $(SHELLCHECK) $(TYPOS) $(YAMLLINT) $(ZIZMOR) ## Install all pinned dev tools into .tools/bin ##@ Formatting fmt: $(GOFUMPT) $(GOLANGCI_LINT) ## Fix Go formatting (gofmt, gofumpt, goimports) @@ -122,6 +132,10 @@ typos-fix: $(TYPOS) ## Fix spelling mistakes found by typos @echo "==> Fixing typos..." @$(TYPOS) --write-changes +zizmor: $(ZIZMOR) ## Audit GitHub workflows for security issues with zizmor + @echo "==> Auditing workflows with zizmor..." + @$(ZIZMOR) . + depscheck: ## Check that go.mod/go.sum and vendor/ are in sync @echo "==> Checking source code with go mod tidy..." @go mod tidy @@ -147,4 +161,4 @@ test: build ## Run the unit tests (with -race) check-all: build test lint actionlint yamllint shellcheck typos depscheck ## Run build + test + all linters + depscheck -.PHONY: default all help fmt goimports build docker lint lint-fix actionlint yamllint shellcheck typos typos-fix depscheck check-all install tools test +.PHONY: default all help fmt goimports build docker lint lint-fix actionlint yamllint shellcheck typos typos-fix zizmor depscheck check-all install tools test