forked from ChelseaKR/gtfs-scorecard
-
Notifications
You must be signed in to change notification settings - Fork 0
208 lines (194 loc) · 8.68 KB
/
Copy pathsecurity.yml
File metadata and controls
208 lines (194 loc) · 8.68 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
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
206
207
208
# Security scanning — SAST (Semgrep) + secret detection (gitleaks).
# Implements SECURITY-AND-SUPPLY-CHAIN-STANDARD backlog #4. Both jobs are
# BLOCKING (no `|| true`): a finding fails the check and blocks the PR.
name: security
on:
push:
branches: [main]
pull_request:
# Least privilege by default. Event payload commit SHAs provide the scan range;
# the secret scanner does not need API or pull-request permissions.
permissions:
contents: read
# Cancel superseded runs on the same ref (new push to a PR) to save minutes.
concurrency:
group: security-${{ github.ref }}
cancel-in-progress: true
jobs:
secret-scan:
name: Secret scan (gitleaks)
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout (full history so push scans cover all new commits)
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Install and run Gitleaks over the event commit range
env:
EVENT_NAME: ${{ github.event_name }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PUSH_BEFORE_SHA: ${{ github.event.before }}
PUSH_AFTER_SHA: ${{ github.event.after }}
GITLEAKS_VERSION: "8.30.1"
# Official gitleaks_8.30.1_checksums.txt, Linux x64 archive.
GITLEAKS_LINUX_X64_SHA256: 551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb
run: |
set -euo pipefail
case "$EVENT_NAME" in
pull_request)
before_sha="$PR_BASE_SHA"
after_sha="$PR_HEAD_SHA"
;;
push)
before_sha="$PUSH_BEFORE_SHA"
after_sha="$PUSH_AFTER_SHA"
;;
*)
echo "::error::unsupported event for the Gitleaks diff gate: $EVENT_NAME"
exit 2
;;
esac
sha_pattern='^[0-9a-f]{40}$'
if [[ ! "$before_sha" =~ $sha_pattern || ! "$after_sha" =~ $sha_pattern ]]; then
echo "::error::event did not provide a valid 40-character commit range"
exit 2
fi
if [[ "$before_sha" == "0000000000000000000000000000000000000000" ]]; then
echo "::error::ref-creation pushes have no finite before..after history range"
exit 2
fi
git cat-file -e "${before_sha}^{commit}"
git cat-file -e "${after_sha}^{commit}"
range="${before_sha}..${after_sha}"
archive="$RUNNER_TEMP/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
bin_dir="$RUNNER_TEMP/gitleaks-bin"
curl --fail --location --proto '=https' --tlsv1.2 --retry 3 \
--output "$archive" \
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
printf '%s %s\n' "$GITLEAKS_LINUX_X64_SHA256" "$archive" | sha256sum --check --strict -
install -d "$bin_dir"
tar -xzf "$archive" -C "$bin_dir" gitleaks
actual_version=$("$bin_dir/gitleaks" version)
if [[ "$actual_version" != "$GITLEAKS_VERSION" && "$actual_version" != "v${GITLEAKS_VERSION}" ]]; then
echo "::error::downloaded Gitleaks reported unexpected version: $actual_version"
exit 2
fi
if "$bin_dir/gitleaks" git --redact --exit-code 1 --log-opts="$range" .; then
echo "Gitleaks v${GITLEAKS_VERSION} scanned \`$range\` with redaction enabled; no leaks found." >> "$GITHUB_STEP_SUMMARY"
else
status=$?
echo "Gitleaks v${GITLEAKS_VERSION} failed while scanning \`$range\`; review the redacted log." >> "$GITHUB_STEP_SUMMARY"
exit "$status"
fi
sast:
name: SAST (Semgrep)
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false
- name: Set up uv
uses: astral-sh/setup-uv@eb1897b8dc4b5d5bfe39a428a8f2304605e0983c # v7.0.0
with:
python-version: "3.12"
- name: Semgrep scan (blocking on ERROR severity)
# Pinned tool version; default + python + javascript registry rulesets.
# --severity ERROR limits findings to high-severity rules
# (HIGH/CRITICAL) and --error makes any such finding fail the build (no
# `|| true`).
#
# p/javascript is here because web/src/ is now in scope (.semgrepignore
# excluded all of web/ until 2026-08-16). p/python over a tree with
# ~6,600 lines of browser JavaScript was not covering it. Measured
# locally over the whole of web/ with the narrowed ignore: 183 files,
# 77 rules, 0 findings, 6 seconds.
run: >
uvx --from semgrep==1.168.0 semgrep scan
--config p/default
--config p/python
--config p/javascript
--severity ERROR
--error
--metrics off
# SEC-11/SEC-13/CQ-11: pip-audit + osv-scanner over the committed, hashed
# lockfile. No `|| true`, no continue-on-error — a real finding blocks.
dependency-audit:
name: Dependency audit (pip-audit + osv-scanner)
runs-on: ubuntu-latest
defaults:
run:
working-directory: pipeline
permissions:
contents: read
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@eb1897b8dc4b5d5bfe39a428a8f2304605e0983c # v7.0.0
with:
python-version: "3.12"
# --no-emit-project: pip-audit's requirement-file collector installs
# every line into a scratch venv to inspect it; the local editable
# package itself isn't a third-party dependency, so leave it out.
- name: Export a plain requirements list from the locked, hashed uv.lock
run: uv export --frozen --format requirements.txt --no-emit-project -o /tmp/reqs.txt
- name: pip-audit (blocking)
run: uvx --from pip-audit==2.10.1 pip-audit --strict -r /tmp/reqs.txt
- name: osv-scanner over uv.lock (blocking)
uses: google/osv-scanner-action/osv-scanner-action@9a498708959aeaef5ef730655706c5a1df1edbc2 # v2.3.8
with:
scan-args: |-
--lockfile=pipeline/uv.lock
# SEC-15-approx: the closest committed approximation to "block on a
# Dependabot alert >= 7.0" available to a solo repo without an Enterprise
# security posture. Needs the repo's dependency graph enabled (on by
# default for public repos) — no repo-settings change made by this job.
dependency-review:
name: Dependency review (PRs only)
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false
- uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
fail-on-severity: high
# CICD-19 / RTF-06 half: zizmor on any PR touching workflow definitions.
# `onboard.yml` parses a feed URL out of an untrusted issue body, which is
# exactly the class of finding zizmor exists to catch.
#
# issue #287: at --min-severity high this gate had zero findings ever, on
# any of the three AUTO-GATE audits the portfolio standards name
# (persist-credentials, excessive-permissions, unpinned-uses) — it could
# not structurally fail on the thing it exists to catch. Lowered to medium.
# .github/zizmor.yml records the small number of remaining medium findings
# that are real and cannot be fixed (a handful of checkouts whose persisted
# credential a later step in the same job genuinely needs to push with),
# each with a reason at its own site; every other finding was fixed
# directly. Verified this gate now actually fails on a seeded violation
# (temporarily removing one of those genuine persist-credentials: false
# lines reproduces exit code 13), not just that it passes today.
zizmor:
name: zizmor (workflow security lint)
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@eb1897b8dc4b5d5bfe39a428a8f2304605e0983c # v7.0.0
with:
python-version: "3.12"
- run: uvx zizmor==1.26.1 -c .github/zizmor.yml .github/workflows --min-severity medium