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
456 changes: 456 additions & 0 deletions .github/scripts/assert_hosted_runners.py

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions .github/workflows/attestation-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ concurrency:
jobs:
verify:
name: Verify Latest Release Attestations
# ubuntu-latest, NOT the self-hosted `cachekit` runner: the ARC pods have no `gh`
# binary (LAB-899) and unreliable Sigstore (Fulcio/Rekor) egress — the same reason
# release.yml's publish job is hosted. `gh attestation verify` is the only tool
# that does Sigstore bundle verification, so moving this job back to ARC silently
# re-breaks it (LAB-984: every `gh` call exited 127 into `|| echo ""`, and the job
# reported green while verifying nothing for weeks).
# Must stay GitHub-hosted: this job needs `gh` (preinstalled on ubuntu-latest)
# and reliable Sigstore (Fulcio/Rekor) egress. `gh attestation verify` is the
# only tool that does Sigstore bundle verification. The whole repo is on
# ubuntu-latest (LAB-3501) and runner-guard.yml keeps it there, but the point
# is load-bearing here specifically: when this once ran on a self-hosted runner
# with no `gh` binary, every `gh` call exited 127 into `|| echo ""` and
# the job reported green while verifying nothing for weeks.
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down
18 changes: 3 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,31 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
# cargo's available_parallelism() reads the node's 32 threads, not the pod's
# cgroup CPU quota, so a cold (cache-miss) build fans out ~22-way and can
# OOM-kill the linker in the 5Gi cachekit runner — the same failure that hit
# cachekit-rs (#25/#26). Cap parallel jobs so peak RSS stays under the cap.
CARGO_BUILD_JOBS: "4"

jobs:
test:
name: ${{ matrix.rust }} / ${{ matrix.os }}
runs-on: ${{ matrix.runner }}
runs-on: ${{ matrix.os }}
# Fail fast instead of hanging ~10min to GitHub's heartbeat if a runner wedges.
timeout-minutes: 20
continue-on-error: ${{ matrix.rust == 'beta' }}
strategy:
fail-fast: false
matrix:
# Full OS matrix for stable only; MSRV and beta on self-hosted only
# Full OS matrix for stable only; MSRV and beta on Linux only.
include:
# MSRV - ensures we don't use newer Rust features
- rust: "1.85"
os: ubuntu-latest
runner: cachekit
# Stable - primary target, all platforms
- rust: stable
os: ubuntu-latest
runner: cachekit
- rust: stable
os: macos-latest
runner: macos-latest
- rust: stable
os: windows-latest
runner: windows-latest
# Beta - early warning (allowed to fail)
- rust: beta
os: ubuntu-latest
runner: cachekit
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

Expand Down Expand Up @@ -76,7 +64,7 @@ jobs:
run: cargo test --features ffi

security:
runs-on: cachekit
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
Expand Down
8 changes: 1 addition & 7 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,10 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
# cargo's available_parallelism() reads the node's 32 threads, not the pod's
# cgroup CPU quota, so the cold release build below can OOM-kill the linker in
# the 5Gi cachekit runner (cachekit-rs #25/#26). Cap parallel jobs to fit.
CARGO_BUILD_JOBS: "4"

jobs:
analyze:
name: Analyze
runs-on: cachekit
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
actions: read
Expand Down
20 changes: 13 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,14 @@ concurrency:
cancel-in-progress: false

