Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/hypatia-scan-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ on:
type: string
required: false
default: 51ab6496bf47e30a0576d503df31fec30f3cfe56
secrets:
HYPATIA_SCAN_PAT:
description: >-
Fine-grained read-only PAT used to read Dependabot, code-scanning
and secret-scanning alerts; the scan falls back to the built-in
GITHUB_TOKEN when it is not supplied. Declared explicitly so
callers pass ONLY this secret instead of `secrets: inherit`,
which forwarded every caller secret (standards#1108 §6b).
required: false

permissions:
actions: read
Expand Down
11 changes: 9 additions & 2 deletions .github/workflows/mirror-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,14 @@
# jobs:
# mirror:
# uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@<sha>
# secrets: inherit
# secrets:
# GITLAB_SSH_KEY: ${{ secrets.GITLAB_SSH_KEY }}
# BITBUCKET_SSH_KEY: ${{ secrets.BITBUCKET_SSH_KEY }}
# CODEBERG_SSH_KEY: ${{ secrets.CODEBERG_SSH_KEY }}
# SOURCEHUT_SSH_KEY: ${{ secrets.SOURCEHUT_SSH_KEY }}
# DISROOT_SSH_KEY: ${{ secrets.DISROOT_SSH_KEY }}
# GITEA_SSH_KEY: ${{ secrets.GITEA_SSH_KEY }}
# RADICLE_KEY: ${{ secrets.RADICLE_KEY }}

name: Mirror to Git Forges (reusable)

Expand Down Expand Up @@ -399,7 +406,7 @@ jobs:
with:
fetch-depth: 0
# All Radicle steps gate on secrets.RADICLE_KEY being set on the
# caller repo (resolved via `secrets: inherit`). Without this gate
# caller repo (passed explicitly under that declared name). Without this gate
# the workflow burned ~3 minutes of Rust+Radicle install on every
# push to every RADICLE_MIRROR_ENABLED repo only to fail at
# `~/.radicle/keys/radicle: No such file or directory` because the
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/secret-scanner-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@
# permissions:
# contents: read
# uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@<sha>
# secrets: inherit
# # Deliberately NO `secrets:` line — the callee declares none and
# # needs none (see the Design note above).

name: Secret Scanner (reusable)

Expand Down
12 changes: 11 additions & 1 deletion 1-formats/a2ml/meta/.github/workflows/mirror.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,14 @@ jobs:
actions: read
contents: read
uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@210f14e753c80064ec1bcae72f1d654dd9b0e687
secrets: inherit
# Exactly the seven optional secrets the callee declares at this pin
# (verified declared ⊇ referenced) — not `secrets: inherit`
# (standards#1108 §6b).
secrets:
GITLAB_SSH_KEY: ${{ secrets.GITLAB_SSH_KEY }}
BITBUCKET_SSH_KEY: ${{ secrets.BITBUCKET_SSH_KEY }}
CODEBERG_SSH_KEY: ${{ secrets.CODEBERG_SSH_KEY }}
SOURCEHUT_SSH_KEY: ${{ secrets.SOURCEHUT_SSH_KEY }}
DISROOT_SSH_KEY: ${{ secrets.DISROOT_SSH_KEY }}
GITEA_SSH_KEY: ${{ secrets.GITEA_SSH_KEY }}
RADICLE_KEY: ${{ secrets.RADICLE_KEY }}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ permissions:

jobs:
scorecard:
# Exactly the callee's declared calling-job contract (scorecard-
# reusable header): a smaller job-level grant startup-fails, because
# job-level permissions replace, never merge with, the workflow grant.
permissions:
actions: read
contents: read
security-events: write
id-token: write
uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@8ff9ce65f7e54f8d5c5aa94584e2ead0f2ba460f
# No `secrets:` line: the reusable declares none (standards#1108 §6b).
5 changes: 5 additions & 0 deletions 1-formats/a2ml/meta/.github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ permissions: read-all

jobs:
analysis:
# Exactly the callee's declared calling-job contract (scorecard-
# reusable header): a smaller job-level grant startup-fails, because
# job-level permissions replace, never merge with, the workflow grant.
permissions:
actions: read
contents: read
security-events: write
id-token: write
uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@8ff9ce65f7e54f8d5c5aa94584e2ead0f2ba460f
# No `secrets:` line: the reusable declares none (standards#1108 §6b).
12 changes: 11 additions & 1 deletion 2-protocols/0-ai-gatekeeper/.github/workflows/mirror.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,14 @@ jobs:
actions: read
contents: read
uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@210f14e753c80064ec1bcae72f1d654dd9b0e687
secrets: inherit
# Exactly the seven optional secrets the callee declares at this pin
# (verified declared ⊇ referenced) — not `secrets: inherit`
# (standards#1108 §6b).
secrets:
GITLAB_SSH_KEY: ${{ secrets.GITLAB_SSH_KEY }}
BITBUCKET_SSH_KEY: ${{ secrets.BITBUCKET_SSH_KEY }}
CODEBERG_SSH_KEY: ${{ secrets.CODEBERG_SSH_KEY }}
SOURCEHUT_SSH_KEY: ${{ secrets.SOURCEHUT_SSH_KEY }}
DISROOT_SSH_KEY: ${{ secrets.DISROOT_SSH_KEY }}
GITEA_SSH_KEY: ${{ secrets.GITEA_SSH_KEY }}
RADICLE_KEY: ${{ secrets.RADICLE_KEY }}
5 changes: 5 additions & 0 deletions 2-protocols/0-ai-gatekeeper/.github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@ permissions:

jobs:
scorecard:
# Exactly the callee's declared calling-job contract (scorecard-
# reusable header): a smaller job-level grant startup-fails, because
# job-level permissions replace, never merge with, the workflow grant.
permissions:
actions: read
contents: read
security-events: write
id-token: write
uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@8ff9ce65f7e54f8d5c5aa94584e2ead0f2ba460f
# No `secrets:` line: the reusable declares none (standards#1108 §6b).
5 changes: 5 additions & 0 deletions CICD-WORKFLOW-CATALOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,12 @@ These should **call** the reusable workflow instead of copying:
```yaml
jobs:
governance:
# Grant exactly what the callee declares (governance-reusable asks for
# actions: read + contents: read). A called workflow can never exceed
# its caller's grant, and a job-level block REPLACES the workflow-level
# one — under-granting here rejects the run at startup.
permissions:
actions: read
contents: read
uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@<full-40-char-commit-sha>
# Add a `secrets:` block ONLY when the callee declares named secrets
Expand Down
69 changes: 65 additions & 4 deletions tests/test_secret_scanner_no_inherit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,7 @@
# startup-fail (passing an undeclared secret is a validation error).
# Each line: path<TAB>reason. Shrinks as the templates are re-pinned.
SUPPORTED_INHERIT=(
"1-formats/a2ml/meta/.github/workflows/mirror.yml callee declares 7 SSH secrets; explicit form lands with the template re-pin (standards#943)"
"1-formats/a2ml/meta/.github/workflows/hypatia-scan.yml callee predates the declared HYPATIA_SCAN_PAT contract at its pin (standards#1108 §6b)"
"2-protocols/0-ai-gatekeeper/.github/workflows/mirror.yml callee declares 7 SSH secrets; explicit form lands with the template re-pin (standards#943)"
"2-protocols/0-ai-gatekeeper/.github/workflows/hypatia-scan.yml callee predates the declared HYPATIA_SCAN_PAT contract at its pin (standards#1108 §6b)"
)

Expand Down Expand Up @@ -130,6 +128,63 @@
grep -vE '^[[:space:]]*#' "$CALLER" | grep -nE '^[[:space:]]+secrets:' | sed 's/^/ /'
fi

# --- 2b. Documented caller EXAMPLES must not teach inherit -------------
# The directive scan below reads non-comment lines (a directive is live
# YAML), but the original §6b defect propagated precisely through the
# commented examples generators copy. So the example blocks in the two
# reusables that document a caller are asserted separately: extract from
# the "Caller example" marker to the first non-comment line and forbid
# the inherit string there (in any role — an example has no business
# containing it at all).
for ex_file in "$REUSABLE" "$WF_DIR/mirror-reusable.yml"; do
[ -r "$ex_file" ] || { fail "example-bearing reusable unreadable: $ex_file"; continue; }

Check failure on line 140 in tests/test_secret_scanner_no_inherit.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaFPRSSt5DhrfaQ-cX7a&open=AaFPRSSt5DhrfaQ-cX7a&pullRequest=1284
example="$(awk '/Caller example/{f=1} f && /^[^#]/{exit} f{print}' "$ex_file")"
[ -n "$example" ] || { fail "no caller example found in $ex_file — extraction is broken"; continue; }

Check failure on line 142 in tests/test_secret_scanner_no_inherit.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaFPRSSt5DhrfaQ-cX7b&open=AaFPRSSt5DhrfaQ-cX7b&pullRequest=1284
if printf '%s
' "$example" | grep -q 'secrets:[[:space:]]*inherit'; then
fail "caller example in $(basename "$ex_file") still teaches \`secrets: inherit\` — examples are what generators copy"
fi
done

# --- 2c. Narrowed callers may pass only secrets the callee declares ----
# Passing a secret the callee does not declare is a startup validation
# error (this is not theoretical: it startup-failed hypatia-scan on main
# when the caller was narrowed one commit before the declaration
# landed). For each standards wrapper that passes named secrets to a
# LOCAL reusable, every passed name must be declared in that reusable's
# workflow_call.secrets mapping. These workflows use two-space indentation;
# inspect only direct secret keys, not inputs or nested metadata.
check_declared() { # caller reusable
local caller="$1" reusable="$2" name declared
[ -r "$caller" ] && [ -r "$reusable" ] || return 0

Check failure on line 159 in tests/test_secret_scanner_no_inherit.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaFPRSSt5DhrfaQ-cX7c&open=AaFPRSSt5DhrfaQ-cX7c&pullRequest=1284

Check failure on line 159 in tests/test_secret_scanner_no_inherit.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaFPRSSt5DhrfaQ-cX7d&open=AaFPRSSt5DhrfaQ-cX7d&pullRequest=1284
declared="$(awk '
/^[[:space:]]*(#|$)/ { next }
/^ workflow_call:/ { call=1; next }
call && /^ {0,2}[^ ]/ { exit }
call && /^ secrets:[[:space:]]*(#.*)?$/ { secrets=1; next }
secrets && /^ [^ ]/ { exit }
secrets && /^ [A-Za-z_][A-Za-z0-9_]*:/ {
sub(/^ /, ""); sub(/:.*/, ""); print
}
' "$reusable")"
while IFS= read -r name; do
if ! grep -qxF -- "$name" <<< "$declared"; then
fail "$(basename "$caller") passes secret $name but $(basename "$reusable") does not declare it in workflow_call.secrets — every run startup-fails"
fi
done < <(awk '
/^[[:space:]]*(#|$)/ { next }
/^jobs:/ { jobs=1; next }
jobs && /^[^ ]/ { exit }
jobs && /^ secrets:[[:space:]]*(#.*)?$/ { secrets=1; next }
secrets && /^ {0,4}[^ ]/ { secrets=0 }
secrets && /^ [A-Za-z_][A-Za-z0-9_]*:/ {
sub(/^ /, ""); sub(/:.*/, ""); print
}
' "$caller")
}
check_declared "$WF_DIR/hypatia-scan.yml" "$WF_DIR/hypatia-scan-reusable.yml"
check_declared "$WF_DIR/governance.yml" "$WF_DIR/governance-reusable.yml"

# --- 3. No live `secrets: inherit` directive outside the supported list -
supported_path() {
local p="$1" entry
Expand Down Expand Up @@ -213,10 +268,16 @@

checkout_pin="$(grep -vE '^[[:space:]]*#' "$REUSABLE" | grep -oE 'actions/checkout@[0-9a-f]{40}' | sort -u)"
[ -n "$checkout_pin" ] || fail "could not find the reusable's actions/checkout pin — extraction is broken"
# Look the pin up inside the scanner reusable's OWN lock section, not the
# whole file: checkout is keyed under nearly every workflow, so a global
# search would pass even if the scanner's section lost its record.
lock_section="$(awk "/^ '\\.github\\/workflows\\/secret-scanner-reusable\\.yml':/{f=1;next} /^ '\\.github\\/workflows\\//{f=0} f{print}" "$LOCKFILE")"
[ -n "$lock_section" ] || fail "actions.lock has no section for secret-scanner-reusable.yml — regenerate with \`gh actions-lock\`"

Check failure on line 275 in tests/test_secret_scanner_no_inherit.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaFPRSSt5DhrfaQ-cX7e&open=AaFPRSSt5DhrfaQ-cX7e&pullRequest=1284
while IFS= read -r pin; do
[ -n "$pin" ] || continue
grep -qF "'$pin'" "$LOCKFILE" \
|| fail "scanner reusable uses $pin but actions.lock does not key it — callers pinned to this commit will fail lock validation at startup"
printf '%s
' "$lock_section" | grep -qF "'$pin'" \
|| fail "scanner reusable uses $pin but its actions.lock section does not key it — callers pinned to this commit will fail lock validation at startup"
done <<< "$checkout_pin"

# --- Verdict ---------------------------------------------------------------
Expand Down
Loading