1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
name: CI
# Build / test / lint / audit gate. Runs on GitHub-hosted `ubuntu-latest` — free
# and unlimited for this public repo, with the 5 jobs running in parallel. (They
# used to be pinned to the single self-hosted ci-VM runner to save private-repo
# minutes; that rationale went away when the repo went public.)
# CodeQL, dependency-review, and OSSF Scorecard live in their own workflows
# (they are designed for GitHub-hosted runners — see those files + the README).
on:
push:
branches:
pull_request:
branches:
workflow_dispatch: # allow manual runs from the Actions tab / gh CLI
concurrency:
# One in-flight CI run per ref: a new push to a PR cancels that PR's own
# in-progress run rather than letting a superseded 5-job build finish. Keyed on
# the PR number (falling back to the ref for push/dispatch). On GitHub-hosted
# runners the jobs also run in parallel — so this is purely about not wasting a
# run on outdated commits.
group: ci-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
# Least privilege: every job only checks out the tree and hits package registries
# — none push, tag, comment, or upload — so trim the default GITHUB_TOKEN to read.
permissions:
contents: read
jobs:
rust:
name: Rust
# FORK-PR GUARD (applied to every job): same-repo PRs + push / dispatch only;
# fork PRs get NO CI. Originally to keep untrusted fork code (a slipped-in
# build.rs / proc-macro / npm lifecycle script) off the self-hosted homelab
# runner. Now that CI is GitHub-hosted (sandboxed; secrets aren't exposed to
# fork-PR workflows) that RCE risk is gone — so this is now a POLICY choice:
# drop this `if:` to give contributor PRs CI.
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
runs-on: ubuntu-latest # GitHub-hosted: free + parallel now the repo is public
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Install stable toolchain (clippy + rustfmt)
uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable
with:
toolchain: stable # required — SHA-pinned, so the @ref no longer selects the toolchain
components: clippy, rustfmt
- name: Cache cargo
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- name: Build
run: cargo build --all-targets --locked
- name: Test
run: cargo test --locked
- name: Clippy
run: cargo clippy --all-targets -- -D warnings
- name: Rustfmt
run: cargo fmt --all --check
cargo-deny:
name: cargo deny (licenses + bans + sources)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} # fork-PR guard (see rust job)
runs-on: ubuntu-latest # GitHub-hosted: free + parallel now the repo is public
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
# cargo-deny shells out to `cargo metadata` to build the crate graph, so
# this job needs a Rust toolchain (cargo on PATH) even though it only runs
# the license/ban/source gates.
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable
with:
toolchain: stable # required — SHA-pinned ref no longer selects the toolchain
# Install a pinned cargo-deny binary directly (rather than
# EmbarkStudios/cargo-deny-action@v2, whose bundled RustSec advisory-DB
# fetch broke the gate on the old self-hosted runner every run).
# Config is deny.toml at the repo root; feature evaluation is set there
# (`[graph] all-features = true`).
- name: Install cargo-deny
uses: taiki-e/install-action@43aecc8d72668fbcfe75c31400bc4f890f1c5853 # v2
with:
tool: cargo-deny@0.20.2
# We deliberately DO NOT run the `advisories` check here: it needs the
# RustSec advisory DB, which the dedicated `cargo audit` job below already
# covers (same DB, same fail-on-vuln semantics). This job enforces the
# AGPL-compatible license allowlist, duplicate/ban hygiene, and the
# crates.io-only source policy. (Not `--offline`: `cargo metadata` needs
# the registry index to resolve the dependency graph.)
- name: cargo-deny (licenses + bans + sources)
run: cargo-deny check bans licenses sources
cargo-audit:
name: cargo audit (RustSec)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} # fork-PR guard (see rust job)
runs-on: ubuntu-latest # GitHub-hosted: free + parallel now the repo is public
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable
with:
toolchain: stable # required — SHA-pinned ref no longer selects the toolchain
- name: Cache cargo
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
# Dedicated RustSec scan of Cargo.lock. Fails the build on any known
# vulnerability. Un-actionable transitive advisories (no fixed release, or
# a fix that needs a semver-major bump) are whitelisted in deny.toml's
# `[advisories] ignore = [...]`; mirror any such id here as an explicit
# `--ignore RUSTSEC-YYYY-NNNN` flag. Keep the list SHORT and documented.
- name: Install cargo-audit
run: cargo install cargo-audit --locked || true
- name: cargo audit
# `-D warnings` promotes unmaintained/unsound advisories to failures too.
run: cargo audit -D warnings
sidecar:
name: OAuth sidecar
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} # fork-PR guard (see rust job)
runs-on: ubuntu-latest # GitHub-hosted: free + parallel now the repo is public
defaults:
run:
working-directory: oauth-sidecar
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 24
cache: npm
cache-dependency-path: oauth-sidecar/package-lock.json
- name: Install
run: npm ci
- name: Build
run: npm run build
- name: Typecheck
run: npm run typecheck
- name: Lint (ESLint, --max-warnings 0)
run: npm run lint
- name: Format check (Prettier)
# Scoped to the tooling/config files the CI pipeline owns. The hand-
# authored src/ + test/ predate Prettier and are enforced for
# CORRECTNESS by ESLint above; a repo-wide `prettier --write` is left as
# a deliberate one-time follow-up (`npm run format:write`) so this gate
# never fights the code-fix workflows over whitespace. See
# .github/workflows/README.md.
run: npm run format:check
- name: npm audit (prod deps, high+)
# Sidecar runtime deps only (--omit=dev); dev tooling churn shouldn't
# break CI. Fails on high/critical advisories.
run: npm audit --omit=dev --audit-level=high
secrets:
name: Secret scan (gitleaks)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} # fork-PR guard (see rust job)
runs-on: ubuntu-latest # GitHub-hosted: free + parallel now the repo is public
env:
GITLEAKS_VERSION: 8.28.0
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
fetch-depth: 0 # full history so gitleaks can scan past commits too
# Run the gitleaks BINARY directly rather than gitleaks/gitleaks-action@v2.
# The action gates on a GITLEAKS_LICENSE (required for any repo owned by a
# GitHub *org*/account it deems non-free) and does GITHUB_TOKEN-scoped PR
# bookkeeping — both of which trip on a personal-account private repo and
# failed the job regardless of findings. The binary is licence-free for
# this use and has none of that PR/org coupling.
- name: Install gitleaks
run: |
set -euo pipefail
url="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
curl -sSfL "$url" -o /tmp/gitleaks.tar.gz
tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks
install -m 0755 /tmp/gitleaks "$RUNNER_TEMP/gitleaks"
"$RUNNER_TEMP/gitleaks" version
# Scan the full git history (checkout above sets fetch-depth: 0). Config is
# .gitleaks.toml at the repo root (extends the default ruleset + allowlists
# the documented test fixtures/placeholders). --redact keeps any match out
# of the logs; a non-zero exit (leak found) fails the job.
- name: gitleaks (history + tree)
run: |
"$RUNNER_TEMP/gitleaks" git --config .gitleaks.toml --redact --exit-code 1