jobs:
# GitHub-hosted: this job inherits contents+PR write and mints an App
# installation token, so it must not share a writable build cache with other
# jobs — job-level `permissions:` does not isolate a filesystem, and untrusted
# build-script/proc-macro code could poison a cache a later credentialed job
# runs against. A fresh hosted VM has no shared cache to poison, and
# release-please needs no warm cache anyway.
release-please:
runs-on: cachekit
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
Expand All @@ -41,8 +47,9 @@ jobs:
# release PR here. outputs.pr is set whenever the release PR was created
# OR updated, so this re-runs on every push to main while a release PR is
# open — adding an already-present assignee is a no-op, so that's safe.
# github-script, NOT `gh`: the self-hosted cachekit runner has no gh CLI
# (LAB-899). outputs.pr is passed raw via env and parsed in JS — never
# github-script, NOT `gh`: chosen when this job ran on a self-hosted
# runner without the gh CLI; it works identically on
# a hosted runner. outputs.pr is passed raw via env and parsed in JS — never
# through template-position fromJson, which is evaluated even when if: is
# false and crashes on '' for no-release pushes (LAB-865).
- name: Assign release PR to 27Bslash6
Expand Down Expand Up @@ -103,10 +110,9 @@ jobs:
subject-path: target/package/*.crate

- name: Install cargo-sbom
# --force is required: the self-hosted runner's CARGO_HOME (/cache/cargo) is a
# persistent volume, so the binary survives between runs and a plain install
# exits 101 ("binary `cargo-sbom` already exists"). --force reinstalls the
# --locked pinned version idempotently.
# --force kept from when this job ran self-hosted with a persistent
# CARGO_HOME (/cache/cargo), where a plain install exits 101 ("binary
# `cargo-sbom` already exists"). Harmless no-op cost on a hosted runner.
run: cargo install cargo-sbom --locked --force

- name: Generate SBOM
Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/runner-guard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Runner Guard

# Drift protection (LAB-3501): every job in this repo must run on a GitHub-hosted
# runner. This job fails the workflow if any `runs-on` or matrix `os:`/`runner:`
# value in .github/workflows/ is not a hosted label — an allow-list, so a NEW
# self-hosted label nobody has named yet still fails.
#
# This is protection against MAINTAINER drift on trusted branches only. It is NOT a
# fork-PR control: a fork runs its own copy of this file and can delete the guard;
# the control for that lives in repository and org runner settings, outside this file.

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

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

jobs:
assert-hosted-runners:
name: Assert all runners are GitHub-hosted
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Self-test the guard, then scan every workflow
run: |
python3 .github/scripts/assert_hosted_runners.py --selftest
python3 .github/scripts/assert_hosted_runners.py
56 changes: 27 additions & 29 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ on:
branches: [main]
schedule:
# Saturday 11:07 UTC = Sat 21:07 AEST / 22:07 AEDT (Sydney night, year-round).
# Weekly cadence: deep fuzz at 1h/target × 16 targets is ~16 runner-hours, which
# drains in a few hours overnight instead of monopolising the shared ARC pool for
# ~32h. The full 8h/target run is opt-in via workflow_dispatch (run_deep_fuzz).
# PR-time coverage (cargo audit/deny, Cargo Vet, Quick Fuzz, CodeQL) catches
# regressions promptly; deep fuzz is for finding bugs, not gating merges.
# Off-minute (:07) avoids the cron pile-up that GitHub schedules at :00.
# Weekly cadence: deep fuzz at 1h/target × 16 targets. Each matrix target is a
# separate GitHub-hosted job, so they run in parallel (bounded by GitHub's
# concurrency limit) rather than serialising on shared runners — and hosted minutes
# are free for public repos. The longer opt-in run is via workflow_dispatch
# (run_deep_fuzz). PR-time coverage (cargo audit/deny, Cargo Vet, Quick Fuzz,
# CodeQL) catches regressions promptly; deep fuzz is for finding bugs, not
# gating merges. Off-minute (:07) avoids the cron pile-up GitHub schedules at :00.
- cron: '7 11 * * 6'
# Deliberately no `release:` trigger. It existed only for an SBOM job that
# attached a release asset, and that can never work here: immutable releases
Expand All @@ -29,13 +30,13 @@ on:
workflow_dispatch:
inputs:
run_deep_fuzz:
description: "Run the full deep-fuzz matrix (heavy — occupies the ARC pool)"
description: "Run the full deep-fuzz matrix (heavy — one hosted job per target)"
type: boolean
default: false
fuzz_seconds:
description: "Seconds per target for an on-demand deep fuzz (default 8h)"
description: "Seconds per target for an on-demand deep fuzz (default 5h; hard cap 18000s to fit GitHub's 6h job limit)"
type: string
default: "28800"
default: "18000"

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand All @@ -44,16 +45,11 @@ concurrency:
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
# Cap cargo parallelism so cold builds (incl. ASAN fuzz-target builds) don't
# OOM-kill the linker in the 5Gi cachekit runner — see cachekit-rs #25/#26.
# NOTE: -j bounds parallel codegen, not ASAN's absolute footprint; it's a
# strong mitigation for the fuzz builds, a full fix for normal cargo builds.
CARGO_BUILD_JOBS: "4"

jobs:
fast-security:
name: Fast Security Checks
runs-on: cachekit
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event_name == 'pull_request'
steps:
- name: Checkout code
Expand Down Expand Up @@ -98,7 +94,7 @@ jobs:

quick-fuzz:
name: Quick Fuzz (Corpus Only)
runs-on: cachekit
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event_name == 'pull_request'
strategy:
fail-fast: false
Expand Down Expand Up @@ -160,13 +156,13 @@ jobs:

deep-fuzz:
name: Deep Fuzzing
runs-on: cachekit
# Scheduled weekly run is light (1h/target) so it can't monopolise the shared
# ARC pool. The full 8h/target run is opt-in via workflow_dispatch (run_deep_fuzz).
runs-on: ubuntu-latest
# Scheduled weekly run is light (1h/target). The longer on-demand run is opt-in
# via workflow_dispatch (run_deep_fuzz) and clamped to fit the hosted 6h cap.
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.run_deep_fuzz)
# 540min cap accommodates the on-demand 8h path; the inner timeout governs the
# actual scheduled (1h) vs dispatched (configurable) duration.
timeout-minutes: 540
# 360min = GitHub's hosted job cap. The inner timeout governs the actual
# scheduled (1h) vs dispatched (<=5h, clamped below) duration.
timeout-minutes: 360
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -214,18 +210,20 @@ jobs:
run: cargo install cargo-fuzz

- name: Run deep fuzz
# Scheduled runs use 1h/target (keeps the ARC pool free for PR CI); a manual
# workflow_dispatch can request the full 8h (or any duration) via fuzz_seconds.
# Scheduled runs use 1h/target; a manual workflow_dispatch can request a
# longer duration (up to the 5h clamp below) via fuzz_seconds.
env:
FUZZ_SECONDS: ${{ (github.event_name == 'workflow_dispatch' && inputs.fuzz_seconds) || '3600' }}
run: |
# Validate the dispatch-supplied duration before it reaches shell arithmetic
# and the fuzzer. Must be a positive integer and within the 540min job cap.
# and the fuzzer. Must be a positive integer within the hosted 6h job cap.
# Ceiling is 18000s (5h): +180s hang slack (below) still leaves headroom
# under timeout-minutes 360 (21600s), so a dispatched job cannot exceed 6h.
case "$FUZZ_SECONDS" in
''|*[!0-9]*) echo "::error::fuzz_seconds must be a positive integer (got '$FUZZ_SECONDS')"; exit 1 ;;
esac
if [ "$FUZZ_SECONDS" -lt 1 ] || [ "$FUZZ_SECONDS" -gt 32400 ]; then
echo "::error::fuzz_seconds must be 1..32400 (<= 540min job cap), got $FUZZ_SECONDS"; exit 1
if [ "$FUZZ_SECONDS" -lt 1 ] || [ "$FUZZ_SECONDS" -gt 18000 ]; then
echo "::error::fuzz_seconds must be 1..18000 (<= 6h hosted job cap), got $FUZZ_SECONDS"; exit 1
fi
cd fuzz
# Build first - fail fast on compile errors
Expand All @@ -249,7 +247,7 @@ jobs:

kani:
name: Kani Formal Verification
runs-on: cachekit
runs-on: ubuntu-latest
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
permissions:
contents: read # least-privilege: the job only checks out and verifies
Expand Down Expand Up @@ -293,7 +291,7 @@ jobs:

cargo-vet:
name: Cargo Vet (Supply Chain)
runs-on: cachekit
runs-on: ubuntu-latest
if: github.event_name == 'schedule' || github.event_name == 'pull_request'
steps:
- name: Checkout code
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Rust build artifacts
/target/

# Python bytecode from .github/scripts/ (the runner-guard drift check)
__pycache__/
*.py[cod]

# Generated files (keep .gitkeep but ignore generated header)
include/cachekit.h

Expand Down
Loading