-
Notifications
You must be signed in to change notification settings - Fork 11
140 lines (126 loc) · 5.55 KB
/
Copy pathcheck-vulnerabilities.yaml
File metadata and controls
140 lines (126 loc) · 5.55 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
name: Check Vulnerabilities
on:
pull_request:
schedule:
- cron: '0 8 * * 1'
permissions:
contents: read
issues: write
jobs:
check-vulnerabilities:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
python-version: '3.12'
- name: Export requirements from lockfile
run: uv export --no-hashes --frozen > /tmp/requirements.txt
- name: Install pip-audit
run: uv tool install pip-audit
- name: Run pip-audit
id: audit
continue-on-error: true
run: pip-audit -r /tmp/requirements.txt --format json --output /tmp/audit-results.json
- name: Process results and report CVEs
if: steps.audit.outcome == 'failure'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const results = JSON.parse(fs.readFileSync('/tmp/audit-results.json', 'utf8'));
const vulns = [];
for (const dep of results.dependencies || []) {
for (const vuln of dep.vulns || []) {
vulns.push({ name: dep.name, version: dep.version, id: vuln.id, fix_versions: vuln.fix_versions || [] });
}
}
if (vulns.length === 0) return;
// Query OSV API for severity of each unique vuln ID
const uniqueIds = [...new Set(vulns.map(v => v.id))];
const severityMap = {};
for (const id of uniqueIds) {
try {
const resp = await fetch(`https://api.osv.dev/v1/vulns/${id}`);
if (!resp.ok) continue;
const data = await resp.json();
let score = 0;
if (data.database_specific?.cvss_v3) {
score = typeof data.database_specific.cvss_v3 === 'number'
? data.database_specific.cvss_v3
: parseFloat(data.database_specific.cvss_v3) || 0;
}
if (data.database_specific?.severity) {
const sev = data.database_specific.severity.toUpperCase();
if (sev === 'CRITICAL') score = Math.max(score, 9.0);
else if (sev === 'HIGH') score = Math.max(score, 7.0);
}
if (score === 0 && data.affected) {
for (const a of data.affected) {
if (a.database_specific?.cvss_v3) {
score = Math.max(score, parseFloat(a.database_specific.cvss_v3) || 0);
}
}
}
severityMap[id] = score;
} catch (e) {
console.log(`Warning: could not fetch severity for ${id}`);
severityMap[id] = 0;
}
}
// Categorize
const critical = vulns.filter(v => (severityMap[v.id] || 0) >= 9.0);
const high = vulns.filter(v => {
const s = severityMap[v.id] || 0;
return s >= 7.0 && s < 9.0;
});
console.log(`Found ${critical.length} critical, ${high.length} high vulnerabilities`);
// Create/update GH issue for high+critical
if (critical.length > 0 || high.length > 0) {
const lines = ['## Automated CVE Scan Results\n', `_Last scanned: ${new Date().toISOString().split('T')[0]}_\n`];
if (critical.length) {
lines.push('### Critical (CVSS >= 9.0)\n');
critical.forEach(v => lines.push(`- **${v.id}** in \`${v.name}==${v.version}\` — fix: ${v.fix_versions.join(', ') || 'none available'}`));
}
if (high.length) {
lines.push('\n### High (CVSS >= 7.0)\n');
high.forEach(v => lines.push(`- **${v.id}** in \`${v.name}==${v.version}\` — fix: ${v.fix_versions.join(', ') || 'none available'}`));
}
const existing = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
labels: 'security,automated',
state: 'open'
});
const title = `[Security] ${critical.length} critical, ${high.length} high vulnerabilities detected`;
const existingIssue = existing.data.find(i => i.labels.some(l => l.name === 'automated'));
if (existingIssue) {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existingIssue.number,
title,
body: lines.join('\n')
});
console.log(`Updated issue #${existingIssue.number}`);
} else {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
body: lines.join('\n'),
labels: ['security', 'automated']
});
console.log('Created new security issue');
}
}
// Report but never block the build
if (critical.length > 0) {
core.warning(`${critical.length} critical CVEs found: ${critical.map(v => v.id).join(', ')} — tracked in GitHub issue`);
} else {
console.log('No critical vulnerabilities. High-severity issues tracked in GitHub issue.');
}