diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8ad06a41..1ad033ba 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -28,8 +28,14 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false +# Must cover everything codeql-reusable's `analyze` job requests: a reusable +# cannot widen its caller's grant, and asking for more aborts the run as +# `startup_failure` with zero jobs. security-events: write is what uploads +# the SARIF results that branch protection requires. permissions: + actions: read contents: read + security-events: write jobs: analyze-js: diff --git a/.github/workflows/deno-ci-reusable.yml b/.github/workflows/deno-ci-reusable.yml index 62a54fc9..7232051d 100644 --- a/.github/workflows/deno-ci-reusable.yml +++ b/.github/workflows/deno-ci-reusable.yml @@ -41,8 +41,13 @@ on: required: false default: v2.x +# Only `contents: read` is requested. A reusable workflow may narrow the +# caller's permissions but never widen them: requesting a permission the +# caller has not granted aborts the run as `startup_failure` with ZERO +# jobs — no logs, no red step, just an empty run. The previous +# `actions: read` here was used by no job in this file, so every caller +# granting only `contents: read` (the estate default) failed to start. permissions: - actions: read contents: read jobs: diff --git a/.github/workflows/elixir-ci-reusable.yml b/.github/workflows/elixir-ci-reusable.yml index b359b1c5..23d6a76b 100644 --- a/.github/workflows/elixir-ci-reusable.yml +++ b/.github/workflows/elixir-ci-reusable.yml @@ -103,8 +103,13 @@ on: required: false default: "." +# Only `contents: read` is requested. A reusable workflow may narrow the +# caller's permissions but never widen them: requesting a permission the +# caller has not granted aborts the run as `startup_failure` with ZERO +# jobs — no logs, no red step, just an empty run. The previous +# `actions: read` here was used by no job in this file, so every caller +# granting only `contents: read` (the estate default) failed to start. permissions: - actions: read contents: read jobs: diff --git a/.github/workflows/governance-reusable.yml b/.github/workflows/governance-reusable.yml index 113ee245..a682b88f 100644 --- a/.github/workflows/governance-reusable.yml +++ b/.github/workflows/governance-reusable.yml @@ -84,9 +84,18 @@ jobs: - name: Run allowlist preflight check run: | - cp .standards-checkout/scripts/check-actions-policy.sh "$RUNNER_TEMP/" + # check-actions-policy.sh `exec`s its SIBLING check-allowed-actions.sh + # via "${0%/*}/...". Copying only the first script and then deleting + # the checkout left that sibling missing, so the step died with exit + # 127 (command not found) on every run. Stage both, plus the canonical + # allowlist itself — consumer repos have no copy of it in their tree. + cp .standards-checkout/scripts/check-actions-policy.sh \ + .standards-checkout/scripts/check-allowed-actions.sh "$RUNNER_TEMP/" + cp .standards-checkout/rhodium-standard-repositories/actions-allowlist/allowed-actions.json \ + "$RUNNER_TEMP/allowed-actions.json" rm -rf .standards-checkout - bash "$RUNNER_TEMP/check-actions-policy.sh" .github/workflows + ALLOWLIST_JSON="$RUNNER_TEMP/allowed-actions.json" \ + bash "$RUNNER_TEMP/check-actions-policy.sh" .github/workflows validate-hypatia-baseline: name: Validate Hypatia Baseline diff --git a/.github/workflows/rust-ci-reusable.yml b/.github/workflows/rust-ci-reusable.yml index 89f428cb..f4c3ba51 100644 --- a/.github/workflows/rust-ci-reusable.yml +++ b/.github/workflows/rust-ci-reusable.yml @@ -101,8 +101,13 @@ on: required: false default: "." +# Only `contents: read` is requested. A reusable workflow may narrow the +# caller's permissions but never widen them: requesting a permission the +# caller has not granted aborts the run as `startup_failure` with ZERO +# jobs — no logs, no red step, just an empty run. The previous +# `actions: read` here was used by no job in this file, so every caller +# granting only `contents: read` (the estate default) failed to start. permissions: - actions: read contents: read jobs: diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 9b259806..fad266ef 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -14,9 +14,14 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +# Must cover everything scorecard-reusable's job requests (see codeql.yml). +# id-token: write is the OIDC token Scorecard uses to publish results; +# security-events: write uploads the SARIF. permissions: actions: read contents: read + security-events: write + id-token: write jobs: scorecard: diff --git a/.github/workflows/signed-push-smoke.yml b/.github/workflows/signed-push-smoke.yml index dc962303..c867dc4d 100644 --- a/.github/workflows/signed-push-smoke.yml +++ b/.github/workflows/signed-push-smoke.yml @@ -18,6 +18,10 @@ permissions: jobs: smoke: runs-on: ubuntu-latest + # Safe here because `smoke` is a steps-job. Never add timeout-minutes to a + # job that `uses:` a reusable workflow — it is an illegal key there and + # GitHub refuses to parse the ENTIRE file, killing every job in it. + timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 diff --git a/.machine_readable/REGISTRY.a2ml b/.machine_readable/REGISTRY.a2ml index 52ae7325..a3dd0c60 100644 --- a/.machine_readable/REGISTRY.a2ml +++ b/.machine_readable/REGISTRY.a2ml @@ -45,7 +45,7 @@ name = "A2ML — Attested Markup Language" stream = "foundation" home = "a2ml/" canonical_doc = "a2ml/README.adoc" -source_hash = "sha256:3409e79367d002ba52f4ed1742b2846c1beffdf8acf78c53997a69a32bd46617" +source_hash = "sha256:4ce7ddc2e22f4fe4138dfef944b4eab1ab4bc58e7bd473f9094c518ee0eb0b04" route = "the typed/verified machine-readable document format" [[spec]] @@ -189,7 +189,7 @@ name = "ARG — Adoption Readiness Grades" stream = "readiness" home = "adoption-readiness-grades/" canonical_doc = "adoption-readiness-grades/README.adoc" -source_hash = "sha256:77e3c0d74e9fd037b57dc883804501be1117ac12534d3d817654f2c96919a0e8" +source_hash = "sha256:8943491cf3991b8c6fd5a53bd83657c63978592510cbe79996b4034ab34ae40d" route = "per-language adoption-maturity profile templates" [[spec]] @@ -225,7 +225,7 @@ name = "RSR — Rhodium Standard Repositories" stream = "governance" home = "rhodium-standard-repositories/" canonical_doc = "rhodium-standard-repositories/README.adoc" -source_hash = "sha256:03252ce83c0361887c6a96530c8bba15c4c7f816c07d6d2ffbffd53250e3bf61" +source_hash = "sha256:4cad25af39c3a27a79bf5ad64ea70ec0f6ff888855fdefc09d617f2df4d2d018" route = "the repository-compliance standard every repo is graded against" [[spec]] @@ -269,8 +269,8 @@ id = "publication-pre-flight" name = "Publication Pre-Flight" stream = "governance" home = "publication-pre-flight/" -canonical_doc = "publication-pre-flight/ESTATE-AUDIT-BASELINE-2026-03-30.adoc" -source_hash = "sha256:8e1f3bb0515e80636046332b99639346655d87412e1f2f3613903854213025a1" +canonical_doc = "publication-pre-flight/HOL-SUITABILITY-CHECKLIST.adoc" +source_hash = "sha256:86e93a00784d646d99dcaf412efc3d647a02ff7ac2e38cc1f94c1d6bc775c188" route = "submission gate (HOL + Zenodo checklists)" [[spec]] diff --git a/.machine_readable/scorecards/a2ml.scorecard.a2ml b/.machine_readable/scorecards/a2ml.scorecard.a2ml index 80504db7..c42eca1b 100644 --- a/.machine_readable/scorecards/a2ml.scorecard.a2ml +++ b/.machine_readable/scorecards/a2ml.scorecard.a2ml @@ -84,7 +84,7 @@ text = "Secrets SHOULD be scanned on every push/PR to prevent credential leaks." system = ".github/workflows/secret-scanner.yml (Gitleaks + rust/shell secret jobs)" status = "pass" evidence = "secret-scanner.yml runs gitleaks/gitleaks-action plus rust-secrets and shell-secrets jobs on pull_request and push to main. TruffleHog was retired as redundant to the pinned gitleaks gate (#507/#525)." -check = "test -f a2ml/.github/workflows/secret-scanner.yml && grep -q 'gitleaks/gitleaks-action' a2ml/.github/workflows/secret-scanner.yml" +check = "test -f a2ml/.github/workflows/secret-scanner.yml && grep -qE 'gitleaks/gitleaks-action|secret-scanner-reusable' a2ml/.github/workflows/secret-scanner.yml" effects = "Prevents accidental credential leakage into repo history, which would otherwise require costly rotation/remediation across the hyperpolymath estate." [[should]] diff --git a/.machine_readable/scorecards/avow-protocol.scorecard.a2ml b/.machine_readable/scorecards/avow-protocol.scorecard.a2ml index 744e4a60..5d957dfc 100644 --- a/.machine_readable/scorecards/avow-protocol.scorecard.a2ml +++ b/.machine_readable/scorecards/avow-protocol.scorecard.a2ml @@ -41,7 +41,7 @@ effects = "Three different license identifiers appear: LICENSE declares 'SPDX-Li id = "M5" text = "The codebase MUST be scanned for security vulnerabilities via static analysis on every push and pull request." system = ".github/workflows/codeql.yml (CodeQL Advanced) — runs on push to main, pull_request to main, and weekly schedule, analyzing javascript-typescript and actions languages" -status = "pass" +status = "fail" evidence = ".github/workflows/codeql.yml defines the 'analyze' job with a language matrix (actions, javascript-typescript) using github/codeql-action/init and analyze@v3, triggered on push/pull_request/schedule; this is a real, currently configured CI workflow." check = "cd avow-protocol && test -f .github/workflows/codeql.yml && grep -q 'github/codeql-action/analyze' .github/workflows/codeql.yml" effects = "Downstream repos/consumers get continuous automated code-scanning coverage for JS/TS and GitHub Actions definitions in this repo, reducing risk of shipping known vulnerability patterns; if this workflow were removed, that scanning safety net would be lost estate-wide for this spec." @@ -50,7 +50,7 @@ effects = "Downstream repos/consumers get continuous automated code-scanning cov id = "S1" text = "The spec SHOULD maintain a dedicated threat-model document covering adversaries, trust boundaries, and mitigations." system = "none (manually authored document, no automated freshness/consistency check)" -status = "pass" +status = "fail" evidence = "AVOW-THREAT-MODEL.adoc (24,831 bytes) exists with a substantive threat-actor analysis, threat-landscape matrix (Mass Bot Creation, Verification Bypass, Identity Theft, etc.) and mitigation mapping. Note it is still internally titled 'STAMP Protocol: Comprehensive Threat Model', indicating it has not been fully updated for the AVOW rename." check = "cd avow-protocol && test -f AVOW-THREAT-MODEL.adoc && [ $(wc -c < AVOW-THREAT-MODEL.adoc) -gt 10000 ]" effects = "Reviewers and integrators evaluating AVOW's security posture have a concrete artifact to consult, though the stale STAMP naming inside it means downstream readers may not immediately recognize it as the current AVOW threat model." diff --git a/.machine_readable/scorecards/axel-protocol.scorecard.a2ml b/.machine_readable/scorecards/axel-protocol.scorecard.a2ml index 7d49945b..d07d1982 100644 --- a/.machine_readable/scorecards/axel-protocol.scorecard.a2ml +++ b/.machine_readable/scorecards/axel-protocol.scorecard.a2ml @@ -75,7 +75,7 @@ text = "The project SHOULD track and report an OSSF Scorecard supply-chain secur system = ".github/workflows/scorecard.yml — OSSF Scorecard action, uploads SARIF to code scanning, runs daily and on push" status = "pass" evidence = ".github/workflows/scorecard.yml runs ossf/scorecard-action@4eaacf05... on a daily cron and push to main/master, uploading results.sarif via github/codeql-action/upload-sarif." -check = "test -f axel-protocol/.github/workflows/scorecard.yml && grep -q 'ossf/scorecard-action' axel-protocol/.github/workflows/scorecard.yml" +check = "test -f axel-protocol/.github/workflows/scorecard.yml && grep -qE 'ossf/scorecard-action|scorecard-reusable' axel-protocol/.github/workflows/scorecard.yml" effects = "Consumers evaluating supply-chain trust of this repo (branch protection, pinned actions, etc.) have an ongoing automated signal; without it they'd have to audit manually." [[should]] diff --git a/.machine_readable/scorecards/rhodium-standard-repositories.scorecard.a2ml b/.machine_readable/scorecards/rhodium-standard-repositories.scorecard.a2ml index 60be281d..17c9ebdf 100644 --- a/.machine_readable/scorecards/rhodium-standard-repositories.scorecard.a2ml +++ b/.machine_readable/scorecards/rhodium-standard-repositories.scorecard.a2ml @@ -22,7 +22,7 @@ effects = "Every estate repo is graded against this; a missing spec voids all gr id = "M2" text = "RSR MUST ship a runnable audit tool that scores a repo and exits non-zero on error." system = "rhodium-standard-repositories/rsr-audit.sh (arg parsing fixed standards#387); Justfile validate via scripts/rsr-selfaudit.sh" -status = "pass" +status = "fail" evidence = "rsr-audit.sh runs; invalid --format exits 4; scripts/tests/wave0-false-green-test.sh asserts the arg contract." check = "bash scripts/tests/wave0-false-green-test.sh" effects = "Without a working auditor, RSR grades are hand-waved; downstream badge claims become unverifiable." diff --git a/COMPLIANCE-DASHBOARD.md b/COMPLIANCE-DASHBOARD.md index d740e92c..5017ba4c 100644 --- a/COMPLIANCE-DASHBOARD.md +++ b/COMPLIANCE-DASHBOARD.md @@ -33,7 +33,7 @@ | `anchor-a2ml` | ❌ gap | 0/5 | 0/5 | 0/3 | 15% | – | 2026-07-03 | | `0-ai-gatekeeper-protocol` | ❌ gap | 3/5 | 0/4 | 0/2 | 54% | 3/3 | 2026-07-03 | | `k9-coordination-protocol` | ❌ gap | 3/5 | 2/4 | 0/3 | 100% | 5/5 | 2026-07-03 | -| `avow-protocol` | ❌ gap | 1/5 | 2/4 | 0/3 | 58% | 3/3 | 2026-07-03 | +| `avow-protocol` | ❌ gap | 0/5 | 1/4 | 0/3 | 58% | 1/1 | 2026-07-03 | | `axel-protocol` | ❌ gap | 0/5 | 4/5 | 0/3 | 92% | 4/4 | 2026-07-03 | | `overlay-protocol` | ❌ gap | 1/5 | 0/4 | 0/3 | 50% | 1/1 | 2026-07-03 | | `consent-aware-http` | ❌ gap | 1/5 | 1/5 | 0/3 | 69% | 2/2 | 2026-07-03 | @@ -41,7 +41,7 @@ | `foundations-readiness-grades` | ❌ gap | 2/5 | 1/4 | 0/2 | 72% | 3/3 | 2026-07-03 | | `component-readiness-grades` | ❌ gap | 2/5 | 2/4 | 0/3 | 66% | 4/4 | 2026-07-03 | | `toolchain-readiness-grades` | ❌ gap | 1/5 | 2/4 | 0/3 | 83% | 3/3 | 2026-07-03 | -| `rhodium-standard-repositories` | ❌ gap | 2/3 | 1/2 | 0/1 | 50% | 3/3 | 2026-07-03 | +| `rhodium-standard-repositories` | ❌ gap | 1/3 | 1/2 | 0/1 | 50% | 2/2 | 2026-07-03 | | `session-management-standards` | ❌ gap | 1/5 | 1/4 | 0/3 | 41% | 2/2 | 2026-07-03 | | `did-you-actually-do-that` | ✅ met | 5/5 | 2/3 | 0/2 | 90% | 7/7 | 2026-07-03 | | `ensaid-config` | ❌ gap | 0/5 | 0/3 | 0/3 | 90% | – | 2026-07-03 | @@ -55,9 +55,9 @@ - **Specs registered (local):** 30 - **Specs with a scorecard:** 30 / 30 -- **MUST requirements:** 47 passing / 147 total (73 failing) +- **MUST requirements:** 45 passing / 147 total (75 failing) - **Estate systems coverage:** 67% of 343 graded requirements have a mechanical check -- **Grounded passes:** 87 / 87 (100%) pass rows carry an executable `check` run by `--verify` +- **Grounded passes:** 84 / 84 (100%) pass rows carry an executable `check` run by `--verify` ## How this dashboard stays honest diff --git a/deno.json b/deno.json new file mode 100644 index 00000000..0b556655 --- /dev/null +++ b/deno.json @@ -0,0 +1,39 @@ +{ + "_comment": [ + "Canonical Deno config for hyperpolymath/standards.", + "", + "Its job is to stop `deno lint`/`deno fmt` reporting on files that are", + "COMPILER OUTPUT rather than hand-authored source. Without this file the", + "deno-ci reusable falls back to scanning raw *.js, which swept in the four", + "bsc-generated axel-protocol/src/*.res.js artifacts and failed the lint on", + "prefer-const violations in generated code — code no human wrote and nobody", + "should edit.", + "", + "The exclude list deliberately MIRRORS the one already used by this repo's", + "own .github/workflows/no-js-scan.yml, which classifies *.res.js, *.res.mjs,", + "lib/{js,es6,bs}, out, dist, .deno and generated/ as 'generated/compiled'.", + "Two gates in the same repo disagreeing about what counts as source is the", + "actual defect; this makes them agree rather than inventing new policy.", + "", + "Per estate language policy the .affine sources are primary — every one of", + "those .res.js files already has an .affine sibling." + ], + "exclude": [ + ".git", + "node_modules", + "**/node_modules", + "**/deps", + "**/vendor", + "**/lib/js", + "**/lib/es6", + "**/lib/bs", + "**/out", + "**/dist", + "**/.deno", + "**/generated", + "**/*.res.js", + "**/*.res.mjs", + "**/*.min.js", + "**/*.d.ts" + ] +} diff --git a/scripts/bulk-fix-workflows.py b/scripts/bulk-fix-workflows.py deleted file mode 100644 index 6edfecf4..00000000 --- a/scripts/bulk-fix-workflows.py +++ /dev/null @@ -1,355 +0,0 @@ -#!/usr/bin/env python3 -""" -Bulk script to replace duplicate workflow files with calls to root reusable workflows. - -Usage: python3 scripts/bulk-fix-workflows.py --dry-run - python3 scripts/bulk-fix-workflows.py --execute -""" - -import os -import re -import sys -from pathlib import Path - -REPO_ROOT = Path(__file__).parent.parent.absolute() - -# Workflows to fix and their reusable equivalents -WORKFLOWS = { - "codeql.yml": { - "reusable": "codeql-reusable.yml", - "triggers": ["push", "pull_request", "schedule"], - "content": """# SPDX-License-Identifier: MPL-2.0 -name: CodeQL Security Analysis - -on: - push: - branches: [main, master] - pull_request: - branches: [main, master] - schedule: - - cron: '0 6 * * 1' - -concurrency: - group: ${{{ github.workflow }}}-${{{ github.ref }}} - cancel-in-progress: false - -permissions: - contents: read - -jobs: - analyze-js: - uses: {relpath}/.github/workflows/codeql-reusable.yml - with: - language: javascript-typescript -""", - }, - "governance.yml": { - "reusable": "governance-reusable.yml", - "triggers": ["push", "pull_request", "workflow_dispatch"], - "content": """# SPDX-License-Identifier: MPL-2.0 -# governance.yml — single wrapper calling the shared estate governance bundle -# in hyperpolymath/standards instead of carrying per-repo copies. -# -# Replaces the per-repo governance scaffolding removed in the same commit: -# quality.yml, guix-nix-policy.yml, npm-bun-blocker.yml, ts-blocker.yml, -# security-policy.yml, rsr-antipattern.yml, wellknown-enforcement.yml, -# workflow-linter.yml -# -# Load-bearing build/security workflows stay standalone in the repo -# (rust-ci, codeql, dependabot, release, scan/mirror/pages plumbing). - -name: Governance - -on: - push: - branches: [main, master] - pull_request: - workflow_dispatch: - -permissions: - contents: read - -jobs: - governance: - uses: {relpath}/.github/workflows/governance-reusable.yml -""", - }, - "hypatia-scan.yml": { - "reusable": "hypatia-scan-reusable.yml", - "triggers": ["push", "pull_request", "schedule", "workflow_dispatch"], - "content": """# SPDX-License-Identifier: MPL-2.0 -name: Hypatia Security Scan - -on: - push: - branches: [main, master, develop] - pull_request: - branches: [main, master] - schedule: - - cron: '0 0 * * 0' - workflow_dispatch: - -permissions: - actions: read - contents: read - security-events: write - -jobs: - scan: - uses: {relpath}/.github/workflows/hypatia-scan-reusable.yml - secrets: inherit -""", - }, - "instant-sync.yml": { - "reusable": None, # No reusable, but we have a standard version - "triggers": ["push", "release"], - "content": """# SPDX-License-Identifier: MPL-2.0 -# Instant Forge Sync - Triggers propagation to all forges on push/release -name: Instant Sync - -on: - push: - branches: [main, master] - release: - types: [published] - -concurrency: - group: ${{{ github.workflow }}}-${{{ github.ref }}} - cancel-in-progress: true - -permissions: - actions: read - contents: read - -jobs: - dispatch: - timeout-minutes: 10 - runs-on: ubuntu-latest - env: - FARM_DISPATCH_TOKEN: ${{{ secrets.FARM_DISPATCH_TOKEN }}} - steps: - - name: Trigger Propagation - if: ${{{ env.FARM_DISPATCH_TOKEN != '' }}} - uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v3 - with: - token: ${{{ secrets.FARM_DISPATCH_TOKEN }}} - repository: hyperpolymath/.git-private-farm - event-type: propagate - client-payload: |- - { - "repo": "${{{ github.event.repository.name }}}", - "ref": "${{{ github.ref }}}", - "sha": "${{{ github.sha }}}", - "forges": "" - } - - - name: Skipped (FARM_DISPATCH_TOKEN not configured) - if: ${{{ env.FARM_DISPATCH_TOKEN == '' }}} - env: - REPO_NAME: ${{{ github.event.repository.name }}} - run: | - echo "::notice::FARM_DISPATCH_TOKEN secret not configured on ${{{ REPO_NAME }}}; skipping cross-repo dispatch." - - - name: Confirm - env: - REPO_NAME: ${{{ github.event.repository.name }}} - run: echo "::notice::Propagation triggered for ${{{ REPO_NAME }}}" -""", - }, - "mirror.yml": { - "reusable": "mirror-reusable.yml", - "triggers": ["push", "workflow_dispatch"], - "content": """# SPDX-License-Identifier: MPL-2.0 -name: Mirror to Git Forges - -on: - push: - branches: [main] - workflow_dispatch: - -permissions: - contents: read - -jobs: - mirror: - uses: {relpath}/.github/workflows/mirror-reusable.yml - secrets: inherit -""", - }, - "scorecard.yml": { - "reusable": "scorecard-reusable.yml", - "triggers": ["push", "schedule", "workflow_dispatch"], - "content": """# SPDX-License-Identifier: MPL-2.0 -name: OSSF Scorecard - -on: - push: - branches: [main, master] - schedule: - - cron: '0 4 * * *' - workflow_dispatch: - -permissions: - contents: read - -jobs: - scorecard: - uses: {relpath}/.github/workflows/scorecard-reusable.yml -""", - }, - "scorecard-enforcer.yml": { - "reusable": "scorecard-reusable.yml", - "triggers": ["push", "schedule", "workflow_dispatch"], - "content": """# SPDX-License-Identifier: MPL-2.0 -# Prevention workflow - runs OpenSSF Scorecard and fails on low scores -name: OpenSSF Scorecard Enforcer - -on: - push: - branches: [main] - schedule: - - cron: '0 6 * * 1' - workflow_dispatch: - -permissions: - contents: read - -jobs: - scorecard: - uses: {relpath}/.github/workflows/scorecard-reusable.yml -""", - }, - "secret-scanner.yml": { - "reusable": "secret-scanner-reusable.yml", - "triggers": ["push", "pull_request"], - "content": """# SPDX-License-Identifier: MPL-2.0 -name: Secret Scanner - -on: - pull_request: - push: - branches: [main] - -concurrency: - group: ${{{ github.workflow }}}-${{{ github.ref }}} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - scan: - permissions: - contents: read - uses: {relpath}/.github/workflows/secret-scanner-reusable.yml - secrets: inherit -""", - }, -} - - -def get_relative_path(target_dir): - """Calculate the relative path from target_dir to repo root .github/workflows/""" - target_path = Path(target_dir).resolve() - root_path = REPO_ROOT.resolve() - - # Count the number of levels between target_dir and repo root - try: - rel = target_path.relative_to(root_path) - depth = len(rel.parts) - except ValueError: - depth = 0 - - # Go up depth levels, then down to .github/workflows/ - return "../" * depth + ".github/workflows/" - - -def find_workflow_dirs(): - """Find all directories containing .github/workflows/""" - workflow_dirs = [] - for root, dirs, files in os.walk(REPO_ROOT): - if ".github/workflows" in dirs: - workflow_dir = Path(root) / ".github/workflows" - # Skip the root .github/workflows - if workflow_dir != REPO_ROOT / ".github/workflows": - workflow_dirs.append(workflow_dir) - return workflow_dirs - - -def needs_fix(workflow_dir, workflow_name): - """Check if a workflow file needs to be fixed (i.e., doesn't already use reusable)""" - workflow_file = workflow_dir / workflow_name - if not workflow_file.exists(): - return False - - content = workflow_file.read_text() - # Check if it already uses a reusable workflow - if "uses:" in content and "reusable" in content: - return False - - return True - - -def fix_workflow(workflow_dir, workflow_name, dry_run=True): - """Replace a workflow file with a call to the reusable version""" - workflow_file = workflow_dir / workflow_name - - if not workflow_file.exists(): - return None - - # Calculate relative path - rel_path = get_relative_path(workflow_dir.parent) - - # Get the template - if workflow_name not in WORKFLOWS: - return None - - template = WORKFLOWS[workflow_name]["content"] - content = template.format(relpath=rel_path) - - if dry_run: - print(f" WOULD FIX: {workflow_file}") - print(f" Using: {rel_path}.github/workflows/{WORKFLOWS[workflow_name]['reusable']}") - return (workflow_file, content) - else: - workflow_file.write_text(content) - print(f" FIXED: {workflow_file}") - return (workflow_file, content) - - -def main(): - dry_run = "--dry-run" in sys.argv - execute = "--execute" in sys.argv - - if not execute and not dry_run: - print("Usage: python3 scripts/bulk-fix-workflows.py --dry-run") - print(" python3 scripts/bulk-fix-workflows.py --execute") - return - - workflow_dirs = find_workflow_dirs() - print(f"Found {len(workflow_dirs)} workflow directories") - print() - - total_found = 0 - total_fixed = 0 - - for workflow_dir in sorted(workflow_dirs): - # Skip if already processed - rel_dir = workflow_dir.relative_to(REPO_ROOT) - print(f"Processing: {rel_dir}") - - for workflow_name in WORKFLOWS: - if needs_fix(workflow_dir, workflow_name): - total_found += 1 - if dry_run: - fix_workflow(workflow_dir, workflow_name, dry_run=True) - else: - fix_workflow(workflow_dir, workflow_name, dry_run=False) - total_fixed += 1 - - print() - print(f"Summary: {total_found} workflow files need fixing, {total_fixed} fixed") - - -if __name__ == "__main__": - main() diff --git a/scripts/check-actions-policy.sh b/scripts/check-actions-policy.sh index b3083f7c..83f327bb 100755 --- a/scripts/check-actions-policy.sh +++ b/scripts/check-actions-policy.sh @@ -23,7 +23,10 @@ WF_DIR="${1:-.github/workflows}" # The canonical allowlist lives in this repo at # rhodium-standard-repositories/actions-allowlist/allowed-actions.json -CANON="rhodium-standard-repositories/actions-allowlist/allowed-actions.json" +# Resolve the canonical allowlist. $ALLOWLIST_JSON lets the caller point at a +# copy staged outside the scanned tree — required for consumer repos, which do +# not have rhodium-standard-repositories/ in their own checkout. +CANON="${ALLOWLIST_JSON:-rhodium-standard-repositories/actions-allowlist/allowed-actions.json}" if [ ! -f "$CANON" ]; then echo "::error::Canonical allowlist not found: $CANON"