diff --git a/standards/ci-standards.md b/standards/ci-standards.md index a58ae81e6..b20ccecb7 100644 --- a/standards/ci-standards.md +++ b/standards/ci-standards.md @@ -732,12 +732,17 @@ first time it is **added** to a SonarCloud-gated repo. (The legacy per-file also works and is accepted during transition, but the inline marker is canonical.) Caller-stub templates that use `secrets: inherit` and therefore carry the S7635 -marker: `initiative-planner.yml`, `idea-triage.yml`, `idea-enhancer.yml`. -**Pending:** `dev-lead.yml`, `auto-rebase.yml`, `dependabot-automerge.yml`, and -`pr-review-mention.yml` also use `secrets: inherit` but do not yet carry the -marker; because they are already deployed fleet-wide (so re-syncing them fans out -broadly) the marker is being added to them under a separate rollout — until then a -**new** adoption of one of those four needs the legacy per-file S7635 exemption. +marker: `initiative-planner.yml`, `idea-triage.yml`, `idea-enhancer.yml`, +`persona-mention.yml`, `dev-lead.yml`, `auto-rebase.yml`, +`dependabot-automerge.yml`. Every template with a `secrets: inherit` line now +carries the marker; the +[`s7635-secrets-inherit.bats`](https://github.com/petry-projects/.github/blob/main/test/scripts/standards-templates/s7635-secrets-inherit.bats) +template guard fails CI if any such line drops it (mirrors the S7637 v-form pin +guard). + +Stubs that pass secrets **explicitly** (least privilege — no `secrets: inherit` +line, e.g. `add-to-project.yml`, `pr-review-mention.yml`) never trip S7635 and +carry **no** marker. Do not add one, as the marker is only necessary for `secrets: inherit` lines, which these stubs lack. ### 4. Secret Scanning (`ci.yml` — gitleaks job) diff --git a/standards/workflows/auto-rebase.yml b/standards/workflows/auto-rebase.yml index 78eb4dea2..ec4140e23 100644 --- a/standards/workflows/auto-rebase.yml +++ b/standards/workflows/auto-rebase.yml @@ -51,4 +51,4 @@ jobs: contents: write # update-branch via GITHUB_TOKEN (may touch .github/workflows/) pull-requests: write # post comments on PRs uses: petry-projects/.github/.github/workflows/auto-rebase-reusable.yml@auto-rebase/v2-stable # NOSONAR(githubactions:S7637) first-party channel ref - secrets: inherit + secrets: inherit # NOSONAR(githubactions:S7635) first-party trusted reusable diff --git a/standards/workflows/dependabot-automerge.yml b/standards/workflows/dependabot-automerge.yml index 0a19abc73..eddb767ed 100644 --- a/standards/workflows/dependabot-automerge.yml +++ b/standards/workflows/dependabot-automerge.yml @@ -36,4 +36,4 @@ jobs: contents: read pull-requests: read uses: petry-projects/.github/.github/workflows/dependabot-automerge-reusable.yml@dependabot-automerge/v2-stable # NOSONAR(githubactions:S7637) first-party channel ref - secrets: inherit + secrets: inherit # NOSONAR(githubactions:S7635) first-party trusted reusable diff --git a/standards/workflows/dev-lead.yml b/standards/workflows/dev-lead.yml index ec2c71f64..fbceadb33 100644 --- a/standards/workflows/dev-lead.yml +++ b/standards/workflows/dev-lead.yml @@ -64,7 +64,7 @@ jobs: uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/v1-stable # NOSONAR(githubactions:S7637) first-party channel ref with: agent_ref: dev-lead/v1-stable - secrets: inherit + secrets: inherit # NOSONAR(githubactions:S7635) first-party trusted reusable permissions: contents: write pull-requests: write diff --git a/test/scripts/standards-templates/s7635-secrets-inherit.bats b/test/scripts/standards-templates/s7635-secrets-inherit.bats new file mode 100644 index 000000000..23a291df8 --- /dev/null +++ b/test/scripts/standards-templates/s7635-secrets-inherit.bats @@ -0,0 +1,86 @@ +#!/usr/bin/env bats +# Template-guard for the S7635 NOSONAR marker on caller-stub templates under +# standards/workflows/ (#875, mirrors the #860 v-form/S7637 pin guard). +# +# A caller stub that hands the reusable every org secret via `secrets: inherit` +# trips SonarCloud rule `githubactions:S7635` ("only pass required secrets"). +# The reusable is first-party and fully trusted, so the stub carries an inline +# secrets: inherit # NOSONAR(githubactions:S7635) +# marker that suppresses S7635 on exactly that line and travels with the stub to +# every consumer repo. Dropping the marker regresses any consumer that copied +# the stub (re-triggers S7635 in their SonarCloud run). This guard fails in CI +# the moment a template with `secrets: inherit` loses the marker. +# +# Stubs that pass secrets EXPLICITLY (least privilege, no `secrets: inherit` +# line — e.g. add-to-project.yml, pr-review-mention.yml) do not trip S7635 and +# are correctly ignored: the guard only inspects real `secrets: inherit` lines. + +REPO_ROOT="$(cd -- "${BATS_TEST_DIRNAME}/../../.." && pwd)" +WF_DIR="${REPO_ROOT}/standards/workflows" + +# The exact NOSONAR token every marked line must carry (grep -F, literal). +MARKER='NOSONAR(githubactions:S7635)' + +# Emit `secrets: inherit` lines that are REAL YAML (indented key), never a `#` +# comment mention. Anchors `secrets:` to the start-of-line-after-indent so a +# prose line like `# ... secrets: inherit ...` is not matched. +secrets_inherit_lines() { + grep -nE '^[[:space:]]*secrets:[[:space:]]+inherit([[:space:]]|$)' "$1" || true +} + +@test "every template with 'secrets: inherit' carries the S7635 marker" { + local violations=() + local f name lines line lineno content + for f in "${WF_DIR}"/*.yml; do + name="$(basename "$f")" + lines="$(secrets_inherit_lines "$f")" + [ -n "$lines" ] || continue + while IFS= read -r line; do + lineno="${line%%:*}" + content="${line#*:}" + if ! grep -qF "$MARKER" <<<"$content"; then + violations+=("${name}:${lineno}: 'secrets: inherit' line lacks ${MARKER}") + fi + done <<<"$lines" + done + if [ "${#violations[@]}" -ne 0 ]; then + printf 'missing S7635 marker -> %s\n' "${violations[@]}" + return 1 + fi +} + +@test "every S7635 marker uses the canonical inline shape" { + # Two spaces before the '#', then the exact token. Trailing prose is free-form + # (e.g. 'first-party trusted reusable' vs 'org secrets are scoped ...'), but + # the marker itself must be byte-consistent so SonarCloud honours it. + local violations=() + local f name lines line lineno content + for f in "${WF_DIR}"/*.yml; do + name="$(basename "$f")" + lines="$(secrets_inherit_lines "$f")" + [ -n "$lines" ] || continue + while IFS= read -r line; do + lineno="${line%%:*}" + content="${line#*:}" + grep -qF "$MARKER" <<<"$content" || continue + if ! grep -qE 'secrets: inherit # NOSONAR\(githubactions:S7635\) ' <<<"$content"; then + violations+=("${name}:${lineno}: marker is not the canonical 'secrets: inherit # NOSONAR(githubactions:S7635) ' shape") + fi + done <<<"$lines" + done + if [ "${#violations[@]}" -ne 0 ]; then + printf 'bad marker shape -> %s\n' "${violations[@]}" + return 1 + fi +} + +@test "the guard actually inspects at least one 'secrets: inherit' stub" { + # Positive control: without this, a broken matcher that finds nothing would let + # the first test pass vacuously (its `[ -n "$lines" ] || continue` skips every + # file). At least one template uses `secrets: inherit` by design. + local f found=0 + for f in "${WF_DIR}"/*.yml; do + [ -n "$(secrets_inherit_lines "$f")" ] && found=1 && break + done + [ "$found" -eq 1 ] || { echo "no template with a real 'secrets: inherit' line found"; return 1; } +}