From cd1d8167718c46f8ee362fcb41adb889d3741014 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 19 Mar 2026 15:39:27 -0400 Subject: [PATCH 01/10] Pin actions to SHA hashes with version comments --- .github/workflows/dependabot-auto-merge.yml | 2 +- .github/workflows/release.yml | 24 ++++++------- .github/workflows/scorecard.yml | 4 +-- .github/workflows/security.yml | 26 +++++++------- .github/workflows/test.yml | 40 ++++++++++----------- 5 files changed, 48 insertions(+), 48 deletions(-) diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index b285a859..06608374 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Fetch Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2 + uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2.5.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4383346e..c324ee20 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,24 +33,24 @@ jobs: env: BASECAMP_NO_KEYRING: "1" steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: 'go.mod' - name: Install golangci-lint - uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9 + uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 with: version: v2.9.0 install-only: true - name: Cache BATS id: cache-bats - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: /usr/local/libexec/bats-core key: bats-1.11.0 @@ -112,13 +112,13 @@ jobs: models: read attestations: write steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Generate token for Homebrew tap id: sdk-token - uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ vars.RELEASE_CLIENT_ID }} private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} @@ -127,7 +127,7 @@ jobs: permission-contents: write - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: 'go.mod' @@ -140,7 +140,7 @@ jobs: uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0 - name: Install Syft - uses: anchore/sbom-action/download-syft@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0 + uses: anchore/sbom-action/download-syft@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0.23.1 - name: Build changelog context run: | @@ -226,7 +226,7 @@ jobs: MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} - name: Install GoReleaser - uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7 + uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0 with: distribution: goreleaser version: 'v2.14.1' @@ -278,10 +278,10 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Nix - uses: cachix/install-nix-action@1ca7d21a94afc7c957383a2d217460d980de4934 # v31 + uses: cachix/install-nix-action@1ca7d21a94afc7c957383a2d217460d980de4934 # v31.10.1 - name: Build and verify run: | @@ -303,7 +303,7 @@ jobs: contents: read issues: write steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Generate token for skills repo id: skills-token diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index fc5a0dc4..151b868b 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -17,7 +17,7 @@ jobs: id-token: write contents: read steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false @@ -33,7 +33,7 @@ jobs: path: results.sarif retention-days: 5 - - uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 + - uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 continue-on-error: true with: sarif_file: results.sarif diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index e2435d46..dd357a77 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -21,7 +21,7 @@ jobs: name: Secret Scanning runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 @@ -38,7 +38,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run Trivy vulnerability scanner (filesystem) uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0 @@ -52,7 +52,7 @@ jobs: output: 'trivy-results.sarif' - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 + uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 if: always() continue-on-error: true # Requires GitHub Advanced Security with: @@ -63,10 +63,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: 'go.mod' @@ -76,7 +76,7 @@ jobs: gosec -tags dev -no-fail -fmt sarif -out gosec-results.sarif ./... - name: Upload gosec scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 + uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 if: always() continue-on-error: true # Requires GitHub Advanced Security with: @@ -90,22 +90,22 @@ jobs: if: github.event_name == 'pull_request' continue-on-error: true # Requires GitHub Advanced Security (not available on all plans) steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 codeql: name: CodeQL Analysis runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: 'go.mod' - name: Initialize CodeQL - uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 + uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 with: languages: go build-mode: manual @@ -117,14 +117,14 @@ jobs: run: go build -tags dev ./... - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 + uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 with: category: codeql-go upload: never output: sarif-results - name: Upload SARIF to GitHub Security tab - uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 + uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 continue-on-error: true # Requires GitHub Advanced Security with: sarif_file: sarif-results diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f50dd992..a9fde1fe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,10 +17,10 @@ jobs: env: BASECAMP_NO_KEYRING: "1" steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: 'go.mod' @@ -53,15 +53,15 @@ jobs: name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: 'go.mod' - name: Run golangci-lint - uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9 + uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 with: version: v2.11.1 args: --build-tags dev @@ -70,10 +70,10 @@ jobs: name: Security runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: 'go.mod' @@ -90,10 +90,10 @@ jobs: env: BASECAMP_NO_KEYRING: "1" steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: 'go.mod' @@ -106,16 +106,16 @@ jobs: env: BASECAMP_NO_KEYRING: "1" steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: 'go.mod' - name: Cache BATS id: cache-bats - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: /usr/local/libexec/bats-core key: bats-1.11.0 @@ -135,12 +135,12 @@ jobs: env: BASECAMP_NO_KEYRING: "1" steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: 'go.mod' @@ -169,7 +169,7 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'pull_request' steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check for skill or eval changes id: filter @@ -182,7 +182,7 @@ jobs: - name: Set up Ruby if: steps.filter.outputs.skill == 'true' - uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1 + uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 with: ruby-version: '3.3' @@ -205,7 +205,7 @@ jobs: env: BASECAMP_NO_KEYRING: "1" steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 2 @@ -224,7 +224,7 @@ jobs: - name: Set up Go if: steps.filter.outputs.bench == 'true' - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: 'go.mod' @@ -234,7 +234,7 @@ jobs: - name: Download previous benchmark baseline if: steps.filter.outputs.bench == 'true' - uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: benchmarks-baseline.txt key: benchmarks-baseline-${{ github.sha }} @@ -268,7 +268,7 @@ jobs: - name: Cache benchmark baseline if: steps.filter.outputs.bench == 'true' - uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 + uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: benchmarks-baseline.txt key: benchmarks-baseline-${{ github.sha }} From 2953c72b0ef5084d5dd356fa8240137ced60de67 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 19 Mar 2026 15:40:54 -0400 Subject: [PATCH 02/10] Fix high-severity zizmor findings - Fix bot-conditions in dependabot-auto-merge.yml: use verified github.event.pull_request.user.login instead of spoofable github.actor - Suppress dangerous-triggers in ai-labeler.yml, labeler.yml, and sensitive-change-gate.yml: pull_request_target is required for write access to PRs from forks; none of these workflows check out or execute PR code - Suppress cache-poisoning in release.yml: GitHub Actions caches are branch-isolated so fork PRs cannot write to the cache used by tag-push workflows --- .github/workflows/ai-labeler.yml | 2 +- .github/workflows/dependabot-auto-merge.yml | 2 +- .github/workflows/labeler.yml | 2 +- .github/workflows/release.yml | 6 +++--- .github/workflows/sensitive-change-gate.yml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ai-labeler.yml b/.github/workflows/ai-labeler.yml index 3085c539..206b43d7 100644 --- a/.github/workflows/ai-labeler.yml +++ b/.github/workflows/ai-labeler.yml @@ -1,7 +1,7 @@ name: Classify PR on: - pull_request_target: + pull_request_target: # zizmor: ignore[dangerous-triggers] -- required for write access to PRs from forks; workflow only calls trusted reusable workflows, no PR code is checked out or executed types: [opened, synchronize, reopened] concurrency: diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 06608374..b3ea61d4 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -9,7 +9,7 @@ permissions: jobs: auto-merge: runs-on: ubuntu-latest - if: github.actor == 'dependabot[bot]' + if: github.event.pull_request.user.login == 'dependabot[bot]' steps: - name: Fetch Dependabot metadata id: metadata diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 955c4d8b..0d7b8594 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -1,7 +1,7 @@ name: Label PRs on: - pull_request_target: + pull_request_target: # zizmor: ignore[dangerous-triggers] -- required for write access to PRs from forks; workflow only runs trusted actions/labeler action, no PR code is checked out or executed types: [opened, synchronize, reopened] permissions: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c324ee20..28781dcc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,7 +38,7 @@ jobs: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 # zizmor: ignore[cache-poisoning] -- cache is branch-isolated; fork PRs cannot write to the cache used by tag-push workflows with: go-version-file: 'go.mod' @@ -50,7 +50,7 @@ jobs: - name: Cache BATS id: cache-bats - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 # zizmor: ignore[cache-poisoning] -- cache is branch-isolated; fork PRs cannot write to the cache used by tag-push workflows with: path: /usr/local/libexec/bats-core key: bats-1.11.0 @@ -127,7 +127,7 @@ jobs: permission-contents: write - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 # zizmor: ignore[cache-poisoning] -- cache is branch-isolated; fork PRs cannot write to the cache used by tag-push workflows with: go-version-file: 'go.mod' diff --git a/.github/workflows/sensitive-change-gate.yml b/.github/workflows/sensitive-change-gate.yml index e9736ae4..d3abf2bb 100644 --- a/.github/workflows/sensitive-change-gate.yml +++ b/.github/workflows/sensitive-change-gate.yml @@ -1,7 +1,7 @@ name: Sensitive Change Gate on: - pull_request_target: + pull_request_target: # zizmor: ignore[dangerous-triggers] -- required for write access to PRs from forks; workflow only calls a trusted reusable workflow, no PR code is checked out or executed types: [opened, synchronize, reopened] permissions: From 5569c9fd18f2926f1220867c66fb1393302bc194 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 19 Mar 2026 15:42:04 -0400 Subject: [PATCH 03/10] Pin actions and add persist-credentials: false via pinact Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/release.yml | 6 ++++++ .github/workflows/security.yml | 9 +++++++++ .github/workflows/test.yml | 14 ++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 28781dcc..c63130bb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,6 +36,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 + persist-credentials: false - name: Set up Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 # zizmor: ignore[cache-poisoning] -- cache is branch-isolated; fork PRs cannot write to the cache used by tag-push workflows @@ -115,6 +116,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 + persist-credentials: false - name: Generate token for Homebrew tap id: sdk-token @@ -279,6 +281,8 @@ jobs: contents: read steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Install Nix uses: cachix/install-nix-action@1ca7d21a94afc7c957383a2d217460d980de4934 # v31.10.1 @@ -304,6 +308,8 @@ jobs: issues: write steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Generate token for skills repo id: skills-token diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index dd357a77..a6f31a1d 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -24,6 +24,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 + persist-credentials: false - name: Install gitleaks run: | @@ -39,6 +40,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Run Trivy vulnerability scanner (filesystem) uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0 @@ -64,6 +67,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 @@ -91,6 +96,8 @@ jobs: continue-on-error: true # Requires GitHub Advanced Security (not available on all plans) steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 codeql: @@ -98,6 +105,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a9fde1fe..34dc341d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,6 +18,8 @@ jobs: BASECAMP_NO_KEYRING: "1" steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 @@ -54,6 +56,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 @@ -71,6 +75,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 @@ -91,6 +97,8 @@ jobs: BASECAMP_NO_KEYRING: "1" steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 @@ -107,6 +115,8 @@ jobs: BASECAMP_NO_KEYRING: "1" steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 @@ -138,6 +148,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 + persist-credentials: false - name: Set up Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 @@ -170,6 +181,8 @@ jobs: if: github.event_name == 'pull_request' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Check for skill or eval changes id: filter @@ -208,6 +221,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 2 + persist-credentials: false - name: Check for benchmark-relevant changes id: filter From 8a54edc56194da61172080a90f73ee3b3c9cb21d Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 19 Mar 2026 15:44:08 -0400 Subject: [PATCH 04/10] Address medium zizmor findings: excessive-permissions, secrets-outside-env - security.yml: set permissions: {} at workflow level and scope per job - test.yml: suppress secrets-outside-env for skill-eval job (fork PRs don't receive secrets so untrusted code can't access the key; environment protection would block PR-triggered runs) --- .github/workflows/security.yml | 18 ++++++++++++++---- .github/workflows/test.yml | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index a6f31a1d..00068565 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -11,15 +11,14 @@ on: workflow_call: # Allow release.yml to invoke the full security suite workflow_dispatch: -permissions: - contents: read - security-events: write - pull-requests: read +permissions: {} jobs: secrets: name: Secret Scanning runs-on: ubuntu-latest + permissions: + contents: read steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -37,6 +36,9 @@ jobs: trivy: name: Trivy Security Scan runs-on: ubuntu-latest + permissions: + contents: read + security-events: write steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -64,6 +66,9 @@ jobs: gosec: name: Go Security Checker runs-on: ubuntu-latest + permissions: + contents: read + security-events: write steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -94,6 +99,8 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'pull_request' continue-on-error: true # Requires GitHub Advanced Security (not available on all plans) + permissions: + contents: read steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -103,6 +110,9 @@ jobs: codeql: name: CodeQL Analysis runs-on: ubuntu-latest + permissions: + contents: read + security-events: write steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 34dc341d..1fc5f648 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -202,7 +202,7 @@ jobs: - name: Run skill evals if: steps.filter.outputs.skill == 'true' env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} # zizmor: ignore[secrets-outside-env] -- fork PRs don't receive secrets so untrusted code never sees the key; adding an environment would block PR-triggered runs run: | if [ -z "$ANTHROPIC_API_KEY" ]; then echo "::warning::ANTHROPIC_API_KEY not configured, skipping skill evals" From f906897fd7f577c8deacbb6bff33eb82f42e2c89 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 19 Mar 2026 15:44:34 -0400 Subject: [PATCH 05/10] Address low zizmor findings: dependabot-cooldown Increase default-days to 10 for all ecosystem entries to give a 10-day waiting period after version publication before dependabot proposes it. --- .github/dependabot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f04f54a9..1ff7a9a4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,7 +17,7 @@ updates: patterns: - "*" cooldown: - default-days: 2 + default-days: 10 semver-major-days: 7 semver-minor-days: 3 semver-patch-days: 2 @@ -38,6 +38,6 @@ updates: patterns: - "*" cooldown: - default-days: 2 + default-days: 10 commit-message: prefix: "ci" From 8bbdebf954444e4e17c173a5526b6e0d6c120699 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 19 Mar 2026 15:45:00 -0400 Subject: [PATCH 06/10] Add zizmor CI job to test workflow Add lint-actions job near the existing lint job in test.yml, running actionlint and zizmor to keep GitHub Actions workflows audited in CI. --- .github/workflows/test.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1fc5f648..4626de3e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -70,6 +70,23 @@ jobs: version: v2.11.1 args: --build-tags dev + lint-actions: + name: GitHub Actions audit + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Run actionlint + uses: rhysd/actionlint@393031adb9afb225ee52ae2ccd7a5af5525e03e8 # v1.7.11 + + - name: Run zizmor + uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2 + with: + advanced-security: false + security: name: Security runs-on: ubuntu-latest From c3281eaccfa10e184e6e4a57d8716620aea7ad3b Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 19 Mar 2026 16:59:49 -0400 Subject: [PATCH 07/10] Harden GitHub Actions: set workflow-level permissions to deny-all Replace overly broad workflow-level permissions blocks with `permissions: {}` and move permissions to per-job scope, following least-privilege principle. --- .github/workflows/ai-labeler.yml | 6 +----- .github/workflows/dependabot-auto-merge.yml | 7 ++++--- .github/workflows/direct-push-alert.yml | 4 +--- .github/workflows/labeler.yml | 7 ++++--- .github/workflows/release.yml | 7 +------ .github/workflows/sensitive-change-gate.yml | 4 +--- 6 files changed, 12 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ai-labeler.yml b/.github/workflows/ai-labeler.yml index 206b43d7..bee8b7d6 100644 --- a/.github/workflows/ai-labeler.yml +++ b/.github/workflows/ai-labeler.yml @@ -8,11 +8,7 @@ concurrency: group: classify-pr-${{ github.event.pull_request.number }} cancel-in-progress: true -permissions: - contents: read - issues: write - models: read - pull-requests: write +permissions: {} jobs: classify: diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index b3ea61d4..b39cf2e0 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -2,13 +2,14 @@ name: Dependabot Auto-Merge on: pull_request -permissions: - contents: write - pull-requests: write +permissions: {} jobs: auto-merge: runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write if: github.event.pull_request.user.login == 'dependabot[bot]' steps: - name: Fetch Dependabot metadata diff --git a/.github/workflows/direct-push-alert.yml b/.github/workflows/direct-push-alert.yml index 8e711197..943082fe 100644 --- a/.github/workflows/direct-push-alert.yml +++ b/.github/workflows/direct-push-alert.yml @@ -4,9 +4,7 @@ on: push: branches: [main] -permissions: - contents: read - issues: write +permissions: {} jobs: alert: diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 0d7b8594..9df17352 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -4,13 +4,14 @@ on: pull_request_target: # zizmor: ignore[dangerous-triggers] -- required for write access to PRs from forks; workflow only runs trusted actions/labeler action, no PR code is checked out or executed types: [opened, synchronize, reopened] -permissions: - contents: read - pull-requests: write +permissions: {} jobs: label: runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write steps: - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c63130bb..86b6c444 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,12 +9,7 @@ concurrency: group: release-${{ github.ref }} cancel-in-progress: false -permissions: - contents: write - id-token: write - security-events: write - pull-requests: read - models: read +permissions: {} jobs: security: diff --git a/.github/workflows/sensitive-change-gate.yml b/.github/workflows/sensitive-change-gate.yml index d3abf2bb..4cd94cef 100644 --- a/.github/workflows/sensitive-change-gate.yml +++ b/.github/workflows/sensitive-change-gate.yml @@ -4,9 +4,7 @@ on: pull_request_target: # zizmor: ignore[dangerous-triggers] -- required for write access to PRs from forks; workflow only calls a trusted reusable workflow, no PR code is checked out or executed types: [opened, synchronize, reopened] -permissions: - contents: read - pull-requests: write +permissions: {} jobs: gate: From 7f04353b2fc1f21995d4d7ced52f9059c689a9be Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 19 Mar 2026 17:30:08 -0400 Subject: [PATCH 08/10] Fix shellcheck SC2086: quote variable expansions in workflow scripts --- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 86b6c444..4918e0fb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -285,7 +285,7 @@ jobs: - name: Build and verify run: | STORE_PATH=$(nix build --no-link --print-out-paths) - $STORE_PATH/bin/basecamp --version + "$STORE_PATH/bin/basecamp" --version sync-skills: name: Sync skills diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4626de3e..84a8c9b6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -279,10 +279,10 @@ jobs: - name: Compare benchmarks if: steps.filter.outputs.bench == 'true' && hashFiles('benchmarks-baseline.txt') != '' run: | - echo "## Benchmark Comparison" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - benchstat benchmarks-baseline.txt benchmarks.txt >> $GITHUB_STEP_SUMMARY 2>&1 || true - echo '```' >> $GITHUB_STEP_SUMMARY + echo "## Benchmark Comparison" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + benchstat benchmarks-baseline.txt benchmarks.txt >> "$GITHUB_STEP_SUMMARY" 2>&1 || true + echo '```' >> "$GITHUB_STEP_SUMMARY" - name: Check for significant regression if: steps.filter.outputs.bench == 'true' && hashFiles('benchmarks-baseline.txt') != '' From 69207e20a4c118e06848effa37302e9e40b605ac Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 19 Mar 2026 17:34:25 -0400 Subject: [PATCH 09/10] Fix ref-version-mismatch: update create-github-app-token comment to v3.0.0 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4918e0fb..42ae50d7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -308,7 +308,7 @@ jobs: - name: Generate token for skills repo id: skills-token - uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ vars.RELEASE_CLIENT_ID }} private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} From 538e1466e9175c84381bf27adf28cedf724d7dde Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Fri, 20 Mar 2026 02:20:49 -0700 Subject: [PATCH 10/10] Address PR review: tighten Dependabot gate, normalize permissions, add local workflow lint - Dependabot auto-merge: require both github.actor and user.login checks to prevent human-triggered events from re-entering the approve/merge path - test.yml: deny-all at workflow level, explicit contents:read per job (consistent with the pattern established in all other workflow files) - Makefile: add lint-actions target (actionlint + zizmor) wired into check, fail hard when tools are missing with cross-platform install hints - make tools: install actionlint via go install, zizmor via brew/pacman - bin/setup: full tool provisioning including workflow linters --- .github/workflows/dependabot-auto-merge.yml | 2 +- .github/workflows/test.yml | 21 +++++++++++++++++++-- Makefile | 19 ++++++++++++++++++- bin/setup | 2 +- 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index b39cf2e0..c09e5961 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -10,7 +10,7 @@ jobs: permissions: contents: write pull-requests: write - if: github.event.pull_request.user.login == 'dependabot[bot]' + if: github.actor == 'dependabot[bot]' && github.event.pull_request.user.login == 'dependabot[bot]' # zizmor: ignore[bot-conditions] -- dual check is intentional: actor validates current trigger, user.login validates PR origin; on:pull_request (not pull_request_target) so actor is set by GitHub based on who pushed steps: - name: Fetch Dependabot metadata id: metadata diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 84a8c9b6..cee346bb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,13 +7,14 @@ on: branches: [main] workflow_dispatch: -permissions: - contents: read +permissions: {} jobs: test: name: Tests runs-on: ubuntu-latest + permissions: + contents: read env: BASECAMP_NO_KEYRING: "1" steps: @@ -54,6 +55,8 @@ jobs: lint: name: Lint runs-on: ubuntu-latest + permissions: + contents: read steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -73,6 +76,8 @@ jobs: lint-actions: name: GitHub Actions audit runs-on: ubuntu-latest + permissions: + contents: read steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -90,6 +95,8 @@ jobs: security: name: Security runs-on: ubuntu-latest + permissions: + contents: read steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -110,6 +117,8 @@ jobs: race-check: name: Race Detection runs-on: ubuntu-latest + permissions: + contents: read env: BASECAMP_NO_KEYRING: "1" steps: @@ -128,6 +137,8 @@ jobs: integration: name: Integration Tests runs-on: ubuntu-latest + permissions: + contents: read env: BASECAMP_NO_KEYRING: "1" steps: @@ -159,6 +170,8 @@ jobs: cli-surface: name: CLI Surface Check runs-on: ubuntu-latest + permissions: + contents: read env: BASECAMP_NO_KEYRING: "1" steps: @@ -195,6 +208,8 @@ jobs: skill-eval: name: Skill Evals runs-on: ubuntu-latest + permissions: + contents: read if: github.event_name == 'pull_request' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -230,6 +245,8 @@ jobs: benchmarks: name: Benchmarks runs-on: ubuntu-latest + permissions: + contents: read if: github.event_name == 'push' && github.ref == 'refs/heads/main' continue-on-error: true env: diff --git a/Makefile b/Makefile index e85edd7d..ce8416c8 100644 --- a/Makefile +++ b/Makefile @@ -329,7 +329,15 @@ check-smoke-coverage: build # Run all checks (local CI gate) .PHONY: check -check: fmt-check vet lint test test-e2e check-naming check-surface check-skill-drift check-bare-groups check-smoke-coverage provenance-check tidy-check +check: fmt-check vet lint lint-actions test test-e2e check-naming check-surface check-skill-drift check-bare-groups check-smoke-coverage provenance-check tidy-check + +# Lint GitHub Actions workflows (requires actionlint + zizmor) +.PHONY: lint-actions +lint-actions: + @command -v actionlint >/dev/null || (echo "Install actionlint: make tools (or: go install github.com/rhysd/actionlint/cmd/actionlint@latest)" && exit 1) + @command -v zizmor >/dev/null || (echo "Install zizmor: https://docs.zizmor.sh/installation/" && exit 1) + actionlint + zizmor . # Full pre-flight for release: check + replace-check + vuln + race + surface compat .PHONY: release-check @@ -458,6 +466,14 @@ tools: $(GOCMD) install golang.org/x/perf/cmd/benchstat@latest $(GOCMD) install github.com/zricethezav/gitleaks/v8@latest @command -v jq >/dev/null 2>&1 || echo "NOTE: jq is also required (install via your package manager)" + @command -v actionlint >/dev/null 2>&1 || { echo "Installing actionlint..."; $(GOCMD) install github.com/rhysd/actionlint/cmd/actionlint@latest; } + @command -v zizmor >/dev/null 2>&1 || { \ + echo "Installing zizmor..."; \ + if command -v brew >/dev/null 2>&1; then brew install zizmor; \ + elif command -v pacman >/dev/null 2>&1; then sudo pacman -S --noconfirm zizmor; \ + else echo "Could not auto-install zizmor: https://docs.zizmor.sh/installation/" && exit 1; \ + fi; \ + } # Run skill evals (requires ANTHROPIC_API_KEY and Ruby) @@ -516,6 +532,7 @@ help: @echo " fmt Format code" @echo " fmt-check Check code formatting" @echo " lint Run golangci-lint" + @echo " lint-actions Lint GitHub Actions workflows (actionlint + zizmor)" @echo " tidy-check Verify go.mod/go.sum are tidy" @echo " check Run all checks (local CI gate)" @echo " check-surface Generate CLI surface snapshot (validates --help --agent output)" diff --git a/bin/setup b/bin/setup index c399912f..1b78d1b5 100755 --- a/bin/setup +++ b/bin/setup @@ -10,7 +10,7 @@ fi # Install Go via mise (reads .mise.toml) mise install -# Install dev tools (golangci-lint, govulncheck, etc.) +# Install dev tools (golangci-lint, govulncheck, actionlint, zizmor, etc.) make tools # Build and install the binary