-
Notifications
You must be signed in to change notification settings - Fork 1.7k
193 lines (175 loc) · 7.33 KB
/
Copy pathsecurity.yml
File metadata and controls
193 lines (175 loc) · 7.33 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
# Security scanning — runs on every PR, on push to main, and weekly.
#
# Complements the CodeRabbit + Greptile app reviews (which fire on PR creation)
# with deterministic, gating checks:
# • gitleaks — secret scanning. HARD FAIL: a leaked credential blocks merge.
# • CodeQL — Python + JS/TS SAST. Results land in the Security tab.
# • bandit — Python SAST (SARIF → Security tab). Reporting, non-gating.
# • pip-audit — Python dependency advisories. Reporting, non-gating.
# • bun audit — frontend dependency advisories. Reporting, non-gating.
#
# Only the secret scan gates the PR. Dependency advisories and bandit findings
# are surfaced as signal (Security tab / job log) rather than blocking every PR
# on a transitive upstream advisory — consistent with the "no ceremony,
# continuous-to-main" cadence.
name: Security
on:
pull_request:
branches: [main]
push:
branches: [main]
schedule:
# Mondays 06:00 UTC — catch advisories disclosed since the last PR.
- cron: "0 6 * * 1"
workflow_dispatch:
env:
# Match ci.yml: run JS actions on Node 24 (GH removes Node 20 in Sep 2026).
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
# Least privilege by default; jobs that upload SARIF opt into security-events.
permissions:
contents: read
# PR branches: a new push cancels the superseded scan (no wasted runners).
# main: every commit keeps its own group, so nothing is cancelled — a merge
# train used to leave a permanent red ✗ ("cancelled") on every intermediate
# commit in the history view even though nothing failed.
concurrency:
group: security-${{ github.ref }}-${{ github.ref == 'refs/heads/main' && github.sha || 'branch' }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
# ── Secret scanning (gating) ─────────────────────────────────────────────
# Full-history scan on push to main; PR-diff scan on pull_request (faster,
# and the action picks the right mode from the event automatically).
secrets:
name: Secret scan (gitleaks)
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
# gitleaks needs full history to scan all commits on push events.
fetch-depth: 0
# No authed git needed after clone; don't persist GITHUB_TOKEN.
persist-credentials: false
- name: gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# GITLEAKS_LICENSE is only required for GitHub *organizations*; this is
# a personal public repo, so the action runs free without it.
# ── CodeQL SAST (Python + JS/TS) ─────────────────────────────────────────
codeql:
name: CodeQL (${{ matrix.language }})
runs-on: ubuntu-22.04
permissions:
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [python, javascript-typescript]
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# Both targets are interpreted — no compiled build step needed.
build-mode: none
# Scope analysis to shipped product code. The excluded trees never
# ship in the installer's runtime path and produce the bulk of the
# note-level + false-positive findings (file-not-closed in eval
# harnesses, unused alembic migration globals, bind-all in tests,
# path sinks in the legacy Gradio research UI). Queries live in the
# inline config so there's a single source of truth next to
# paths-ignore. paths-ignore is supported here because build-mode is
# `none` (interpreted analysis).
config: |
queries:
- uses: security-and-quality
paths-ignore:
- omnivoice/eval
- research
- tests
- backend/migrations
- "**/*.test.js"
- "**/*.test.jsx"
- "**/*.test.ts"
- "**/*.test.tsx"
- name: Analyze
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.language }}"
# ── Python SAST (bandit → SARIF) ─────────────────────────────────────────
bandit:
name: Python SAST (bandit)
runs-on: ubuntu-22.04
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Setup Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
# -ll: report MEDIUM+ severity only. -ii: MEDIUM+ confidence only.
# Keeps the SARIF focused on findings worth a human look. The scan step
# is allowed to "fail" (findings present) without failing the job; the
# SARIF upload still runs so results reach the Security tab.
#
# NOTE: the `sarif` output format lives in the `bandit[sarif]` extra
# (pulls in sarif-om + jschema-to-python). Plain `bandit` rejects
# `-f sarif`, so install via the extra spec.
- name: Run bandit
continue-on-error: true
run: |
pipx run --spec 'bandit[sarif]' bandit -r backend/ -ll -ii -f sarif -o bandit.sarif
# continue-on-error: this job is reporting-only. If bandit can't write a
# SARIF for any reason (no findings dir, pipx hiccup), don't fail the job.
- name: Upload bandit SARIF
uses: github/codeql-action/upload-sarif@v3
if: always()
continue-on-error: true
with:
sarif_file: bandit.sarif
category: bandit
# ── Dependency advisories (reporting) ────────────────────────────────────
dependencies:
name: Dependency audit
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Install uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Setup Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
# Audit the resolved Python environment. Non-gating: a transitive
# advisory with no fix available should not wall off every PR.
- name: pip-audit (Python)
continue-on-error: true
run: |
bash scripts/uv-sync-retry.sh
uv run --with pip-audit pip-audit
# Pin a floor: `bun audit` was added in bun 1.2.x, so guarantee it exists.
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: "1.2"
# `bun audit` reports advisories against the frontend lockfile. Non-gating
# for the same reason; also tolerant of older bun without the subcommand.
- name: bun audit (frontend)
continue-on-error: true
working-directory: frontend
run: |
bun install --frozen-lockfile
bun audit