From 4a329372d9257c59929624e8d05d67124d3b84a3 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sat, 29 Aug 2026 02:17:55 +0100 Subject: [PATCH 1/4] fix(governance): split credentialed live policy audit --- .../allowlist-preflight-reusable.yml | 40 ++++++++++++++-- .github/workflows/governance-reusable.yml | 46 ++++++++++++++++--- 2 files changed, 76 insertions(+), 10 deletions(-) diff --git a/.github/workflows/allowlist-preflight-reusable.yml b/.github/workflows/allowlist-preflight-reusable.yml index e33c099e..b722ec0c 100644 --- a/.github/workflows/allowlist-preflight-reusable.yml +++ b/.github/workflows/allowlist-preflight-reusable.yml @@ -15,7 +15,7 @@ permissions: jobs: preflight: - name: Actions policy (tree + live) + name: Actions policy (tree) runs-on: ubuntu-latest timeout-minutes: 5 steps: @@ -56,9 +56,41 @@ jobs: bash "$RUNNER_TEMP/check-allowed-actions.sh" \ "$RUNNER_TEMP/allowed-actions.json" .github/workflows + live-policy: + name: Live Actions policy (credentialed advisory) + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout policy source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: hyperpolymath/standards + ref: main + sparse-checkout: | + rhodium-standard-repositories/actions-allowlist + scripts/check-actions-policy.sh + sparse-checkout-cone-mode: false + + - name: Report unavailable live policy credential + env: + GH_TOKEN: ${{ secrets.HYPATIA_SCAN_PAT }} + if: ${{ env.GH_TOKEN == '' }} + run: | + echo "::notice::Live Actions policy was not evaluated: HYPATIA_SCAN_PAT was not supplied by the caller. The separate tree allowlist gate still ran." + # shellcheck disable=SC2016 + printf '%s\n' \ + '### Live Actions policy not evaluated' \ + '' \ + 'The caller did not supply `HYPATIA_SCAN_PAT`. This advisory job' \ + 'does not claim that the repository Administration setting passed.' \ + 'The independent tree allowlist preflight remains enforced.' \ + >> "$GITHUB_STEP_SUMMARY" + - name: Check live repository policy env: - GH_TOKEN: ${{ secrets.HYPATIA_SCAN_PAT || github.token }} + GH_TOKEN: ${{ secrets.HYPATIA_SCAN_PAT }} + if: ${{ env.GH_TOKEN != '' }} run: | - bash "$RUNNER_TEMP/check-actions-policy.sh" \ - "$GITHUB_REPOSITORY" "$RUNNER_TEMP/allowed-actions.json" + bash scripts/check-actions-policy.sh \ + "$GITHUB_REPOSITORY" \ + rhodium-standard-repositories/actions-allowlist/allowed-actions.json diff --git a/.github/workflows/governance-reusable.yml b/.github/workflows/governance-reusable.yml index 5a8a1929..22d32364 100644 --- a/.github/workflows/governance-reusable.yml +++ b/.github/workflows/governance-reusable.yml @@ -103,17 +103,51 @@ jobs: fi bash "$RUNNER_TEMP/check-allowed-actions.sh" \ "$RUNNER_TEMP/allowed-actions.json" .github/workflows + rm -rf .standards-checkout + + actions-policy-live: + name: Live Actions policy (credentialed advisory) + runs-on: ${{ inputs.runs-on }} + timeout-minutes: 5 + permissions: + contents: read + steps: + - name: Checkout standards for the live policy gate + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: hyperpolymath/standards + ref: main + sparse-checkout: | + rhodium-standard-repositories/actions-allowlist + scripts/check-actions-policy.sh + sparse-checkout-cone-mode: false + + - name: Report unavailable live policy credential + env: + GH_TOKEN: ${{ secrets.HYPATIA_SCAN_PAT }} + if: ${{ env.GH_TOKEN == '' }} + run: | + echo "::notice::Live Actions policy was not evaluated: HYPATIA_SCAN_PAT was not supplied by the caller. The separate tree allowlist gate still ran." + # shellcheck disable=SC2016 + printf '%s\n' \ + '### Live Actions policy not evaluated' \ + '' \ + 'The caller did not supply `HYPATIA_SCAN_PAT`. This advisory job' \ + 'does not claim that the repository Administration setting passed.' \ + 'The independent tree allowlist preflight remains enforced.' \ + >> "$GITHUB_STEP_SUMMARY" - name: Check live Actions policy env: - # GitHub's job token cannot read this Administration endpoint. Keep - # this dependency explicit: missing/expired credentials must make the - # live control red rather than silently reducing it to a tree check. + # The ordinary job token cannot read the repository Administration + # endpoint. When a caller deliberately supplies this credential, an + # API/authentication failure or real policy violation remains red. GH_TOKEN: ${{ secrets.HYPATIA_SCAN_PAT }} + if: ${{ env.GH_TOKEN != '' }} run: | - rm -rf .standards-checkout - bash "$RUNNER_TEMP/check-actions-policy.sh" \ - "$GITHUB_REPOSITORY" "$RUNNER_TEMP/allowed-actions.json" + bash scripts/check-actions-policy.sh \ + "$GITHUB_REPOSITORY" \ + rhodium-standard-repositories/actions-allowlist/allowed-actions.json validate-hypatia-baseline: name: Validate Hypatia Baseline From 26112958664f295cd0ffa28936fc940aa001acda Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sat, 29 Aug 2026 02:38:55 +0100 Subject: [PATCH 2/4] fix(governance): distinguish unavailable live audit --- .github/workflows/allowlist-preflight-reusable.yml | 14 ++++++++++++++ .github/workflows/governance-reusable.yml | 14 ++++++++++++++ scripts/check-actions-policy.sh | 7 ++++--- scripts/tests/actions-policy-486-test.sh | 2 ++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/.github/workflows/allowlist-preflight-reusable.yml b/.github/workflows/allowlist-preflight-reusable.yml index b722ec0c..35c9578c 100644 --- a/.github/workflows/allowlist-preflight-reusable.yml +++ b/.github/workflows/allowlist-preflight-reusable.yml @@ -91,6 +91,20 @@ jobs: GH_TOKEN: ${{ secrets.HYPATIA_SCAN_PAT }} if: ${{ env.GH_TOKEN != '' }} run: | + set +e bash scripts/check-actions-policy.sh \ "$GITHUB_REPOSITORY" \ rhodium-standard-repositories/actions-allowlist/allowed-actions.json + rc=$? + set -e + if [ "$rc" -eq 3 ]; then + echo "::notice::Live Actions policy was not evaluated: the supplied credential could not read the Administration endpoint." + printf '%s\n' \ + '### Live Actions policy not evaluated' \ + '' \ + 'A credential was supplied, but the Administration API was unavailable.' \ + 'No live-policy pass is claimed; rotate or re-authorise the credential.' \ + >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + exit "$rc" diff --git a/.github/workflows/governance-reusable.yml b/.github/workflows/governance-reusable.yml index 22d32364..5c095e94 100644 --- a/.github/workflows/governance-reusable.yml +++ b/.github/workflows/governance-reusable.yml @@ -145,9 +145,23 @@ jobs: GH_TOKEN: ${{ secrets.HYPATIA_SCAN_PAT }} if: ${{ env.GH_TOKEN != '' }} run: | + set +e bash scripts/check-actions-policy.sh \ "$GITHUB_REPOSITORY" \ rhodium-standard-repositories/actions-allowlist/allowed-actions.json + rc=$? + set -e + if [ "$rc" -eq 3 ]; then + echo "::notice::Live Actions policy was not evaluated: the supplied credential could not read the Administration endpoint." + printf '%s\n' \ + '### Live Actions policy not evaluated' \ + '' \ + 'A credential was supplied, but the Administration API was unavailable.' \ + 'No live-policy pass is claimed; rotate or re-authorise the credential.' \ + >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + exit "$rc" validate-hypatia-baseline: name: Validate Hypatia Baseline diff --git a/scripts/check-actions-policy.sh b/scripts/check-actions-policy.sh index a03a4946..65a8d117 100755 --- a/scripts/check-actions-policy.sh +++ b/scripts/check-actions-policy.sh @@ -3,7 +3,8 @@ # Check the LIVE GitHub Actions policy for a repository (standards#486). # # Usage: check-actions-policy.sh [allowed-actions.json] -# Exit: 0 compliant | 1 policy violation/API failure | 2 setup error +# Exit: 0 compliant | 1 policy violation | 2 local setup error | +# 3 live policy unavailable (authentication/API failure) set -euo pipefail REPOSITORY="${1:?usage: check-actions-policy.sh [allowed-actions.json]}" @@ -16,7 +17,7 @@ command -v "$GH_BIN" >/dev/null 2>&1 || { echo "ERROR: GitHub CLI not found: $GH permissions="$($GH_BIN api "repos/$REPOSITORY/actions/permissions")" || { echo "ERROR: could not read live Actions permissions for $REPOSITORY" >&2 - exit 1 + exit 3 } enabled="$(jq -r '.enabled // false' <<<"$permissions")" @@ -33,7 +34,7 @@ case "$allowed" in selected) selected="$($GH_BIN api "repos/$REPOSITORY/actions/permissions/selected-actions")" || { echo "ERROR: could not read selected Actions policy for $REPOSITORY" >&2 - exit 1 + exit 3 } count="$(jq -r '(.patterns_allowed // []) | length' <<<"$selected")" [ "$count" -gt 0 ] || { diff --git a/scripts/tests/actions-policy-486-test.sh b/scripts/tests/actions-policy-486-test.sh index c4bb25ce..addbfedd 100755 --- a/scripts/tests/actions-policy-486-test.sh +++ b/scripts/tests/actions-policy-486-test.sh @@ -36,6 +36,7 @@ if [[ "$args" == *"/selected-actions "* ]]; then fi case "$scenario" in + api-unavailable) exit 4 ;; sha-off|setter-reset) printf '%s\n' '{"enabled":true,"allowed_actions":"all","sha_pinning_required":false}' ;; selected-empty|selected-missing|selected-ok|setter-selected) printf '%s\n' '{"enabled":true,"allowed_actions":"selected","sha_pinning_required":true}' ;; @@ -61,6 +62,7 @@ expect() { } expect "all + SHA pinning passes" 0 all-ok "$CHECK" owner/repo "$CANON" +expect "API/authentication failure is unavailable, not a policy verdict" 3 api-unavailable "$CHECK" owner/repo "$CANON" expect "SHA pinning off blocks" 1 sha-off "$CHECK" owner/repo "$CANON" expect "disabled Actions blocks" 1 disabled "$CHECK" owner/repo "$CANON" expect "empty selected allowlist blocks" 1 selected-empty "$CHECK" owner/repo "$CANON" From 541214b84dea7f2a94e5a5ca9506c4a0aadcda28 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sat, 29 Aug 2026 02:52:03 +0100 Subject: [PATCH 3/4] fix(governance): test pinned policy source --- .../workflows/allowlist-preflight-reusable.yml | 9 +++++---- .github/workflows/governance-reusable.yml | 11 +++++++---- scripts/check-workflow-staleness.sh | 15 ++++++++++----- scripts/tests/check-workflow-staleness-test.sh | 17 +++++++++++------ 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/.github/workflows/allowlist-preflight-reusable.yml b/.github/workflows/allowlist-preflight-reusable.yml index 35c9578c..b31e5235 100644 --- a/.github/workflows/allowlist-preflight-reusable.yml +++ b/.github/workflows/allowlist-preflight-reusable.yml @@ -29,7 +29,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: hyperpolymath/standards - ref: main + ref: ${{ job.workflow_sha }} path: .standards-checkout sparse-checkout: | rhodium-standard-repositories/actions-allowlist @@ -65,7 +65,8 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: hyperpolymath/standards - ref: main + ref: ${{ job.workflow_sha }} + path: .standards-checkout sparse-checkout: | rhodium-standard-repositories/actions-allowlist scripts/check-actions-policy.sh @@ -92,9 +93,9 @@ jobs: if: ${{ env.GH_TOKEN != '' }} run: | set +e - bash scripts/check-actions-policy.sh \ + bash .standards-checkout/scripts/check-actions-policy.sh \ "$GITHUB_REPOSITORY" \ - rhodium-standard-repositories/actions-allowlist/allowed-actions.json + .standards-checkout/rhodium-standard-repositories/actions-allowlist/allowed-actions.json rc=$? set -e if [ "$rc" -eq 3 ]; then diff --git a/.github/workflows/governance-reusable.yml b/.github/workflows/governance-reusable.yml index 5c095e94..abd69362 100644 --- a/.github/workflows/governance-reusable.yml +++ b/.github/workflows/governance-reusable.yml @@ -80,7 +80,9 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: hyperpolymath/standards - ref: main + # Exact commit of this reusable workflow. This preserves consumer + # pin semantics and lets a standards PR test its own scripts. + ref: ${{ job.workflow_sha }} path: .standards-checkout sparse-checkout: | rhodium-standard-repositories/actions-allowlist @@ -116,7 +118,8 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: hyperpolymath/standards - ref: main + ref: ${{ job.workflow_sha }} + path: .standards-checkout sparse-checkout: | rhodium-standard-repositories/actions-allowlist scripts/check-actions-policy.sh @@ -146,9 +149,9 @@ jobs: if: ${{ env.GH_TOKEN != '' }} run: | set +e - bash scripts/check-actions-policy.sh \ + bash .standards-checkout/scripts/check-actions-policy.sh \ "$GITHUB_REPOSITORY" \ - rhodium-standard-repositories/actions-allowlist/allowed-actions.json + .standards-checkout/rhodium-standard-repositories/actions-allowlist/allowed-actions.json rc=$? set -e if [ "$rc" -eq 3 ]; then diff --git a/scripts/check-workflow-staleness.sh b/scripts/check-workflow-staleness.sh index 51faa3ea..e46ebbb1 100755 --- a/scripts/check-workflow-staleness.sh +++ b/scripts/check-workflow-staleness.sh @@ -41,7 +41,8 @@ set -eo pipefail # calendar. The pre-cache-fix Hypatia scanner (#441) is the first entry. # * FORGED — the pin is not a commit of this repository reachable from the # default branch, confirmed server-side (supply-chain integrity). -# * the two structural rules (retired scorecard-enforcer; Scorecard SARIF). +# * the two structural rules (retired scorecard-enforcer; direct consumer +# Scorecard SARIF publication outside the canonical reusable). # Age outside the window is reported as a ::notice for the propagation path to # act on. # @@ -454,9 +455,13 @@ fi for wf in "$REPO_ROOT"/.github/workflows/*.yml "$REPO_ROOT"/.github/workflows/*.yaml; do [ -f "$wf" ] || continue - # Rule: no_scorecard_sarif_code_scanning (structural — independent of pins) - if grep -q "ossf/scorecard-action@" "$wf" && grep -q "github/codeql-action/upload-sarif@" "$wf"; then - echo "::error file=$wf::OSSF Scorecard must not upload SARIF to GitHub Code Scanning unless it runs for every PR head commit." + # Rule: no direct consumer-owned Scorecard SARIF publisher. The canonical + # reusable in standards owns publication so alert delivery and policy can be + # repaired once rather than drifting across the estate. + if [ "$IS_STANDARDS" = "false" ] && \ + grep -q "ossf/scorecard-action@" "$wf" && \ + grep -q "github/codeql-action/upload-sarif@" "$wf"; then + echo "::error file=$wf::Direct Scorecard SARIF publication is retired. Call standards/scorecard-reusable.yml so publication policy remains centrally controlled." FAILED=1 fi @@ -473,7 +478,7 @@ for wf in "$REPO_ROOT"/.github/workflows/*.yml "$REPO_ROOT"/.github/workflows/*. done if [ $FAILED -ne 0 ]; then - echo "::error::Staleness gate failed. Each error above names a specific defect: a pin predating a known false-green fix (refresh it — waiting will not help), a pin that is not a published standards commit, a retired scorecard-enforcer.yml, or Scorecard uploading SARIF to Code Scanning. Pins that are merely old are reported as notices and do not fail." + echo "::error::Staleness gate failed. Each error above names a specific defect: a pin predating a known false-green fix (refresh it — waiting will not help), a pin that is not a published standards commit, a retired scorecard-enforcer.yml, or a consumer-owned direct Scorecard SARIF publisher. Pins that are merely old are reported as notices and do not fail." exit 1 fi diff --git a/scripts/tests/check-workflow-staleness-test.sh b/scripts/tests/check-workflow-staleness-test.sh index 6c211fa5..46c75d14 100755 --- a/scripts/tests/check-workflow-staleness-test.sh +++ b/scripts/tests/check-workflow-staleness-test.sh @@ -62,8 +62,8 @@ run_case() { local desc="$1" expected="$2" repo="$3"; shift 3 TOTAL=$((TOTAL + 1)) set +e - env "$@" \ - GITHUB_REPOSITORY="hyperpolymath/test-repo" \ + env GITHUB_REPOSITORY="hyperpolymath/test-repo" \ + "$@" \ STALENESS_STANDARDS_DIR="$FIX" \ bash "$CHECK_SCRIPT" "$repo" >/dev/null 2>&1 local rc=$? @@ -85,8 +85,8 @@ run_case_out() { TOTAL=$((TOTAL + 1)) local out rc set +e - out=$(env "$@" \ - GITHUB_REPOSITORY="hyperpolymath/test-repo" \ + out=$(env GITHUB_REPOSITORY="hyperpolymath/test-repo" \ + "$@" \ STALENESS_STANDARDS_DIR="$FIX" \ bash "$CHECK_SCRIPT" "$repo" 2>&1) rc=$? @@ -183,7 +183,7 @@ R="$TEST_DIR/enforcer"; mk_repo "$R" touch "$R/.github/workflows/scorecard-enforcer.yml" run_case "retired scorecard-enforcer.yml fails" 1 "$R" -# ── 9. Direct Scorecard SARIF upload -> fail ──────────────────────────────── +# ── 9. Consumer-owned direct Scorecard SARIF upload -> fail ──────────────── R="$TEST_DIR/sarif"; mk_repo "$R" cat > "$R/.github/workflows/scorecard.yml" <<'EOF' name: Scorecard @@ -194,7 +194,12 @@ jobs: - uses: ossf/scorecard-action@abc - uses: github/codeql-action/upload-sarif@xyz EOF -run_case "direct Scorecard SARIF upload fails" 1 "$R" +run_case "consumer direct Scorecard SARIF upload fails" 1 "$R" + +# The standards repository contains the canonical reusable that consumers call. +# Its uploader is the controlled source, not a forbidden ad hoc consumer copy. +run_case "canonical standards Scorecard SARIF uploader passes" 0 "$R" \ + GITHUB_REPOSITORY=hyperpolymath/standards # ── 10. No workflows dir -> pass ──────────────────────────────────────────── R="$TEST_DIR/empty"; mkdir -p "$R" From 28f7a2cba34c51ebccbc4e99acd4cb7cbe07c71a Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sat, 29 Aug 2026 02:55:27 +0100 Subject: [PATCH 4/4] fix(governance): validate pinned staleness source --- .github/workflows/governance-reusable.yml | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/governance-reusable.yml b/.github/workflows/governance-reusable.yml index abd69362..0a26b03e 100644 --- a/.github/workflows/governance-reusable.yml +++ b/.github/workflows/governance-reusable.yml @@ -40,14 +40,18 @@ jobs: echo "has_baseline=false" >> $GITHUB_OUTPUT fi - - name: Clone standards repository - run: | - # Treeless partial clone: full commit graph (needed by the staleness - # gate's ancestry / commits-behind / age math) without paying for - # every tree+blob in history. Falls back to a deep clone if the - # server rejects the partial-clone filter. - git clone --filter=tree:0 https://github.com/hyperpolymath/standards.git "$HOME/standards" \ - || git clone --depth 200 https://github.com/hyperpolymath/standards.git "$HOME/standards" + - name: Checkout pinned standards history + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: hyperpolymath/standards + # The gate needs the full graph for ancestry and age checks, but its + # script must come from the exact reusable commit rather than moving + # main. This also lets a Standards PR test its own validator change. + ref: ${{ job.workflow_sha }} + path: .standards-history + fetch-depth: 0 + filter: tree:0 + persist-credentials: false - name: Run staleness check env: @@ -60,8 +64,9 @@ jobs: # past this commit: the script comes from standards HEAD, but the job # definition comes from whatever SHA the consumer pins. GITHUB_TOKEN: ${{ github.token }} + STALENESS_STANDARDS_DIR: ${{ github.workspace }}/.standards-history run: | - bash "$HOME/standards/scripts/check-workflow-staleness.sh" . + bash .standards-history/scripts/check-workflow-staleness.sh . allowlist-preflight: name: Allowlist Preflight