From 5e16845f82440eddb408b7a20d3176db9f15fc55 Mon Sep 17 00:00:00 2001 From: CMaintz Date: Tue, 29 Sep 2026 11:17:49 +0200 Subject: [PATCH] fix(ci): security facade startup failure and ratchet-report on a new baseline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit security.yml: the `guards` job calls _guards.yml without its own permissions, so it inherits the facade's file-level `contents: read`. _guards' secret-scan job asks for `pull-requests: read` (gitleaks-action lists the PR's commits), which is more than its calling job holds, so GitHub rejects the run before it starts (`startup_failure`, with the reason visible only in the Actions UI). Found adopting v2.2.0 in tech-atlas. The guards job now grants `contents: read` + `pull-requests: read`; the usage example, foundry-init's generated caller and OVERVIEW §13 show the caller granting the same. ratchet-report.yml: `git show "$BASE:$f"` exits 128 when the baseline is not on the base yet (the PR adopting Foundry, or bootstrap's first seed). Under `set -euo pipefail` that failed the job instead of reporting "before: 0". --- .github/workflows/ratchet-report.yml | 5 ++++- .github/workflows/security.yml | 9 +++++++++ docs/OVERVIEW.md | 1 + scripts/foundry-init.sh | 1 + 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ratchet-report.yml b/.github/workflows/ratchet-report.yml index b2c35af..c0438e5 100644 --- a/.github/workflows/ratchet-report.yml +++ b/.github/workflows/ratchet-report.yml @@ -62,7 +62,10 @@ jobs: rows=""; moved=0 for f in $BASELINES; do [ -z "$f" ] && continue - before=$(git show "$BASE:$f" 2>/dev/null | count); before=${before:-0} + # `|| true`: a baseline not on the base yet (the PR that adopts Foundry, or + # the first bootstrap seed) makes `git show` exit 128, which pipefail + -e + # would turn into a failed job instead of a "before: 0" row. + before=$( (git show "$BASE:$f" 2>/dev/null || true) | count); before=${before:-0} after=$([ -f "$f" ] && count < "$f" || echo 0) delta=$((after - before)) if [ "$delta" -lt 0 ]; then icon="✅ −$(( -delta ))"; moved=1 diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 337d8ba..51cc8dd 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -5,6 +5,7 @@ # jobs: # security: # uses: CMaintz/foundry/.github/workflows/security.yml@v2 +# permissions: { contents: read, pull-requests: read } # the secret scan lists PR commits # with: { ruleset_paths: '^(mise\.toml|backend/.habit-hooks/|…)' } # # Require the `security-ok` check in branch protection. @@ -38,6 +39,14 @@ permissions: jobs: guards: + # A nested reusable workflow can use at most the permissions of the job that calls + # it. Without this, `guards` inherits the file-level `contents: read` and _guards' + # secret-scan job (which needs `pull-requests: read` to list the PR's commits) is + # rejected, and the whole run ends in `startup_failure`. The consumer's caller job + # must grant the same (see the usage example above). + permissions: + contents: read + pull-requests: read uses: ./.github/workflows/_guards.yml with: ruleset_paths: ${{ inputs.ruleset_paths }} diff --git a/docs/OVERVIEW.md b/docs/OVERVIEW.md index cf8bda9..c85791f 100644 --- a/docs/OVERVIEW.md +++ b/docs/OVERVIEW.md @@ -330,6 +330,7 @@ on: { pull_request: {}, push: { branches: [main] } } jobs: security: uses: CMaintz/foundry/.github/workflows/security.yml@v2 + permissions: { contents: read, pull-requests: read } # the secret scan lists the PR's commits with: ruleset_paths: '^(mise\.toml|backend/\.habit-hooks/|frontend/\.habit-hooks/|\.github/workflows/)' ``` diff --git a/scripts/foundry-init.sh b/scripts/foundry-init.sh index f43cbe7..86470be 100644 --- a/scripts/foundry-init.sh +++ b/scripts/foundry-init.sh @@ -138,6 +138,7 @@ concurrency: { group: security-\${{ github.ref }}, cancel-in-progress: true } jobs: security: uses: $REPO/.github/workflows/security.yml@$REF # facade: secret scan + ruleset-guard + SAST + permissions: { contents: read, pull-requests: read } # the secret scan lists the PR's commits YAML write ".github/workflows/ratchet.yml" <