Skip to content

Commit e83568a

Browse files
feat(ci): require fast security scans on every PR (#327) (#330)
Add a per-PR security gate so secrets, vulnerable dependencies, and workflow misconfigurations are caught at PR time instead of only in the weekly Security suite — the gap that let AWS account IDs reach history in #313. - New security-pr.yml: range-scoped gitleaks + osv-scanner + zizmor on pull_request + merge_group. Secrets are scanned over the PR's own commit range (<base>..<head>), never an unbounded all-refs sweep, so a PR only fails on secrets it introduces. - build.yml: add the merge_group trigger so build can become a required status check without deadlocking the merge queue (a check must report on merge_group to satisfy the queue). - mise.toml: migrate security:secrets / :staged off the deprecated gitleaks detect/protect (removed from --help in v8.19.0) to `gitleaks git`; add security:secrets:range (GITLEAKS_RANGE-parameterized, defaults to HEAD, never an empty all-refs scan). security:secrets remains the full-history sweep used by the weekly security.yml. - build.yml: correct the misleading MISE_DISABLE_TOOLS comment. The required_status_checks ruleset rule, GitHub secret-scanning push protection, and require_code_owner_review are repo settings (not code) and are documented in the PR for an admin to apply. Refs #327
1 parent 1faa5c3 commit e83568a

3 files changed

Lines changed: 103 additions & 5 deletions

File tree

.github/workflows/build.yml

Lines changed: 8 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/security-pr.yml

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
name: security-pr
2+
# Fast, required-on-PR security gate (issue #327). Catches the incident class
3+
# (#313: secrets reaching history because the scan only ran weekly) at PR time.
4+
#
5+
# Scope is deliberately the FAST scanners only — secrets (range-scoped),
6+
# dependencies, and workflow static-analysis — so this stays off the slow path
7+
# (<~3 min) and can be a required status check without throttling the inner loop.
8+
# The heavy image + SAST suite (trivy/grype/semgrep) runs in security.yml and is
9+
# tracked separately by #235; the full-history secret sweep also lives there.
10+
on:
11+
pull_request: {}
12+
# Must also run on merge_group so this check reports inside the merge queue;
13+
# otherwise marking it a required check deadlocks the queue. See #327.
14+
merge_group: {}
15+
workflow_dispatch: {}
16+
17+
# Least privilege: read the code, nothing else.
18+
permissions:
19+
contents: read
20+
21+
concurrency:
22+
group: security-pr-${{ github.event.pull_request.number || github.ref }}
23+
cancel-in-progress: true
24+
25+
jobs:
26+
secrets-deps-actions:
27+
name: Secrets, deps, and workflow scan
28+
runs-on: ubuntu-latest
29+
timeout-minutes: 20
30+
permissions:
31+
contents: read
32+
env:
33+
CI: "true"
34+
MISE_EXPERIMENTAL: "1"
35+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
36+
AQUA_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37+
steps:
38+
- name: Checkout
39+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
40+
with:
41+
# Full history so the range scan can resolve the base ref. A shallow
42+
# clone would leave origin/<base> unresolvable for the commit-range diff.
43+
fetch-depth: 0
44+
persist-credentials: false
45+
46+
- name: Install mise
47+
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
48+
with:
49+
cache: true
50+
51+
- name: Resolve PR commit range
52+
id: range
53+
env:
54+
EVENT_NAME: ${{ github.event_name }}
55+
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
56+
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
57+
run: |
58+
set -euo pipefail
59+
case "$EVENT_NAME" in
60+
pull_request)
61+
# Scan exactly the commits this PR introduces.
62+
echo "range=${PR_BASE_SHA}..${PR_HEAD_SHA}" >> "$GITHUB_OUTPUT"
63+
;;
64+
merge_group|workflow_dispatch|*)
65+
# In the merge queue (and on manual dispatch) there is no PR diff to
66+
# scope to; scan the full reachable history as a backstop.
67+
echo "range=" >> "$GITHUB_OUTPUT"
68+
;;
69+
esac
70+
echo "Resolved GITLEAKS_RANGE='$(tail -n1 "$GITHUB_OUTPUT" | cut -d= -f2-)'"
71+
72+
- name: Secret scan (gitleaks, PR range)
73+
env:
74+
GITLEAKS_RANGE: ${{ steps.range.outputs.range }}
75+
run: mise run security:secrets:range
76+
77+
- name: Dependency scan (osv-scanner)
78+
run: mise run security:deps
79+
80+
- name: Workflow static analysis (zizmor)
81+
run: mise run security:gh-actions

mise.toml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,23 @@ run = [
108108
##################
109109

110110
[tasks."security:secrets"]
111-
description = "Scan for secrets with gitleaks"
112-
run = "gitleaks detect --source . --no-banner"
111+
description = "Scan all git history for secrets with gitleaks (full-history sweep)"
112+
# `gitleaks git` (history-aware) replaces the deprecated `gitleaks detect`
113+
# (removed from --help in v8.19.0). With no --log-opts this scans the full
114+
# history reachable from the checkout, matching the previous behaviour.
115+
run = "gitleaks git . --no-banner --redact"
116+
117+
[tasks."security:secrets:range"]
118+
description = "gitleaks over a commit range only (per-PR gate). Set GITLEAKS_RANGE, e.g. origin/main..HEAD"
119+
# Range-scoped scan: only the commits a branch/PR introduces, not full history.
120+
# Defaults to the full history when GITLEAKS_RANGE is unset so the task is safe
121+
# to run anywhere. The per-PR CI job sets GITLEAKS_RANGE=origin/$BASE..HEAD.
122+
run = 'gitleaks git . --no-banner --redact --log-opts="${GITLEAKS_RANGE:-}"'
113123

114124
[tasks."security:secrets:staged"]
115125
description = "gitleaks on staged changes (pre-commit hook)"
116-
run = "gitleaks protect --staged --no-banner"
126+
# `gitleaks git --staged` replaces the deprecated `gitleaks protect --staged`.
127+
run = "gitleaks git . --staged --no-banner --redact"
117128

118129
[tasks."security:sast"]
119130
description = "SAST scan with semgrep (auto + OWASP top 10)"

0 commit comments

Comments
 (0)