From 3cfde582c39024a45036200d7579cac23e01253e Mon Sep 17 00:00:00 2001 From: Neil Galvin Date: Sun, 30 Aug 2026 13:01:37 +0100 Subject: [PATCH] feat!: migrate secret-scan from gitleaks to betterleaks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit betterleaks is gitleaks' successor, written from scratch by gitleaks' original author after he lost admin control of that repo and its name. MIT, no license key for org use — the same reason this workflow installed the OSS gitleaks binary rather than the paid official action. BREAKING: the `gitleaks-version` input is replaced by `betterleaks-version`. Compatibility is near-total, which is what made the swap cheap. Verified against the v1.8.1 binary rather than the docs: every flag used here exists (--no-banner, --redact, --report-format=sarif, --report-path, --config, --exit-code, --log-opts, git --staged/--pre-commit), release tarballs are named identically in shape and contain a top-level binary on both linux arches, and .gitleaks.toml / GITLEAKS_CONFIG / .gitleaksignore (same fingerprint format) / `gitleaks:allow` are all still honoured as fallbacks. Expect new findings: ~98.6% recall on CredData vs gitleaks' ~70.4%. This repo was exactly that case — full history went from "no leaks" to 1 leak, a low-confidence generic-password match on the GRAFANA_ADMIN_PASSWORD placeholder in examples/README.md. Such matches live in committed history, so an allow-comment can't clear them; .gitleaksignore pins the fingerprint. Adds a `confidence` input as the blunter escape hatch for consumers. Local tooling moves too (mise.toml, lefthook.yml), and the docs move to @v3. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011c5RTvAwJB62bcvuARUqKr --- .github/workflows/secret-scan.yml | 72 +++++++++++-------- .gitleaksignore | 12 ++++ CHANGELOG.md | 71 +++++++++++++++++++ README.md | 12 ++-- examples/README.md | 110 +++++++++++++++--------------- lefthook.yml | 10 +-- mise.toml | 2 +- 7 files changed, 194 insertions(+), 95 deletions(-) create mode 100644 .gitleaksignore diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml index aa6ad1a..4ddfe6b 100644 --- a/.github/workflows/secret-scan.yml +++ b/.github/workflows/secret-scan.yml @@ -1,8 +1,13 @@ -name: Secret scan (gitleaks) +name: Secret scan (betterleaks) -# Installs the OSS gitleaks binary (no license key required, unlike the -# official gitleaks-action which is paid for org use) and scans either -# the PR diff or the full history depending on the trigger. +# Installs the OSS betterleaks binary and scans either the PR diff or the +# full history depending on the trigger. +# +# betterleaks is the successor to gitleaks, written by gitleaks' original +# author after he lost admin control of that project. MIT, no license key +# required for org use (unlike the official gitleaks-action, which is paid). +# It reads .gitleaks.toml / .gitleaksignore / `gitleaks:allow` comments as +# fallbacks, so an existing gitleaks config keeps working unchanged. on: workflow_call: @@ -11,12 +16,21 @@ on: description: "Runner label(s) as a JSON array string (parsed with fromJSON). Pass '[\"self-hosted\", \"linux\", \"x64\"]' to use the self-hosted pool." type: string default: '["ubuntu-latest"]' - gitleaks-version: - # renovate: datasource=github-releases depName=gitleaks/gitleaks + betterleaks-version: + # renovate: datasource=github-releases depName=betterleaks/betterleaks type: string - default: "8.30.1" + default: "1.8.1" config-path: - description: "Path to .gitleaks.toml. Empty = use gitleaks defaults." + description: "Path to .betterleaks.toml (or a legacy .gitleaks.toml). Empty = betterleaks auto-discovers one in the repo root, else uses defaults." + type: string + default: "" + confidence: + description: | + Minimum confidence to report: low, medium or high. Empty (default) + reports every finding. betterleaks detects considerably more than + gitleaks did, so a repo with documentation placeholders that used to + scan clean may surface low-confidence findings; set 'medium' to + filter those, or pin exact fingerprints in .gitleaksignore. type: string default: "" scan-mode: @@ -31,7 +45,7 @@ on: type: boolean default: true upload-artifacts: - description: "Upload the gitleaks SARIF report to GitHub Actions storage. Set false to conserve the account-wide Actions storage quota; the scan still gates the job either way." + description: "Upload the betterleaks SARIF report to GitHub Actions storage. Set false to conserve the account-wide Actions storage quota; the scan still gates the job either way." type: boolean default: true artifact-retention-days: @@ -48,37 +62,39 @@ jobs: steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: - # Full history is required for `gitleaks detect` to traverse commits. + # Full history is required for `betterleaks git` to traverse commits. fetch-depth: 0 - - name: Install gitleaks + - name: Install betterleaks env: - VERSION: ${{ inputs.gitleaks-version }} + VERSION: ${{ inputs.betterleaks-version }} run: | set -euo pipefail arch="$(uname -m)" case "$arch" in - x86_64) gl_arch=x64 ;; - aarch64|arm64) gl_arch=arm64 ;; + x86_64) bl_arch=x64 ;; + aarch64|arm64) bl_arch=arm64 ;; *) echo "unsupported arch: $arch" >&2; exit 1 ;; esac - url="https://github.com/gitleaks/gitleaks/releases/download/v${VERSION}/gitleaks_${VERSION}_linux_${gl_arch}.tar.gz" - curl -sSfL -o /tmp/gitleaks.tar.gz "$url" - tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks - sudo install -m 0755 /tmp/gitleaks /usr/local/bin/gitleaks - gitleaks version + url="https://github.com/betterleaks/betterleaks/releases/download/v${VERSION}/betterleaks_${VERSION}_linux_${bl_arch}.tar.gz" + curl -sSfL -o /tmp/betterleaks.tar.gz "$url" + tar -xzf /tmp/betterleaks.tar.gz -C /tmp betterleaks + sudo install -m 0755 /tmp/betterleaks /usr/local/bin/betterleaks + betterleaks version - - name: Run gitleaks + - name: Run betterleaks env: MODE: ${{ inputs.scan-mode }} CONFIG: ${{ inputs.config-path }} + CONFIDENCE: ${{ inputs.confidence }} FAIL: ${{ inputs.fail-on-finding }} run: | set -euo pipefail # No --verbose: findings still land in the SARIF report and still # fail the job; verbose only floods the log with every scanned commit. - args=(--no-banner --redact --report-format=sarif --report-path=gitleaks.sarif) + args=(--no-banner --redact --report-format=sarif --report-path=betterleaks.sarif) if [ -n "$CONFIG" ]; then args+=(--config="$CONFIG"); fi + if [ -n "$CONFIDENCE" ]; then args+=(--confidence="$CONFIDENCE"); fi [ "$FAIL" = "false" ] && args+=(--exit-code=0) || args+=(--exit-code=1) effective_mode="$MODE" @@ -88,14 +104,14 @@ jobs: case "$effective_mode" in full) - gitleaks detect "${args[@]}" + betterleaks git "${args[@]}" ;; pr-diff) if [ "${{ github.event_name }}" != "pull_request" ]; then echo "scan-mode=pr-diff but event is ${{ github.event_name }} — nothing to scan" exit 0 fi - gitleaks detect "${args[@]}" \ + betterleaks git "${args[@]}" \ --log-opts="${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}" ;; *) @@ -103,15 +119,15 @@ jobs: esac # The SARIF report is a debugging convenience, not the gate — the - # gitleaks step above is what fails the scan on a finding. Don't let an - # artifact-upload failure (e.g. the account-wide Actions storage quota + # betterleaks step above is what fails the scan on a finding. Don't let + # an artifact-upload failure (e.g. the account-wide Actions storage quota # being hit, which recalculates every 6-12h) turn a clean scan red. - name: Upload SARIF - if: always() && inputs.upload-artifacts && hashFiles('gitleaks.sarif') != '' + if: always() && inputs.upload-artifacts && hashFiles('betterleaks.sarif') != '' continue-on-error: true uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: gitleaks-sarif - path: gitleaks.sarif + name: betterleaks-sarif + path: betterleaks.sarif if-no-files-found: ignore retention-days: ${{ inputs.artifact-retention-days }} diff --git a/.gitleaksignore b/.gitleaksignore new file mode 100644 index 0000000..f712b66 --- /dev/null +++ b/.gitleaksignore @@ -0,0 +1,12 @@ +# Suppressed findings (betterleaks reads .gitleaksignore as a fallback for +# .betterleaksignore, so this file works unchanged across the migration). +# +# Format: ::: +# +# examples/README.md documents a compose-validate caller that passes +# `GRAFANA_ADMIN_PASSWORD=ci-validate` as an env var — a literal placeholder +# for a throwaway CI value, not a credential. betterleaks' generic-password +# rule flags it at low confidence. The match lives in committed history, so a +# `betterleaks:allow` comment on the current line can't clear it; the +# fingerprint has to be pinned here. +526db3a8224309be3e54b652a71b6ff9f97df8d0:examples/README.md:generic-password:159 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3985c01..7c0fc3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,77 @@ project uses [SemVer](https://semver.org/) for the `vMAJOR.MINOR.PATCH` tags. ## [Unreleased] +### Changed + +- **BREAKING (consumers): `secret-scan.yml` now scans with + [betterleaks](https://github.com/betterleaks/betterleaks) instead of + gitleaks, and the `gitleaks-version` input is replaced by + `betterleaks-version`** (default `1.8.1`). A caller still passing + `gitleaks-version` will fail validation — drop it, or rename it. + + betterleaks is gitleaks' successor, written from scratch by gitleaks' + original author after he lost admin control of that repository and its + name. It is MIT, he retains ownership, and it carries no license key + requirement for org use — the same reason this workflow installed the OSS + gitleaks binary rather than the paid official action. + + Compatibility is close to total, which is what made the swap cheap: the + flags this workflow uses (`--no-banner`, `--redact`, + `--report-format=sarif`, `--report-path`, `--config`, `--exit-code`, + `--log-opts`) all exist, release tarballs are named identically in shape, + and betterleaks still reads `.gitleaks.toml`, `GITLEAKS_CONFIG`, + `.gitleaksignore` (same fingerprint format) and `gitleaks:allow` comments as + fallbacks. An existing gitleaks config needs no changes. + + **Expect new findings.** betterleaks reports ~98.6% recall against CredData + where gitleaks reports ~70.4%, so a repo that scanned clean may not any + more. This repo was exactly that case: a full-history scan went from *no + leaks* under gitleaks to *1 leak* under betterleaks — a `generic-password` + match, at low confidence, on the `GRAFANA_ADMIN_PASSWORD=ci-validate` + documentation placeholder in `examples/README.md`. Because such matches live + in committed history, a `betterleaks:allow` comment on the current line + cannot clear them; the fingerprint has to be pinned (see Added). PR-diff + mode is unaffected unless the offending line is touched, so the surprise + lands on push/scheduled full-history runs. + +- Other reusables are unchanged. The major bump is repo-wide because the tags + are: `@v3` now tracks this line, and the docs are updated to match. + +### Added + +- `secret-scan.yml` — `confidence` input (`low` / `medium` / `high`, default + empty = report everything). Filters findings below the given confidence, + which is the blunt escape hatch for the recall increase above when pinning + individual fingerprints isn't practical. + +- `.gitleaksignore` — pins the one low-confidence false positive in this + repo's own history so the full-history scan and the `pre-push` hook stay + green. Documents the fingerprint format for consumers hitting the same + thing. + +### Migration + +Callers that never set `gitleaks-version` only need the tag bump: + +```yaml +uses: nkg/github-actions/.github/workflows/secret-scan.yml@v3 +``` + +Callers that pinned a version rename the input: + +```yaml +with: + betterleaks-version: "1.8.1" # was: gitleaks-version: "8.30.1" +``` + +If the first run surfaces false positives, either pin fingerprints in +`.gitleaksignore` (preferred — keeps low-confidence detection on) or set +`confidence: medium`. The SARIF artifact is now named `betterleaks-sarif`. + +Local tooling moved too: `mise.toml` pins `betterleaks` and `lefthook.yml`'s +pre-commit/pre-push secret scans invoke it, so `mise install` on an existing +clone swaps the binary. + ## [2.17.0] - 2026-08-25 ### Added diff --git a/README.md b/README.md index 89b16e3..3b12f25 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,8 @@ doesn't false-positive. Workflows are consumed by tag. Tags follow `vMAJOR.MINOR.PATCH` and a floating `vMAJOR` tag tracks the latest non-breaking release on that line. -- Pin to `@v2` for low-friction updates within a major version. -- Pin to `@v2.4.0` (or a SHA) when you need bit-for-bit reproducibility. +- Pin to `@v3` for low-friction updates within a major version. +- Pin to `@v3.0.0` (or a SHA) when you need bit-for-bit reproducibility. Breaking changes bump the major. See `CHANGELOG.md`. @@ -92,7 +92,7 @@ on: jobs: test: - uses: nkg/github-actions/.github/workflows/python-uv.yml@v2 + uses: nkg/github-actions/.github/workflows/python-uv.yml@v3 with: python-version: "3.14" secrets: inherit @@ -123,7 +123,7 @@ which default to the self-hosted pool. ```yaml steps: - uses: actions/checkout@v6 - - uses: nkg/github-actions/.github/actions/setup-mise@v2 + - uses: nkg/github-actions/.github/actions/setup-mise@v3 - run: mise run build ``` @@ -154,7 +154,7 @@ steps: | Workflow | Purpose | |-------------------------|------------------------------------------------------------------| -| `secret-scan.yml` | OSS gitleaks; PR-diff or full-history scan, SARIF artifact | +| `secret-scan.yml` | OSS betterleaks; PR-diff or full-history scan, SARIF artifact | | `container-security.yml`| Trivy scan of every image in a compose file or explicit list | | `trivy-repo.yml` | Trivy filesystem (lockfiles) or config (IaC) scan of the repo | | `sops-audit.yml` | Verify SOPS encryption + plaintext-secret scan + shellcheck | @@ -202,7 +202,7 @@ are single-job. ## Local development Git hooks (lefthook) run the same checks as `self-test.yml` — actionlint, -yamllint, composite-action syntax, gitleaks — before commit/push, plus a +yamllint, composite-action syntax, betterleaks — before commit/push, plus a guard against direct pushes to `main` (bypass: `ALLOW_MAIN_PUSH=1 git push`). This dogfoods the pattern documented in [examples/pre-push-hook.md](examples/pre-push-hook.md). One-time setup per clone: diff --git a/examples/README.md b/examples/README.md index c2834ff..0ec2ebb 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,7 +1,7 @@ # Consumer examples Drop these into `.github/workflows/*.yml` of a consumer repo. Replace -`nkg/github-actions@v2` with a SHA when you need bit-for-bit pinning. +`nkg/github-actions@v3` with a SHA when you need bit-for-bit pinning. ## Python (uv) @@ -13,7 +13,7 @@ on: pull_request: jobs: test: - uses: nkg/github-actions/.github/workflows/python-uv.yml@v2 + uses: nkg/github-actions/.github/workflows/python-uv.yml@v3 with: python-version: "3.14" secrets: inherit @@ -31,7 +31,7 @@ name: CI on: [push, pull_request] jobs: web: - uses: nkg/github-actions/.github/workflows/node-bun.yml@v2 + uses: nkg/github-actions/.github/workflows/node-bun.yml@v3 with: working-directory: apps/web # package-manager: npm # bun (default) | npm | yarn | pnpm @@ -51,7 +51,7 @@ name: Integration on: [push, pull_request] jobs: integration: - uses: nkg/github-actions/.github/workflows/playwright-integration.yml@v2 + uses: nkg/github-actions/.github/workflows/playwright-integration.yml@v3 with: playwright-image: "mcr.microsoft.com/playwright:v1.60.0-noble" npm-scope: "@hordialabs" @@ -71,7 +71,7 @@ name: CI on: [push, pull_request] jobs: app: - uses: nkg/github-actions/.github/workflows/expo.yml@v2 + uses: nkg/github-actions/.github/workflows/expo.yml@v3 with: bun-version: "1.1" # Opt into an EAS build (off by default; needs EXPO_TOKEN): @@ -89,7 +89,7 @@ at the repo's own scripts: ```yaml jobs: app: - uses: nkg/github-actions/.github/workflows/expo.yml@v2 + uses: nkg/github-actions/.github/workflows/expo.yml@v3 with: package-manager: npm use-mise: true # Node comes from mise.toml @@ -109,7 +109,7 @@ jobs: matrix: otp: ["26", "27"] elixir: ["1.16", "1.17"] - uses: nkg/github-actions/.github/workflows/elixir.yml@v2 + uses: nkg/github-actions/.github/workflows/elixir.yml@v3 with: otp-version: ${{ matrix.otp }} elixir-version: ${{ matrix.elixir }} @@ -124,7 +124,7 @@ name: CI on: [push, pull_request] jobs: ci: - uses: nkg/github-actions/.github/workflows/elixir.yml@v2 + uses: nkg/github-actions/.github/workflows/elixir.yml@v3 with: elixir-version: "1.18" otp-version: "27" @@ -150,7 +150,7 @@ on: paths: ["stacks/cloudflare/**"] jobs: cloudflare: - uses: nkg/github-actions/.github/workflows/opentofu.yml@v2 + uses: nkg/github-actions/.github/workflows/opentofu.yml@v3 with: working-directory: stacks/cloudflare tofu-version: "1.8.0" @@ -167,7 +167,7 @@ on: pull_request: jobs: image: - uses: nkg/github-actions/.github/workflows/docker-build.yml@v2 + uses: nkg/github-actions/.github/workflows/docker-build.yml@v3 with: image-name: ${{ github.repository }} platforms: linux/amd64,linux/arm64 @@ -183,7 +183,7 @@ Consume it in the Dockerfile with ```yaml jobs: image: - uses: nkg/github-actions/.github/workflows/docker-build.yml@v2 + uses: nkg/github-actions/.github/workflows/docker-build.yml@v3 with: image-name: ${{ github.repository }} push: ${{ github.event_name != 'pull_request' }} @@ -202,7 +202,7 @@ name: CI on: [push, pull_request] jobs: ansible: - uses: nkg/github-actions/.github/workflows/ansible.yml@v2 + uses: nkg/github-actions/.github/workflows/ansible.yml@v3 with: working-directory: ansible playbooks: | @@ -218,7 +218,7 @@ name: CI on: [push, pull_request] jobs: go: - uses: nkg/github-actions/.github/workflows/go.yml@v2 + uses: nkg/github-actions/.github/workflows/go.yml@v3 with: go-version: "1.23" run-staticcheck: true @@ -233,7 +233,7 @@ name: CI on: [push, pull_request] jobs: go: - uses: nkg/github-actions/.github/workflows/go.yml@v2 + uses: nkg/github-actions/.github/workflows/go.yml@v3 with: go-version: "1.26" # goprivate is required for private modules — it makes `go` resolve them @@ -261,7 +261,7 @@ name: CI on: [push, pull_request] jobs: go: - uses: nkg/github-actions/.github/workflows/go.yml@v2 + uses: nkg/github-actions/.github/workflows/go.yml@v3 with: go-version: "1.26" run-golangci-lint: true @@ -278,7 +278,7 @@ name: CI on: [push, pull_request] jobs: go: - uses: nkg/github-actions/.github/workflows/go.yml@v2 + uses: nkg/github-actions/.github/workflows/go.yml@v3 with: go-version: "1.26" postgres-enabled: true @@ -293,7 +293,7 @@ name: CI on: [push, pull_request] jobs: scrapy: - uses: nkg/github-actions/.github/workflows/scrapy.yml@v2 + uses: nkg/github-actions/.github/workflows/scrapy.yml@v3 with: run-spider-smoke: true spider-name: products @@ -306,7 +306,7 @@ name: CI on: [push, pull_request] jobs: api: - uses: nkg/github-actions/.github/workflows/fastapi.yml@v2 + uses: nkg/github-actions/.github/workflows/fastapi.yml@v3 with: app-module: myapi.main:app run-openapi-diff: true @@ -320,7 +320,7 @@ name: Validate compose on: [push, pull_request] jobs: validate: - uses: nkg/github-actions/.github/workflows/compose-validate.yml@v2 + uses: nkg/github-actions/.github/workflows/compose-validate.yml@v3 with: compose-file: docker-compose.yml profiles: | @@ -341,7 +341,7 @@ on: branches: [main] jobs: komodo: - uses: nkg/github-actions/.github/workflows/komodo-deploy.yml@v2 + uses: nkg/github-actions/.github/workflows/komodo-deploy.yml@v3 with: komodo-url: https://komodo.example.com operation: DeployStack @@ -356,7 +356,7 @@ jobs: ## Security -### Secret scan (gitleaks) +### Secret scan (betterleaks) ```yaml name: Secret scan @@ -367,8 +367,8 @@ on: schedule: - cron: '0 6 * * 1' # weekly full-history sweep jobs: - gitleaks: - uses: nkg/github-actions/.github/workflows/secret-scan.yml@v2 + betterleaks: + uses: nkg/github-actions/.github/workflows/secret-scan.yml@v3 with: runs-on: '["self-hosted", "linux", "x64"]' ``` @@ -387,7 +387,7 @@ on: - cron: '0 6 * * 1' jobs: trivy: - uses: nkg/github-actions/.github/workflows/container-security.yml@v2 + uses: nkg/github-actions/.github/workflows/container-security.yml@v3 permissions: contents: read actions: read # }- only needed while upload-sarif is true @@ -402,7 +402,7 @@ Or scan an explicit list: ```yaml jobs: trivy: - uses: nkg/github-actions/.github/workflows/container-security.yml@v2 + uses: nkg/github-actions/.github/workflows/container-security.yml@v3 permissions: contents: read actions: read @@ -418,7 +418,7 @@ jobs: ```yaml jobs: trivy: - uses: nkg/github-actions/.github/workflows/container-security.yml@v2 + uses: nkg/github-actions/.github/workflows/container-security.yml@v3 permissions: contents: read with: @@ -442,7 +442,7 @@ on: - cron: '0 6 * * 1' jobs: fs: - uses: nkg/github-actions/.github/workflows/trivy-repo.yml@v2 + uses: nkg/github-actions/.github/workflows/trivy-repo.yml@v3 permissions: contents: read actions: read # }- only needed while upload-sarif is true @@ -450,7 +450,7 @@ jobs: with: scan-type: fs config: - uses: nkg/github-actions/.github/workflows/trivy-repo.yml@v2 + uses: nkg/github-actions/.github/workflows/trivy-repo.yml@v3 permissions: contents: read actions: read @@ -467,7 +467,7 @@ jobs: ```yaml jobs: fs: - uses: nkg/github-actions/.github/workflows/trivy-repo.yml@v2 + uses: nkg/github-actions/.github/workflows/trivy-repo.yml@v3 permissions: contents: read with: @@ -484,7 +484,7 @@ name: SOPS audit on: [push, pull_request] jobs: audit: - uses: nkg/github-actions/.github/workflows/sops-audit.yml@v2 + uses: nkg/github-actions/.github/workflows/sops-audit.yml@v3 with: encrypted-glob: '*.encrypted' shellcheck: true @@ -503,7 +503,7 @@ on: paths: ['.github/workflows/**', '.github/actions/**'] jobs: lint: - uses: nkg/github-actions/.github/workflows/lint-workflows.yml@v2 + uses: nkg/github-actions/.github/workflows/lint-workflows.yml@v3 with: # actionlint's bundled action metadata trails behind upstream. # See sproncy-distillery/.github/workflows/lint-workflows.yml for context. @@ -542,14 +542,14 @@ permissions: actions: read jobs: claude: - uses: nkg/github-actions/.github/workflows/claude.yml@v2 + uses: nkg/github-actions/.github/workflows/claude.yml@v3 secrets: CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} ``` ### Claude code review on every PR -Note: `claude-code-action@v2` self-validates that the workflow file on +Note: `claude-code-action@v3` self-validates that the workflow file on the PR matches the version on the default branch. On the PR that first introduces this wrapper, expect one "Workflow validation failed" failure — it's safe to ignore (the action's own message says so) and @@ -570,7 +570,7 @@ permissions: id-token: write jobs: review: - uses: nkg/github-actions/.github/workflows/claude-code-review.yml@v2 + uses: nkg/github-actions/.github/workflows/claude-code-review.yml@v3 secrets: CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} ``` @@ -580,7 +580,7 @@ Pass a repo-specific prompt + restricted gh-tools allow-list: ```yaml jobs: review: - uses: nkg/github-actions/.github/workflows/claude-code-review.yml@v2 + uses: nkg/github-actions/.github/workflows/claude-code-review.yml@v3 with: prompt: | REPO: ${{ github.repository }} @@ -597,7 +597,7 @@ Filter to first-time contributors only: ```yaml jobs: review: - uses: nkg/github-actions/.github/workflows/claude-code-review.yml@v2 + uses: nkg/github-actions/.github/workflows/claude-code-review.yml@v3 with: filter-first-time-only: true secrets: @@ -615,7 +615,7 @@ on: types: [opened] jobs: assign: - uses: nkg/github-actions/.github/workflows/auto-assign.yml@v2 + uses: nkg/github-actions/.github/workflows/auto-assign.yml@v3 with: assignees: nkg ``` @@ -634,7 +634,7 @@ on: pull_request: jobs: merge: - uses: nkg/github-actions/.github/workflows/dependabot-auto-merge.yml@v2 + uses: nkg/github-actions/.github/workflows/dependabot-auto-merge.yml@v3 # Defaults: squash strategy, skip major bumps, use-auto-merge: true. # with: # merge-strategy: rebase @@ -656,7 +656,7 @@ jobs: permissions: contents: write pull-requests: write - uses: nkg/github-actions/.github/workflows/dependabot-auto-merge.yml@v2 + uses: nkg/github-actions/.github/workflows/dependabot-auto-merge.yml@v3 with: use-auto-merge: false ``` @@ -676,7 +676,7 @@ permissions: contents: write jobs: update-lockfile: - uses: nkg/github-actions/.github/workflows/dependabot-uv-lockfile.yml@v2 + uses: nkg/github-actions/.github/workflows/dependabot-uv-lockfile.yml@v3 with: directories: | sproncy-api @@ -707,7 +707,7 @@ jobs: permissions: contents: write pull-requests: write - uses: nkg/github-actions/.github/workflows/auto-revert-on-main-failure.yml@v2 + uses: nkg/github-actions/.github/workflows/auto-revert-on-main-failure.yml@v3 with: bad-sha: ${{ github.event.workflow_run.head_sha }} failed-run-url: ${{ github.event.workflow_run.html_url }} @@ -733,7 +733,7 @@ on: workflow_dispatch: jobs: cleanup: - uses: nkg/github-actions/.github/workflows/stale-run-cleanup.yml@v2 + uses: nkg/github-actions/.github/workflows/stale-run-cleanup.yml@v3 # with: # min-age-minutes: 5 # grace period before a superseded run is cancelled # dry-run: true # log only @@ -800,7 +800,7 @@ name: CI on: [push, pull_request] jobs: build: - uses: nkg/github-actions/.github/workflows/turbo.yml@v2 + uses: nkg/github-actions/.github/workflows/turbo.yml@v3 with: turbo-tasks: "lint type-check test build" turbo-filter: "@my-org/web @my-org/api" @@ -827,7 +827,7 @@ jobs: web: needs: changes if: needs.changes.outputs.web == 'true' - uses: nkg/github-actions/.github/workflows/turbo.yml@v2 + uses: nkg/github-actions/.github/workflows/turbo.yml@v3 with: turbo-tasks: "lint type-check test build" turbo-filter: "@my-org/web" @@ -842,7 +842,7 @@ name: Molecule on: [push, pull_request] jobs: molecule: - uses: nkg/github-actions/.github/workflows/molecule.yml@v2 + uses: nkg/github-actions/.github/workflows/molecule.yml@v3 with: roles: | common @@ -860,7 +860,7 @@ name: TOML lint on: [push, pull_request] jobs: toml: - uses: nkg/github-actions/.github/workflows/toml-lint.yml@v2 + uses: nkg/github-actions/.github/workflows/toml-lint.yml@v3 ``` ### Bats + shellcheck @@ -870,7 +870,7 @@ name: Bash tests on: [push, pull_request] jobs: bash: - uses: nkg/github-actions/.github/workflows/bats.yml@v2 + uses: nkg/github-actions/.github/workflows/bats.yml@v3 with: test-paths: tests/ # Auto-detects scripts/ + tests/ + bin/ when shellcheck-paths is empty @@ -881,7 +881,7 @@ Pinning bats and customising shellcheck scope: ```yaml jobs: bash: - uses: nkg/github-actions/.github/workflows/bats.yml@v2 + uses: nkg/github-actions/.github/workflows/bats.yml@v3 with: bats-version: "1.11.0" test-paths: "tests/sops tests/restore" @@ -903,7 +903,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: nkg/github-actions/.github/actions/setup-sops@v2 + - uses: nkg/github-actions/.github/actions/setup-sops@v3 with: age-key: ${{ secrets.SOPS_AGE_KEY }} decrypt-file: secrets.enc.yaml @@ -921,7 +921,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: nkg/github-actions/.github/actions/setup-deps-reader@v2 + - uses: nkg/github-actions/.github/actions/setup-deps-reader@v3 with: app-client-id: ${{ vars.DEPS_READER_CLIENT_ID }} app-private-key: ${{ secrets.DEPS_READER_PRIVATE_KEY }} @@ -938,7 +938,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: nkg/github-actions/.github/actions/setup-trivy@v2 + - uses: nkg/github-actions/.github/actions/setup-trivy@v3 - run: trivy fs --severity HIGH,CRITICAL . sign: @@ -947,7 +947,7 @@ jobs: id-token: write contents: read steps: - - uses: nkg/github-actions/.github/actions/setup-cosign@v2 + - uses: nkg/github-actions/.github/actions/setup-cosign@v3 - run: cosign sign --yes ghcr.io/nkg/api@sha256:... ``` @@ -959,7 +959,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: nkg/github-actions/.github/actions/setup-mise@v2 # runs `mise install` from .mise.toml + - uses: nkg/github-actions/.github/actions/setup-mise@v3 # runs `mise install` from .mise.toml - run: mise run build ``` @@ -971,7 +971,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: nkg/github-actions/.github/actions/setup-go@v2 + - uses: nkg/github-actions/.github/actions/setup-go@v3 with: go-version: "1.23" - run: go build ./... @@ -988,7 +988,7 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: nkg/github-actions/.github/actions/setup-token@v2 + - uses: nkg/github-actions/.github/actions/setup-token@v3 id: token with: app-client-id: ${{ vars.APP_CLIENT_ID }} diff --git a/lefthook.yml b/lefthook.yml index 3f6d0ea..54e95ef 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -5,14 +5,14 @@ # here passes Actions. See examples/pre-push-hook.md for the rationale — # this repo documents the pattern, so it dogfoods it too. # -# Tools (lefthook, actionlint, gitleaks, uv) are pinned in mise.toml; +# Tools (lefthook, actionlint, betterleaks, uv) are pinned in mise.toml; # `mise install && lefthook install` on a fresh clone enables the hooks. pre-commit: parallel: true commands: - gitleaks: - run: gitleaks git --pre-commit --staged --redact --no-banner + betterleaks: + run: betterleaks git --pre-commit --staged --redact --no-banner yamllint: glob: "*.{yml,yaml}" run: uvx yamllint {staged_files} @@ -49,5 +49,5 @@ pre-push: "import sys, yaml; yaml.safe_load(open(sys.argv[1]))" "$action" done # Full-history secret scan, matching secret-scan.yml's push-event mode. - gitleaks: - run: gitleaks git --redact --no-banner + betterleaks: + run: betterleaks git --redact --no-banner diff --git a/mise.toml b/mise.toml index 8f7c668..4729d85 100644 --- a/mise.toml +++ b/mise.toml @@ -6,7 +6,7 @@ [tools] lefthook = "latest" actionlint = "1.7.12" -gitleaks = "8.30.1" +betterleaks = "1.8.1" uv = "latest" taplo = "latest"