Repository navigation
313 lines (276 loc) · 12.2 KB
/
Copy pathsecurity.yml
File metadata and controls
313 lines (276 loc) · 12.2 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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
name: Security Scanning
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * 1'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
PNPM_VERSION: '10.34.5'
GO_VERSION: '1.26.10'
# Least-privilege default; the SARIF-uploading job opts into
# security-events: write per-job below.
permissions:
contents: read
jobs:
npm-audit:
name: NPM Audit
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- name: Setup pnpm
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: ${{ env.PNPM_VERSION }}
- name: Setup Node.js
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version-file: .node-version
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install osv-scanner
run: bash scripts/security/install-osv-scanner.sh
- name: Run dependency audit
run: bash scripts/security/check-npm-audit.sh
go-vuln:
name: Go Vulnerability Check
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
# Negative self-tests for the two guards below (#8283): a deleted pin or
# an empty govulncheck scan must fail, not pass. Uses the runner's Node.
- name: Self-test security guards
run: node --test scripts/security/check-security-guards.test.mjs
# Runs before Setup Go on purpose: this job only proves the toolchain it
# was handed is clean, so a pin that drifts anywhere else (release.yml
# builds the shipped binary) would scan one Go and ship another.
- name: Check Go toolchain pins agree
run: bash scripts/security/check-go-version-pins.sh
- name: Setup Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: agent/go.sum
- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
# Allowlist wrapper: fails on any symbol-level finding not in
# scripts/security/govulncheck-allowlist.txt (reviewed, unfixable-only
# exceptions with code-level mitigations).
- name: Run govulncheck
run: bash scripts/security/run-govulncheck.sh
cargo-audit:
name: Cargo Audit
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- name: Setup Rust
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
toolchain: stable
- name: Install cargo-audit
run: cargo install cargo-audit --locked
- name: Audit helper Cargo dependencies
working-directory: apps/helper/src-tauri
run: cargo audit --deny warnings
- name: Audit viewer Cargo dependencies
working-directory: apps/viewer/src-tauri
run: cargo audit --deny warnings
trivy-fs-scan:
name: Trivy Filesystem Scan
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- name: Generate Trivy filesystem SARIF
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
scan-type: 'fs'
severity: 'HIGH,CRITICAL'
format: 'sarif'
output: 'trivy-fs.sarif'
exit-code: '0'
- name: Upload Trivy filesystem SARIF
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
if: always()
with:
sarif_file: 'trivy-fs.sarif'
- name: Generate Trivy filesystem SBOM
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
scan-type: 'fs'
format: 'cyclonedx'
output: 'trivy-fs.cdx.json'
exit-code: '0'
- name: Upload Trivy filesystem SBOM
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
if: always()
with:
name: trivy-fs-sbom
path: trivy-fs.cdx.json
retention-days: 30
- name: Run blocking Trivy filesystem scan
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
scan-type: 'fs'
severity: 'HIGH,CRITICAL'
format: 'table'
exit-code: '1'
# Builds and scans every container image this repo publishes, using the SAME
# Dockerfile the release lanes ship.
#
# This list used to be hand-maintained beside the publish list in release.yml,
# and the two drifted: the job built the
# `docker/Dockerfile.api|web` compose variants while release.yml shipped
# `apps/api|web/Dockerfile`, so the two most widely deployed images in the
# product were gated by nothing and shipped a known HIGH CVE
# (issues #4273 / #4260). `apps/api/src/config/dockerfileImageScanCoverage.test.ts`
# now derives the published set from those workflows and fails if anything
# published is missing from the matrix below.
trivy-image-scan:
name: Trivy Image Scan (${{ matrix.image }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
# --- Published to GHCR by release.yml (emergency manual builds go
# through `docker buildx` + a GHCR push from a maintainer machine
# instead of a workflow) ---
- image: api
dockerfile: apps/api/Dockerfile
- image: web
dockerfile: apps/web/Dockerfile
- image: portal
dockerfile: apps/portal/Dockerfile
- image: m365-graph-read-executor
dockerfile: apps/m365-graph-read-executor/Dockerfile
- image: m365-graph-actions-executor
dockerfile: apps/m365-graph-actions-executor/Dockerfile
- image: m365-communications-executor
dockerfile: apps/m365-communications-executor/Dockerfile
# --- Never published, but built from source by CI and by users ---
# ci.yml's smoke test builds these via docker-compose.override.yml.ci,
# and self-hosters build them via docker-compose.override.yml.local-build,
# so they stay gated too.
- image: api-compose
dockerfile: docker/Dockerfile.api
- image: web-compose
dockerfile: docker/Dockerfile.web
# --- Never published, but executes model-authored code, so it is
# scanned anyway. Built from docker/ai-workspace, not the repo root.
#
# Debian base, not Alpine (glibc wheels are required for
# pandas/numpy). `bookworm` carries dozens of HIGH/CRITICAL Debian
# CVEs with no fix published yet (affected/will_not_fix/fix_deferred
# upstream) — that's a property of the distro, not this image, and
# blocking on it would make the job permanently red. The policy for
# this image is "zero FIXABLE HIGH/CRITICAL findings": OS packages
# with a real fix are picked up by `apt-get upgrade` in the
# Dockerfile, and `ignore-unfixed` below silences everything else.
# The remaining two Python findings (msgpack, setuptools) come from
# files vendored inside pip itself in the upstream base image, not
# from anything in requirements.lock, and have no available fix
# from our side either — see docker/ai-workspace/.trivyignore for
# the full explanation and the specific CVE IDs excluded there.
# The Alpine sibling images (api-compose, web-compose, etc.) stay on
# the strict `ignore-unfixed: false` default above.
- image: ai-workspace
dockerfile: docker/ai-workspace/Dockerfile
context: docker/ai-workspace
ignore-unfixed: true
trivyignores: docker/ai-workspace/.trivyignore
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- name: Build image
env:
DOCKERFILE: ${{ matrix.dockerfile }}
IMAGE: ${{ matrix.image }}
CONTEXT: ${{ matrix.context || '.' }}
run: docker build -f "$DOCKERFILE" -t "breeze-${IMAGE}:security-scan" "$CONTEXT"
- name: Scan image
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: 'breeze-${{ matrix.image }}:security-scan'
severity: 'HIGH,CRITICAL'
format: 'table'
exit-code: '1'
ignore-unfixed: ${{ matrix.ignore-unfixed || false }}
trivyignores: ${{ matrix.trivyignores || '' }}
# Lints the workflow files themselves. actionlint catches real workflow
# bugs (bad expressions, wrong action inputs, invalid syntax); shellcheck
# integration is disabled because the existing style nits in release.yml
# would make this job perma-red — red must mean "act now". zizmor audits
# for security smells (template injection, unpinned third-party actions,
# excessive permissions); pin policy + accepted findings live in
# .github/zizmor.yml.
workflow-lint:
name: Workflow Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- name: Setup Node
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7; temporary resolver input; Task 2 pins it before push
with:
node-version-file: .node-version
- name: Run workflow security policy
run: |
node --test \
.github/scripts/check-workflow-security.test.mjs \
.github/scripts/cancel-stale-merge-group-runs.test.mjs \
scripts/security/pin-github-actions.test.mjs \
scripts/release/attest-macos-pkg.test.mjs
node .github/scripts/check-workflow-security.mjs
- name: Install actionlint (pinned + checksum-verified)
run: |
curl -sSfL -o actionlint.tar.gz \
https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_amd64.tar.gz
echo "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 actionlint.tar.gz" | sha256sum -c -
tar xzf actionlint.tar.gz actionlint
- name: Run actionlint
run: ./actionlint -shellcheck=
# selfhost-signing-template/ is the source of truth for the public
# breeze-selfhost-signing template repo. Its own .github/workflows/ci.yml
# is never loaded here (nested workflow directories are not scanned), so
# without this the verifier that decides whether a self-hoster applies
# their Authenticode/Apple certificate to downloaded bytes is only ever
# tested in the CONSUMER's fork, after adoption.
- name: Verify BYO-signing template (self-test + standalone actionlint)
run: |
set -euo pipefail
node selfhost-signing-template/scripts/verify-manifest.test.mjs
# Lint the template as the standalone repo it becomes: its local
# reusable-workflow refs (./.github/workflows/sign-release.yml) only
# resolve when the template directory IS the repo root.
standalone="$(mktemp -d)"
cp -R selfhost-signing-template/. "$standalone/"
# actionlint locates the workflows directory via the git root.
git -C "$standalone" init -q
(cd "$standalone" && "$GITHUB_WORKSPACE/actionlint" -shellcheck=)
rm -rf "$standalone"
- name: Run zizmor
run: pipx run zizmor==1.25.2 --no-progress --config .github/zizmor.yml --min-severity medium .github/workflows/