-
Notifications
You must be signed in to change notification settings - Fork 1
222 lines (196 loc) · 10.3 KB
/
security.yml
File metadata and controls
222 lines (196 loc) · 10.3 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
# security.yml — Colabs
#
# Three independent security scanning jobs, each targeting a different surface:
#
# secret-scan — credentials accidentally committed (Gitleaks)
# audit — known CVEs in npm dependencies (npm audit)
# codeql — static analysis: XSS, open redirect, prototype pollution
#
# Triggers:
# PR into dev or main → all three jobs run, block merge on failure
# Weekly (Monday 08:00) → full scan of main, catches newly disclosed CVEs
# Manual dispatch → run on demand from the Actions tab
#
# Required status checks to add in branch protection (Settings → Branches):
# ✓ Security / secret-scan
# ✓ Security / codeql
# (audit is required too but consider leaving it advisory initially — see note below)
#
# Note on audit as a required check:
# npm audit fails on high/critical CVEs. Transitive dependency vulnerabilities
# you cannot immediately update will permanently block all PRs until resolved.
# Strategy: add it as required, then use .nsprc or audit-exceptions for any
# known unfixable transitive CVEs — document the reason as a comment.
name: Security
on:
push:
branches:
- '**'
pull_request:
branches:
- dev
- main
schedule:
- cron: '0 8 * * 1'
workflow_dispatch:
# Separate concurrency groups for PRs vs scheduled/manual runs.
# PR runs cancel each other (only latest push matters).
# Scheduled and manual runs must NEVER be cancelled mid-flight —
# a cancelled security scan gives a false "green" signal.
concurrency:
group: security-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
# ─────────────────────────────────────────────────────────────────────────────
# JOB 1 — Secret scanning (Gitleaks)
#
# Why this matters for Colabs specifically:
# - Open source contributors copy-paste from local .env files
# - Supabase anon keys, GitHub OAuth client secrets, and JWT secrets all
# have recognisable patterns that Gitleaks catches out of the box
# - A leaked Supabase anon key with permissive RLS is a direct data breach
#
# Scans the FULL git history of the PR diff — a secret added in commit 1 and
# "removed" in commit 3 is still caught. fetch-depth: 0 is required for this.
# Required for repositories owned by organizations (SpaceyaTech).
# ─────────────────────────────────────────────────────────────────────────────
secret-scan:
name: Security / secret-scan
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
security-events: write # required to upload SARIF to the Security tab
steps:
- name: Checkout (full history for complete diff scan)
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Verify Gitleaks License Presence
# Diagnoses if the GITLEAKS_LICENSE secret is accessible to the workflow.
# Secrets are not available in PRs from forks, and Gitleaks is free for public repos.
# Therefore, we issue a warning rather than failing the build.
run: |
if [ -z "$GITLEAKS_LICENSE" ]; then
echo "::warning::GITLEAKS_LICENSE is empty! Check Org settings if this is a private repository. This is expected for PRs from forks."
else
echo "GITLEAKS_LICENSE is present and populated."
fi
env:
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2.3.9
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
GITLEAKS_ENABLE_SARIF: true
# ─────────────────────────────────────────────────────────────────────────────
# JOB 2 — Dependency vulnerability audit (npm audit)
#
# Why this matters for Colabs specifically:
# - Supabase client, React Hook Form, Zod, Framer Motion — any of these
# can receive a CVE disclosure after you've already installed them
# - Colabs will add Stripe — a CVE in the payment library path is critical
# - --audit-level=high: ignore low/moderate (too noisy in open source),
# fail on high/critical (direct risk to user data or payment flow)
#
# The weekly schedule is what makes this valuable — it catches CVEs that
# are disclosed against packages you haven't touched in months.
# ─────────────────────────────────────────────────────────────────────────────
audit:
name: Security / audit
if: github.event_name != 'push' || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v6
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm ci
- name: Audit for high and critical CVEs
# Focus on production dependencies to ensure application security
# while avoiding blocks from unfixable build-tool vulnerabilities.
run: npm audit --audit-level=high --omit=dev
# 3. Add a comment above it explaining the CVE, why it is acceptable
# temporarily, and a target date to resolve it.
# Never add exceptions silently.
# ─────────────────────────────────────────────────────────────────────────────
# JOB 3 — Static Application Security Testing (CodeQL)
#
# Why this matters for Colabs specifically:
# - GitHub OAuth flow has redirect URIs — CodeQL catches open redirects
# where user-supplied next params reach window.location without validation
# - Supabase query builders with user input — CodeQL catches unvalidated
# input reaching query construction in Edge Functions
# - File upload (Storage) — CodeQL catches path traversal patterns
# - dangerouslySetInnerHTML with user content — DOM-based XSS
#
# Uses security-extended query suite (broader than default):
# + DOM XSS, prototype pollution, open redirect, uncontrolled format strings
#
# Results appear in: Security → Code scanning alerts on the repo.
# Free for public repos, runs entirely within GitHub infrastructure.
# ─────────────────────────────────────────────────────────────────────────────
codeql:
name: Security / codeql
if: github.event_name != 'push' || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
security-events: write
actions: read
contents: read
steps:
- uses: actions/checkout@v4
- name: Initialise CodeQL
uses: github/codeql-action/init@v4
with:
languages: javascript
queries: security-extended
- name: Install dependencies for import tracing
run: npm ci
- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v4
with:
category: '/language:javascript'
# ─────────────────────────────────────────────────────────────────────────────
# JOB 4 — Unintended .env commit check
#
# Ensures .env files aren't accidentally pushed into the repository.
# ─────────────────────────────────────────────────────────────────────────────
check-env:
name: Security / check-env
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- name: Check for tracked .env files
run: |
ENV_FILES=$(git ls-files | awk '/(^|\/)\.env($|\.)/ && !/\.(example|template|sample)$/')
if [ -n "$ENV_FILES" ]; then
echo "::error::Found committed .env files. These files usually contain secrets and should not be tracked by git."
echo "The following .env files are currently tracked:"
echo "$ENV_FILES"
echo "Please remove them using: git rm --cached <file>"
echo "And ensure they are listed in .gitignore before committing."
exit 1
fi
TEMPLATE_FILES=$(git ls-files | awk '/(^|\/)\.env\.(example|template|sample)$/')
if [ -n "$TEMPLATE_FILES" ]; then
for file in $TEMPLATE_FILES; do
# 1. Known high-entropy prefixes: eyJ (JWTs), sk_test_/sk_live_/whsec_ (Stripe), gh[pousr]_ (GitHub)
SUSPICIOUS=$(grep -E '=(eyJ|sk_(test|live)_|whsec_|gh[pousr]_)' "$file" || true)
# 2. Generic 32+ char alphanumerics, ignoring usual placeholder words
GENERIC=$(grep -E '=[a-zA-Z0-9]{32,}' "$file" | grep -v -i -E '(your|example|dummy|mock|insert|here)' || true)
if [ -n "$SUSPICIOUS" ] || [ -n "$GENERIC" ]; then
echo "::error::File $file contains what looks like actual key patterns."
[ -n "$SUSPICIOUS" ] && echo "$SUSPICIOUS"
[ -n "$GENERIC" ] && echo "$GENERIC"
echo "Please ensure templates only contain empty values or placeholders (e.g., 'your_key_here')."
exit 1
fi
done
fi
echo "No tracked .env files found, and templates are clean."