-
Notifications
You must be signed in to change notification settings - Fork 0
141 lines (134 loc) · 5.76 KB
/
Copy pathsecurity.yml
File metadata and controls
141 lines (134 loc) · 5.76 KB
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
# Security scanning (2026-07-16 standards sweep): SAST (Semgrep), secret scanning
# (gitleaks), and dependency audit (pip-audit against the locked uv environment —
# the same `make audit` a contributor runs locally). All jobs run automatically on
# push/PR plus a weekly sweep; none is dispatch-only.
#
# The secret-scan job is full-history, not diff-scoped: it runs the repository's
# own `make secret-scan` over every ref, every object in the object database
# (including unreachable ones), and the working tree. See
# tools/secret-scan-full-history.sh for what each phase covers and why the
# scanner is pinned.
name: security
on:
push:
branches: [main]
pull_request:
schedule:
- cron: "0 7 * * 0" # weekly, Sunday, staggered off ci.yml's push/PR runs
permissions:
contents: read
concurrency:
group: security-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
semgrep:
name: SAST (Semgrep, registry auto config)
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
container:
image: semgrep/semgrep@sha256:59fbed6127ea7c5dde3ba6a85142733bb20ea9aaa36120c953904f1539aaf66e # 1.168.0
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
# `semgrep scan`, not `semgrep ci`. On a pull_request event `semgrep ci`
# resolves a diff baseline and shells out to
# `git fetch origin --force --depth=1 <head-sha>`. This repository is
# private and the checkout above sets `persist-credentials: false`, so that
# fetch died with "could not read Username for 'https://github.com'",
# semgrep aborted before scanning anything, and its default
# `--suppress-errors` turned the aborted run into exit 0. Every pull request
# therefore got a green SAST check over zero scanned files, while the same
# config on a push to main ran the full scan and went red. `semgrep scan`
# needs no baseline and no credential, so both events now run the identical
# full scan. `--error` fails on findings; `--strict` fails on an analysis
# error, so a scan that cannot run can no longer report success.
- run: semgrep scan --config auto --error --strict
secret-scan:
name: secret scanning (gitleaks, full history + object database)
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
steps:
# fetch-depth: 0 brings down every branch and tag as a remote-tracking
# ref, which is what phase 1's `--log-opts="--all --full-history"` needs.
# It also brings down the packfile phase 2 enumerates. A shallow checkout
# would let this job report success over a few commits.
- name: Checkout (full history — this gate scans every ref and every object)
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false
# Not gitleaks-action: that wrapper resolves the scanning binary at run
# time, so pinning the action does not pin the ruleset. This installs one
# named release and checks it against a recorded SHA-256.
- name: Install pinned gitleaks
id: gitleaks
uses: ./.github/actions/setup-gitleaks
# The same command a maintainer runs locally. Findings are redacted, and
# any finding, any phase, fails the job — no mute, no .gitleaksignore.
- name: Full-history secret scan (make secret-scan)
env:
GITLEAKS_BIN: ${{ steps.gitleaks.outputs.gitleaks-bin }}
run: make secret-scan
pip-audit:
name: dependency audit (pip-audit, locked env)
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
env:
UV_PYTHON: "3.12"
UV_PYTHON_DOWNLOADS: never
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.12"
check-latest: false
- name: Set up uv
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
version: "0.11.28"
enable-cache: true
- name: pip-audit (same target as `make audit`)
run: |
uv sync --locked
uv run pip-audit --skip-editable --cache-dir .cache/pip-audit
publication-sweep:
# `make verify` already runs this sweep, but ci.yml carries a `paths-ignore`
# for `**.md` and `docs/**` so that a docs-only change does not spend a full
# CI run. That skip would leave the one gate whose whole job is documentation
# hygiene unable to see documentation changes: a reintroduced personal path
# or internal hostname in a Markdown file would land unswept. This workflow
# has no paths-ignore, so the sweep runs on every change either way.
name: publication sweep (no personal paths, internal hosts, dead pointers)
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
env:
UV_PYTHON: "3.12"
UV_PYTHON_DOWNLOADS: never
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.12"
check-latest: false
- name: Set up uv
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
version: "0.11.28"
enable-cache: true
- name: make publication-sweep
run: make publication-sweep