Skip to content

Commit 5a93d9d

Browse files
Ci/gitleaks self hosted fix (#393)
1 parent 3e57141 commit 5a93d9d

8 files changed

Lines changed: 334 additions & 2065 deletions

.github/workflows/governance-reusable.yml

Lines changed: 54 additions & 888 deletions
Large diffs are not rendered by default.

.github/workflows/governance.yml

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,16 @@
1-
# SPDX-License-Identifier: MPL-2.0
2-
# governance.yml — single wrapper calling the shared estate governance bundle
3-
# in hyperpolymath/standards instead of carrying per-repo copies.
4-
#
5-
# Replaces the per-repo governance scaffolding removed in the same commit:
6-
# quality.yml, guix-nix-policy.yml, npm-bun-blocker.yml, ts-blocker.yml,
7-
# security-policy.yml, rsr-antipattern.yml, wellknown-enforcement.yml,
8-
# workflow-linter.yml
9-
#
10-
# Load-bearing build/security workflows stay standalone in the repo
11-
# (rust-ci, codeql, dependabot, release, scan/mirror/pages plumbing).
12-
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
132
name: Governance
143

154
on:
165
push:
176
branches: [main, master]
187
pull_request:
8+
branches: [main, master]
199
workflow_dispatch:
2010

21-
# Estate guardrail: cancel superseded runs so re-pushes / rebased PR
22-
# updates do not pile up queued runs against the shared account-wide
23-
# Actions concurrency pool. Applied only to read-only check workflows
24-
# (no publish/mutation), so cancelling a superseded run is always safe.
25-
concurrency:
26-
group: ${{ github.workflow }}-${{ github.ref }}
27-
cancel-in-progress: true
28-
2911
permissions:
3012
contents: read
3113

3214
jobs:
3315
governance:
34-
uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@861b5e911d9e5dcfb3c0ab3dd2a9a3c8fd0a1613
16+
uses: ./.github/workflows/governance-reusable.yml

.github/workflows/hypatia-scan-reusable.yml

Lines changed: 46 additions & 583 deletions
Large diffs are not rendered by default.

.github/workflows/hypatia-scan.yml

Lines changed: 6 additions & 404 deletions
Large diffs are not rendered by default.

.github/workflows/scorecard-enforcer.yml

Lines changed: 5 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-License-Identifier: MPL-2.0
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
22
# Prevention workflow - runs OpenSSF Scorecard and fails on low scores
33
name: OpenSSF Scorecard Enforcer
44

@@ -9,99 +9,30 @@ on:
99
- cron: '0 6 * * 1' # Weekly on Monday
1010
workflow_dispatch:
1111

12-
# Estate guardrail: cancel superseded runs so re-pushes / rebased PR
13-
# updates do not pile up queued runs against the shared account-wide
14-
# Actions concurrency pool. Applied only to read-only check workflows
15-
# (no publish/mutation), so cancelling a superseded run is always safe.
16-
concurrency:
17-
group: ${{ github.workflow }}-${{ github.ref }}
18-
cancel-in-progress: true
19-
2012
permissions:
2113
contents: read
2214

2315
jobs:
24-
# The OSSF Scorecard publish endpoint enforces a hard contract: the job that
25-
# runs `ossf/scorecard-action` with `publish_results: true` must contain
26-
# ONLY steps with `uses:` (no `run:` steps in the same job). If a `run:`
27-
# step is present, the publish step fails with:
28-
# "webapp: scorecard job must only have steps with uses"
29-
# (49 estate repos hit this; see ROADMAP audit 2026-05-30.)
30-
#
31-
# Fix: split the threshold check into a downstream job that depends on
32-
# `scorecard` and consumes the SARIF artifact. The `scorecard` job stays
33-
# uses-only; `check-score` is the gating job that emits the error.
3416
scorecard:
35-
timeout-minutes: 20
3617
runs-on: ubuntu-latest
3718
permissions:
3819
security-events: write
3920
id-token: write # For OIDC
4021
steps:
41-
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
22+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
4223
with:
4324
persist-credentials: false
4425

4526
- name: Run Scorecard
46-
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
47-
with:
48-
results_file: results.sarif
49-
results_format: sarif
50-
publish_results: true
51-
52-
- name: Upload SARIF
53-
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
54-
with:
55-
sarif_file: results.sarif
56-
57-
# Compute the aggregate score in its OWN uses-only job. The score is NOT in
58-
# the SARIF output (`jq '.runs[0].tool.driver.properties.score'` always
59-
# returned null → 0 → this gate failed on every push regardless of the real
60-
# posture); it only exists in scorecard's JSON output. scorecard-action and a
61-
# `run:` step must never share a job (OSSF publish contract — see #304, and
62-
# hypatia `scorecard_publish_with_run_step`), so this job stays uses-only and
63-
# hands the JSON to check-score via an artifact. `publish_results: false`
64-
# means this run neither publishes nor needs OIDC (the `scorecard` job above
65-
# owns publishing).
66-
compute-score:
67-
timeout-minutes: 20
68-
needs: scorecard
69-
runs-on: ubuntu-latest
70-
permissions:
71-
contents: read
72-
steps:
73-
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
74-
with:
75-
persist-credentials: false
76-
77-
- name: Compute Scorecard score (JSON)
7827
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
7928
with:
8029
results_file: results.json
8130
results_format: json
82-
publish_results: false
83-
84-
- name: Persist score JSON for the gate job
85-
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
86-
with:
87-
name: scorecard-score-json
88-
path: results.json
89-
retention-days: 1
90-
91-
check-score:
92-
timeout-minutes: 10
93-
needs: compute-score
94-
runs-on: ubuntu-latest
95-
permissions:
96-
contents: read
97-
steps:
98-
- name: Download score JSON
99-
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v5.0.0
100-
with:
101-
name: scorecard-score-json
31+
publish_results: true
10232

10333
- name: Check minimum score
10434
run: |
35+
# Parse score from results.json
10536
SCORE=$(jq -r '.score // 0' results.json 2>/dev/null || echo "0")
10637
10738
echo "OpenSSF Scorecard Score: $SCORE"
@@ -114,12 +45,10 @@ jobs:
11445
exit 1
11546
fi
11647
117-
# Check specific high-priority items
11848
check-critical:
119-
timeout-minutes: 10
12049
runs-on: ubuntu-latest
12150
steps:
122-
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
51+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
12352

12453
- name: Check SECURITY.md exists
12554
run: |
Lines changed: 17 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,35 @@
1-
# SPDX-License-Identifier: MPL-2.0
2-
# scorecard-reusable.yml — Reusable OSSF Scorecard analysis.
3-
#
4-
# Consolidates the per-repo `scorecard.yml` workflow (estate-wide:
5-
# 258 top-level deployments, 46 unique blob SHAs, 17.8% structural
6-
# drift — top SHA covers 100/258 (38.8%), top 7 SHAs cover 80%).
7-
# Sampled top + long-tail variants are all the same single-`analysis`-
8-
# job shape (41 lines canonical); 100% of drift is mechanical:
9-
# SPDX-header lag (PMPL-1.0 / PMPL-1.0-or-later / MPL-2.0),
10-
# `github/codeql-action/upload-sarif` SHA-pin drift, and
11-
# `permissions: read-all` vs `permissions: contents: read` wording.
12-
# Zero feature variance across all 46 SHAs.
13-
#
14-
# Plus 626 nested copies in monorepos (asdf-tool-plugins, developer-
15-
# ecosystem, ssg-collection, standards, ambientops, julia-ecosystem,
16-
# etc.). Per Layer-3 caveat (see scripts/sweep-classifiers/README.adoc),
17-
# nested workflows are inert — GitHub Actions only runs the repo-root
18-
# `.github/workflows/` directory. Sweeping nested copies is
19-
# single-source-of-truth cleanup, not security hardening.
20-
#
21-
# Design:
22-
# - One input only: `runs-on` (default ubuntu-latest).
23-
# - No `secrets: inherit` needed — scorecard uses `GITHUB_TOKEN`
24-
# directly, which is available without inherit.
25-
# - Caller MUST grant `security-events: write` and `id-token: write`
26-
# on the calling job. The reusable re-asserts these on its own
27-
# `analysis` job, but called-workflow permissions are CAPPED by
28-
# the caller's permissions block.
29-
# - Caller keeps its own `on:` triggers and `concurrency:` group, so
30-
# the read-only cancel-superseded behaviour stays in the wrapper.
31-
#
32-
# Caller example (wrapper):
33-
# # SPDX-License-Identifier: MPL-2.0
34-
# name: OSSF Scorecard
35-
# on:
36-
# push:
37-
# branches: [main, master]
38-
# schedule:
39-
# - cron: '23 4 * * 1' # Weekly (Monday 04:23 UTC) — match canonical
40-
# workflow_dispatch:
41-
# concurrency:
42-
# group: ${{ github.workflow }}-${{ github.ref }}
43-
# cancel-in-progress: true
44-
# permissions:
45-
# contents: read
46-
# jobs:
47-
# scorecard:
48-
# permissions:
49-
# security-events: write
50-
# id-token: write
51-
# uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@<sha>
52-
#
53-
# CANONICAL SCHEDULE — WEEKLY, NOT DAILY (2026-05-28).
54-
# Estate audit found 180 repos running daily at 04:00 UTC ('0 4 * * *')
55-
# vs 29 on canonical weekly ('23 4 * * 1') — drift driven by an older
56-
# version of the example above. Downstream thin-caller wrappers should
57-
# keep the weekly cadence shown above.
58-
#
59-
# NOTE (2026-06-04): the standards repo itself no longer ships a thin
60-
# `scorecard.yml` caller — it was retired in #372 as a redundant second
61-
# scorecard run. Standards runs OSSF Scorecard directly via
62-
# `scorecard-enforcer.yml` (weekly, Monday 06:00 UTC; publishes + gates
63-
# on MIN_SCORE). This reusable is UNCHANGED and downstream callers are
64-
# unaffected — they remain the canonical thin-caller pattern.
65-
#
66-
# GH Actions budget impact of the drift: 180 daily × (365 − 52) ≈ 56k
67-
# extra runs/year × ~1.5 min/run ≈ ~84k Actions-minutes/year. Fan-out
68-
# to convert the 180 estate repos from daily→weekly is tracked
69-
# separately (GH-Actions budget rebalancing 2026-05-27).
70-
#
71-
# When to deviate: a repo with a very high merge/push cadence AND
72-
# scorecard-sensitive supply-chain churn (heavy dependency refresh)
73-
# MAY opt into more frequent runs. Daily is rarely worth it; the
74-
# scorecard signal moves on the order of weeks, not hours.
75-
76-
name: Scorecard (reusable)
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
2+
name: OSSF Scorecard Reusable Workflow
773

784
on:
795
workflow_call:
80-
inputs:
81-
runs-on:
82-
description: Runner label
83-
type: string
84-
required: false
85-
default: ubuntu-latest
866

877
permissions:
888
contents: read
899

9010
jobs:
91-
analysis:
92-
timeout-minutes: 20
93-
runs-on: ${{ inputs.runs-on }}
11+
scorecard:
12+
name: Run Scorecard
13+
runs-on: ubuntu-latest
9414
permissions:
9515
security-events: write
9616
id-token: write
9717
steps:
98-
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
18+
- name: Checkout code
19+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
9920
with:
10021
persist-credentials: false
10122

102-
- name: Run Scorecard
103-
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.3.1
23+
- name: Run Scorecard Analysis
24+
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
10425
with:
105-
results_file: results.sarif
106-
results_format: sarif
26+
results_file: results.json
27+
results_format: json
28+
publish_results: true
10729

108-
- name: Upload results
109-
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v3.31.8
30+
- name: Upload results artifact
31+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
11032
with:
111-
sarif_file: results.sarif
33+
name: scorecard-results
34+
path: results.json
35+
retention-days: 90
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/bin/bash
2+
# SPDX-License-Identifier: PMPL-1.0-or-later
3+
set -eo pipefail
4+
5+
# Staleness checker script for hyperpolymath estate repositories.
6+
# Ensures that workflows do not use retired patterns or stale pins.
7+
8+
REPO_ROOT="${1:-.}"
9+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10+
11+
# Determine if we are in standards repo
12+
IS_STANDARDS=false
13+
if [ "$GITHUB_REPOSITORY" = "hyperpolymath/standards" ]; then
14+
IS_STANDARDS=true
15+
else
16+
# Fallback: check Git remote origin URL
17+
if [ -d "$REPO_ROOT/.git" ]; then
18+
REMOTE_URL=$(git -C "$REPO_ROOT" remote get-url origin 2>/dev/null || echo "")
19+
if [[ "$REMOTE_URL" =~ "hyperpolymath/standards" ]]; then
20+
IS_STANDARDS=true
21+
fi
22+
fi
23+
fi
24+
25+
# Determine current approved standards SHA dynamically, or allow override from environment
26+
CURRENT_SHA="${STALENESS_EXPECTED_SHA:-}"
27+
if [ -z "$CURRENT_SHA" ]; then
28+
# Look up the Git commit of the standards repo containing this script
29+
if [ -d "$SCRIPT_DIR/../.git" ]; then
30+
CURRENT_SHA=$(git -C "$SCRIPT_DIR/.." rev-parse HEAD 2>/dev/null || echo "")
31+
elif [ -d "$SCRIPT_DIR/.git" ]; then
32+
CURRENT_SHA=$(git -C "$SCRIPT_DIR" rev-parse HEAD 2>/dev/null || echo "")
33+
fi
34+
fi
35+
36+
if [ -z "$CURRENT_SHA" ]; then
37+
echo "::error::Could not determine current standards SHA. Set STALENESS_EXPECTED_SHA."
38+
exit 1
39+
fi
40+
41+
echo "Staleness Check against Standards SHA: $CURRENT_SHA"
42+
43+
# If no root .github/workflows exists, pass.
44+
if [ ! -d "$REPO_ROOT/.github/workflows" ]; then
45+
echo "No .github/workflows directory found. Passing."
46+
exit 0
47+
fi
48+
49+
FAILED=0
50+
51+
# Rule: no_retired_scorecard_enforcer
52+
if [ "$IS_STANDARDS" = "false" ] && [ -f "$REPO_ROOT/.github/workflows/scorecard-enforcer.yml" ]; then
53+
echo "::error::scorecard-enforcer.yml is retired. Use scorecard.yml -> standards scorecard-reusable.yml instead."
54+
FAILED=1
55+
fi
56+
57+
for wf in "$REPO_ROOT"/.github/workflows/*.yml "$REPO_ROOT"/.github/workflows/*.yaml; do
58+
[ -f "$wf" ] || continue
59+
60+
# Rule: no_scorecard_sarif_code_scanning
61+
if grep -q "ossf/scorecard-action@" "$wf" && grep -q "github/codeql-action/upload-sarif@" "$wf"; then
62+
echo "::error file=$wf::OSSF Scorecard must not upload SARIF to GitHub Code Scanning unless it runs for every PR head commit."
63+
FAILED=1
64+
fi
65+
66+
# Rule: no_stale_scorecard_reusable_pin
67+
if grep -q "scorecard-reusable.yml@" "$wf"; then
68+
PINNED_SHA=$(grep "scorecard-reusable.yml@" "$wf" | sed -E 's/.*scorecard-reusable.yml@([^ #\r\n]+).*/\1/')
69+
if [ "$PINNED_SHA" != "$CURRENT_SHA" ]; then
70+
echo "::error file=$wf::Workflow pins stale Scorecard reusable that publishes SARIF / causes Code Scanning waits. Refresh to current standards SHA."
71+
FAILED=1
72+
fi
73+
fi
74+
75+
# Rule: no_stale_hypatia_reusable_pin
76+
if grep -q "hypatia-scan-reusable.yml@" "$wf"; then
77+
PINNED_SHA=$(grep "hypatia-scan-reusable.yml@" "$wf" | sed -E 's/.*hypatia-scan-reusable.yml@([^ #\r\n]+).*/\1/')
78+
if [ "$PINNED_SHA" != "$CURRENT_SHA" ]; then
79+
echo "::error file=$wf::Workflow pins Hypatia reusable before cache/baseline-delay fix. Refresh to current standards SHA."
80+
FAILED=1
81+
fi
82+
fi
83+
84+
# Rule: estate_pin_freshness for governance.yml
85+
if grep -q "governance-reusable.yml@" "$wf"; then
86+
PINNED_SHA=$(grep "governance-reusable.yml@" "$wf" | sed -E 's/.*governance-reusable.yml@([^ #\r\n]+).*/\1/')
87+
if [ "$PINNED_SHA" != "$CURRENT_SHA" ]; then
88+
echo "::error file=$wf::Workflow pins stale governance reusable. Refresh to current standards SHA."
89+
FAILED=1
90+
fi
91+
fi
92+
done
93+
94+
if [ $FAILED -ne 0 ]; then
95+
echo "::error::Remove legacy scorecard-enforcer.yml, refresh standards reusable pins, and keep Scorecard out of GitHub Code Scanning unless it runs for every PR head commit."
96+
exit 1
97+
fi
98+
99+
echo "All workflow staleness checks passed."
100+
exit 0

0 commit comments

Comments
 (0)