Skip to content
Merged
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
12 changes: 12 additions & 0 deletions .github/workflows/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ either loses real function — keep both.
Rust-secrets check and a shell-secrets check). An inline TruffleHog job was
removed earlier as redundant with gitleaks; do not add it again.

== Three workflows are self-contained, not reusable callers

`hypatia-scan.yml`, `rust-ci.yml` and `scorecard.yml` follow the workflows of
the estate template (`hyperpolymath/rsr-template-repo`). Earlier each one
called a reusable workflow of the standards repository. Those calls did not
start: the Actions rule of this repository refuses a reusable workflow of a
different owner, and each run failed with no job. Each `uses:` in these three
files is now a step-level action, pinned to a full commit SHA.

`governance.yml` and `mirror.yml` are still callers of the standards
repository. They start only when the Actions rule permits that repository.

== SAST tools are complementary, not redundant

* `codeql.yml` — CodeQL (matrix defaults to `actions`; every repo has workflows).
Expand Down
146 changes: 133 additions & 13 deletions .github/workflows/hypatia-scan.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,153 @@
# SPDX-License-Identifier: MPL-2.0
#
# Standalone Hypatia security scan (push / PR / weekly). This is NOT a duplicate
# of the `hypatia-scan` job in `static-analysis-gate.yml`: that job exists to
# feed the gitbot-fleet LEARNING pipeline (via `deposit-findings`), whereas this
# workflow is the repo's independent security scan. Same tool, two consumers.
# Self-contained Hypatia scan with SARIF upload (category `hypatia`), after
# `hypatia-scan.yml` of the estate template (hyperpolymath/rsr-template-repo).
# Hypatia is built from hyperpolymath/hypatia at the commit in HYPATIA_SHA.
#
# The earlier form of this file called the reusable workflow `hypatia-scan-reusable.yml` of the
# standards repository. That call did not start: each run failed with no job.
#
# `static-analysis-gate.yml` also runs Hypatia, for the gitbot-fleet deposit.
# See .github/workflows/README.adoc.
name: Hypatia Security Scan

on:
pull_request:
push:
branches: [main, master, develop]
pull_request:
branches: [main, master]
schedule:
- cron: '0 0 * * 0'
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
actions: read
contents: read
security-events: write

jobs:
hypatia:
uses: metadatastician/standards/.github/workflows/hypatia-scan-reusable.yml@740c9d7d4ed4d1f319a04c92b28218beb75668d7
# Only the secret that the called workflow declares. `secrets: inherit`
# gives every secret of this repository to the called workflow.
secrets:
HYPATIA_SCAN_PAT: ${{ secrets.HYPATIA_SCAN_PAT }}
name: Hypatia Neurosymbolic Analysis
runs-on: ubuntu-24.04
timeout-minutes: 20
permissions:
contents: read
security-events: write
vulnerability-alerts: read # Dependabot alerts, for Hypatia's DA rules
env:
HYPATIA_SHA: 'be98f74ce93df200752f3798efa1305bfa3e0e10'
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false

- name: Setup Elixir 1.19.4 / OTP 28.5.0.8 for Hypatia scanner
uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1
with:
elixir-version: '1.19.4'
otp-version: '28.5.0.8'

- name: Cache pinned Hypatia build
id: cache-hypatia
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/hypatia
~/.hex
~/.mix
key: hypatia-sarif-${{ runner.os }}-otp28.5.0.8-elx1194-${{ env.HYPATIA_SHA }}

- name: Build pinned Hypatia scanner
run: |
set -euo pipefail
if [ ! -x "$HOME/hypatia/hypatia" ] || [ ! -x "$HOME/hypatia/hypatia-cli.sh" ]; then
rm -rf "$HOME/hypatia"
git init "$HOME/hypatia"
git -C "$HOME/hypatia" remote add origin https://github.com/hyperpolymath/hypatia.git
git -C "$HOME/hypatia" fetch --depth=1 origin "$HYPATIA_SHA"
git -C "$HOME/hypatia" checkout --detach FETCH_HEAD
(
cd "$HOME/hypatia"
mix local.hex --force
mix local.rebar --force
mix deps.get
MIX_NO_PUBSUB=1 mix escript.build
)
chmod +x "$HOME/hypatia/hypatia" "$HOME/hypatia/hypatia-cli.sh"
fi

- name: Run Hypatia JSON and SARIF analysis
id: scan
env:
# The API-backed rules (code-scanning, Dependabot, secret-scanning,
# branch-protection, baseline-health) return no findings without a
# token. Scoped to this step. The job token reaches code-scanning
# (security-events) and Dependabot (vulnerability-alerts) alerts;
# it cannot read secret-scanning alerts, which need an App or PAT.
GITHUB_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . --exit-zero > hypatia-findings.json
jq -e 'type == "array"' hypatia-findings.json >/dev/null

