Repository navigation
172 lines (157 loc) · 8.33 KB
/
Copy pathsnyk.yml
File metadata and controls
172 lines (157 loc) · 8.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
name: 🔒 Snyk
# Dependency vulnerability scanning and automated triage alerting.
#
# Runs as its own workflow rather than a job inside CI, matching how this repo
# already separates concerns (lint.yml, typecheck.yml, main.yml, chromatic.yml
# are each one file). Concretely it needs a different trigger gate, a different
# secret, and has different failure semantics from a build — folding it into
# main.yml would mean a security finding reads as "the build broke".
#
# In addition to push/PR events, this runs on a recurring weekly schedule
# (Mondays at 06:00 UTC) to catch newly disclosed CVEs even when no new code
# has been merged. When high/critical vulnerabilities are detected on main,
# it automatically creates or updates a tracking issue for maintainer triage.
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
# Recurring weekly scan: Every Monday at 06:00 UTC (matches Dependabot's weekly schedule)
- cron: '0 6 * * 1'
workflow_dispatch:
permissions:
contents: read
issues: write
concurrency:
group: snyk-${{ github.ref }}
cancel-in-progress: true
jobs:
security:
name: Dependency scan
runs-on: ubuntu-latest
# GitHub does not expose secrets to pull_request runs from forks, and
# almost every contribution here comes from one. Without this gate SNYK_TOKEN
# would be empty on those runs and the job would fail red on contributor PRs
# through no fault of theirs — the same trap the Vercel check falls into.
# main still gets scanned on every push, weekly schedule, and manual dispatch.
if: >-
github.event_name == 'push' ||
github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch' ||
github.event.pull_request.head.repo.full_name == github.repository
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node
uses: actions/setup-node@v7
with:
node-version: 22
# ── Why this step exists ────────────────────────────────────────────────
# This project uses bun, and its lockfile is the newer text-format
# `bun.lock`. Snyk's Node scanner resolves dependency trees from
# package-lock.json, yarn.lock or pnpm-lock.yaml — bun.lock is not a
# supported target file, so `snyk test` would either scan nothing useful
# or fall back to package.json ranges, which cannot pin transitive
# versions and therefore misses most real vulnerabilities.
#
# `--package-lock-only` writes a lockfile from package.json without
# installing anything or running scripts. It is generated fresh here and
# never committed — package-lock.json is in .gitignore, and CLAUDE.md
# forbids committing it, since bun.lock remains the source of truth.
- name: Generate a lockfile Snyk can parse
run: npm install --package-lock-only --ignore-scripts
- name: Run Snyk
id: snyk
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
# High and critical only, so the first run is actionable rather than
# a wall of low-severity transitive noise. Lower it once the backlog
# is clean.
args: --severity-threshold=high --file=package-lock.json
- name: Alert on High/Critical Snyk Findings
if: failure() && steps.snyk.outcome == 'failure' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch')
uses: actions/github-script@v9
env:
HAS_SNYK_TOKEN: ${{ secrets.SNYK_TOKEN != '' }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
if (process.env.HAS_SNYK_TOKEN !== 'true') {
console.log('SNYK_TOKEN secret is not set; skipping automated vulnerability issue creation.');
return;
}
const owner = context.repo.owner;
const repo = context.repo.repo;
const runUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`;
const commitSha = context.sha ? context.sha.substring(0, 7) : 'unknown';
const MARKER = '<!-- snyk-vulnerability-alert -->';
// Check if an open alert issue already exists to avoid spamming duplicates
const { data: issues } = await github.rest.issues.listForRepo({
owner,
repo,
state: 'open',
labels: 'security',
per_page: 50,
});
const existingIssue = issues.find(i => i.body && i.body.includes(MARKER));
if (existingIssue) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: existingIssue.number,
body: [
`### ⚠️ Recurring Snyk Scan Alert Update`,
``,
`The automated Snyk scan on \`main\` detected active high/critical vulnerabilities.`,
``,
`- **Trigger:** \`${context.eventName}\``,
`- **Commit:** \`${commitSha}\``,
`- **Workflow Run:** [View Snyk Logs](${runUrl})`,
``,
`Please follow the triage and remediation guide in [docs/DEPENDENCY_REVIEW.md](https://github.com/${owner}/${repo}/blob/main/docs/DEPENDENCY_REVIEW.md).`,
].join('\n'),
});
console.log(`Updated existing security tracking issue #${existingIssue.number}`);
} else {
const newIssue = await github.rest.issues.create({
owner,
repo,
title: '🔒 [Security Alert] High/Critical vulnerabilities detected by Snyk',
labels: ['security', 'dependencies', 'type:security', 'level:intermediate'],
body: [
MARKER,
`## 🔒 Snyk Security Scan Alert`,
``,
`The recurring Snyk dependency scan on \`main\` has detected one or more **high** or **critical** severity vulnerabilities.`,
``,
`### Scan Context`,
`- **Trigger Event:** \`${context.eventName}\``,
`- **Commit:** \`${commitSha}\``,
`- **Run Details:** [View Workflow Run & Vulnerability Logs](${runUrl})`,
`- **Severity Threshold:** \`high\` / \`critical\``,
``,
`### Triage Expectations & SLA`,
`- **Critical Findings:** Triage within **48 hours**; resolve/mitigate within **7 days**.`,
`- **High Findings:** Triage within **5 business days**; resolve/mitigate within **14 days**.`,
``,
`### Action Checklist for Maintainers & Contributors`,
`1. **Inspect Findings:** Review the [Workflow Run](${runUrl}) logs to identify the vulnerable packages and CVE details.`,
`2. **Check Dependabot Overlap:** Review open [Dependabot PRs](https://github.com/${owner}/${repo}/pulls?q=is%3Apr+is%3Aopen+label%3Adependencies) to see if an automated update PR already exists for the flagged package.`,
`3. **Local Reproduction & Fix:**`,
` \`\`\`bash`,
` npm run security # generates temporary package-lock.json and runs snyk test`,
` bun update <vulnerable-package>`,
` bun test`,
` bun run build`,
` \`\`\``,
` > **Note:** Never commit \`package-lock.json\`. Keep \`bun.lock\` as the single source of truth.`,
`4. **Unreachable / Dev-Only Exceptions:** If a vulnerability is confirmed non-exploitable (e.g. dev-only build tool with no client-side runtime path), document the exception with a clear rationale and expiry date in [\`.snyk\`](https://github.com/${owner}/${repo}/blob/main/.snyk).`,
``,
`📖 Process details: [docs/DEPENDENCY_REVIEW.md](https://github.com/${owner}/${repo}/blob/main/docs/DEPENDENCY_REVIEW.md)`,
].join('\n'),
});
console.log(`Created new security tracking issue #${newIssue.data.number}`);
}