Skip to content
Closed
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
57 changes: 52 additions & 5 deletions .github/workflows/allowlist-preflight-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ permissions:

jobs:
preflight:
name: Actions policy (tree + live)
name: Actions policy (tree)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 HIGH RISK

Renaming the job from Actions policy (tree + live) to Actions policy (tree) is a breaking change. Any repository using this reusable workflow with the old name specified as a 'Required' status check in branch protection will have its merges blocked.

runs-on: ubuntu-latest
timeout-minutes: 5
steps:
Expand All @@ -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
Expand All @@ -56,9 +56,56 @@ 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: ${{ job.workflow_sha }}
path: .standards-checkout
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 == '' }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM RISK

The 'if' condition is evaluated before the step-level 'env' block is applied. This means 'env.GH_TOKEN' will be empty at the time of check, and this notice will always be displayed. Reference the secret context directly:

if: ${{ secrets.HYPATIA_SCAN_PAT == '' }}

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 != '' }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 HIGH RISK

The 'if' condition is evaluated before the step-level 'env' block is applied. This means 'env.GH_TOKEN' will be empty, and this security check will always be skipped. Use the secret context directly to ensure the policy check runs when intended:

if: ${{ secrets.HYPATIA_SCAN_PAT != '' }}

run: |
bash "$RUNNER_TEMP/check-actions-policy.sh" \
"$GITHUB_REPOSITORY" "$RUNNER_TEMP/allowed-actions.json"
set +e
bash .standards-checkout/scripts/check-actions-policy.sh \
"$GITHUB_REPOSITORY" \
.standards-checkout/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"
88 changes: 72 additions & 16 deletions .github/workflows/governance-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -80,7 +85,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
Expand All @@ -103,17 +110,66 @@ 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: ${{ job.workflow_sha }}
path: .standards-checkout
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 == '' }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM RISK

The 'if' condition is evaluated before the step-level 'env' block is applied. This means 'env.GH_TOKEN' will be empty, and this notice will always be displayed regardless of secret availability. Use secrets.HYPATIA_SCAN_PAT directly.

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 != '' }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 HIGH RISK

The 'if' condition is evaluated before the step-level 'env' block is applied. This causes the security gate to be skipped even when the required secret is provided. Reference the secret directly in the condition.

run: |
rm -rf .standards-checkout
bash "$RUNNER_TEMP/check-actions-policy.sh" \
"$GITHUB_REPOSITORY" "$RUNNER_TEMP/allowed-actions.json"
set +e
bash .standards-checkout/scripts/check-actions-policy.sh \
"$GITHUB_REPOSITORY" \
.standards-checkout/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
Expand Down
7 changes: 4 additions & 3 deletions scripts/check-actions-policy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
# Check the LIVE GitHub Actions policy for a repository (standards#486).
#
# Usage: check-actions-policy.sh <owner/repo> [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 <owner/repo> [allowed-actions.json]}"
Expand All @@ -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")"
Expand All @@ -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 ] || {
Expand Down
15 changes: 10 additions & 5 deletions scripts/check-workflow-staleness.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
# 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.
#
Expand Down Expand Up @@ -454,9 +455,13 @@
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" ] && \

Check failure on line 461 in scripts/check-workflow-staleness.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=AaBLOHM4_AyqEPplzi5-&open=AaBLOHM4_AyqEPplzi5-&pullRequest=685
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."

Check warning on line 464 in scripts/check-workflow-staleness.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Redirect this error message to stderr (>&2).

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBLOHM4_AyqEPplzi5_&open=AaBLOHM4_AyqEPplzi5_&pullRequest=685
FAILED=1
fi

Expand All @@ -473,7 +478,7 @@
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."

Check warning on line 481 in scripts/check-workflow-staleness.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Redirect this error message to stderr (>&2).

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBLOHM4_AyqEPplzi6A&open=AaBLOHM4_AyqEPplzi6A&pullRequest=685
exit 1
fi

Expand Down
2 changes: 2 additions & 0 deletions scripts/tests/actions-policy-486-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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}' ;;
Expand All @@ -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"
Expand Down
17 changes: 11 additions & 6 deletions scripts/tests/check-workflow-staleness-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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=$?
Expand All @@ -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=$?
Expand Down Expand Up @@ -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
Expand All @@ -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"
Expand Down
Loading