HYPATIA_FORMAT=sarif "$HOME/hypatia/hypatia-cli.sh" scan . --exit-zero > hypatia.sarif
jq -e '.runs | type == "array" and length > 0' hypatia.sarif >/dev/null

# Repo-relative paths, so that a reader can compare a finding with
# the output of `git diff --name-only`.
jq --arg ws "$GITHUB_WORKSPACE/" '
map(if (.file | type) == "string" then
.file |= (if startswith($ws) then .[($ws | length):] else . end)
else . end)' hypatia-findings.json > hypatia-findings.relativized.json

CRITICAL="$(jq '[.[] | select(.severity == "critical")] | length' hypatia-findings.json)"
HIGH="$(jq '[.[] | select(.severity == "high")] | length' hypatia-findings.json)"
TOTAL="$(jq 'length' hypatia-findings.json)"
{
echo "critical=$CRITICAL"
echo "high=$HIGH"
echo "total=$TOTAL"
} >> "$GITHUB_OUTPUT"

# Uploaded even when the scan step failed: a missing artifact and a failed
# scan are different facts. `if-no-files-found: warn` because a scan that
# never produced a file is already visible as a red step here.
- name: Upload Hypatia findings
if: ${{ always() }}
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: hypatia-scan-findings
path: |
hypatia-findings.json
hypatia-findings.relativized.json
if-no-files-found: warn
retention-days: 90

- name: Upload Hypatia SARIF to GitHub Code Scanning
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with:
sarif_file: hypatia.sarif
category: hypatia

- name: Enforce zero critical Hypatia findings
env:
CRITICAL_COUNT: ${{ steps.scan.outputs.critical }}
HIGH_COUNT: ${{ steps.scan.outputs.high }}
TOTAL_COUNT: ${{ steps.scan.outputs.total }}
run: |
set -euo pipefail
{
echo "## Hypatia Neurosymbolic Scan"
echo ""
echo "- Critical: ${CRITICAL_COUNT}"
echo "- High: ${HIGH_COUNT}"
echo "- Total findings: ${TOTAL_COUNT}"
} >> "$GITHUB_STEP_SUMMARY"
if [ "$CRITICAL_COUNT" != "0" ]; then
echo "::error::Hypatia reported ${CRITICAL_COUNT} critical finding(s)"
exit 1
fi
46 changes: 40 additions & 6 deletions .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,55 @@
# SPDX-License-Identifier: MPL-2.0
# Rust CI — thin wrapper calling the shared estate reusable in
# hyperpolymath/standards. Configure once, propagate everywhere.
# See: docs/CI-REUSABLE-WORKFLOWS.adoc in standards.
#
# Rust CI, after `rust-ci.yml` of the estate template
# (hyperpolymath/rsr-template-repo): compiles standalone scripts/*.rs utilities
# with -D warnings and runs cargo check and cargo test when a Cargo.toml is
# present. This repository has no Rust file today.
#
# The earlier form of this file called the reusable workflow `rust-ci-reusable.yml` of the
# standards repository. That call did not start: each run failed with no job.
name: Rust CI

on:
push:
branches: [main, master]
pull_request:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
rust-ci:
uses: metadatastician/standards/.github/workflows/rust-ci-reusable.yml@571cc734cd69fb846032ec77a662aa8ee4fc32cd
name: Rust standalone tools and workspace gate
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Compile standalone Rust helpers and run tests
run: |
set -euo pipefail
rustc --version
mkdir -p "$RUNNER_TEMP/rust-bins"
for src in scripts/*.rs; do
[ -f "$src" ] || continue
bin_name="$(basename "$src" .rs)"
echo "Compiling $src (-D warnings)..."
rustc --edition 2021 -D warnings "$src" -o "$RUNNER_TEMP/rust-bins/$bin_name"
done
if [ -f tests/workflows/archetype_lock_merge_test.sh ]; then
bash tests/workflows/archetype_lock_merge_test.sh
fi
if [ -f Cargo.toml ]; then
cargo check --all-targets --locked
cargo test --locked
fi
30 changes: 29 additions & 1 deletion .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
# SPDX-License-Identifier: MPL-2.0
#
# OSSF Scorecard, after `scorecard.yml` of the estate template
# (hyperpolymath/rsr-template-repo). It runs on a schedule and on manual dispatch.
#
# The earlier form of this file called the reusable workflow `scorecard-reusable.yml` of the
# standards repository. That call did not start: each run failed with no job.
name: OSSF Scorecard

on:
Expand All @@ -9,14 +15,36 @@ on:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
scorecard:
uses: metadatastician/standards/.github/workflows/scorecard-reusable.yml@d7c22711e830e1f383846472f6e9b99debdb201e
if: github.event_name != 'pull_request'
name: OSSF Scorecard analysis
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
actions: read
contents: read
security-events: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Run OSSF Scorecard
uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.0
with:
results_file: results.sarif
results_format: sarif
publish_results: ${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}

- name: Upload Scorecard SARIF to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with:
sarif_file: results.sarif
category: scorecard
Loading