-
Notifications
You must be signed in to change notification settings - Fork 11
fix(ci): move CVE scan to separate workflow to fix permission conflict #282
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,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')); | ||||||||||||||||||||||||||||||
|
Comment on lines
+37
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win Guard against a missing/empty audit-results file.
🛡️ Proposed guard const fs = require('fs');
- const results = JSON.parse(fs.readFileSync('/tmp/audit-results.json', 'utf8'));
+ if (!fs.existsSync('/tmp/audit-results.json')) {
+ console.log('No audit results file found; pip-audit may have errored. Skipping.');
+ return;
+ }
+ let results;
+ try {
+ results = JSON.parse(fs.readFileSync('/tmp/audit-results.json', 'utf8'));
+ } catch (e) {
+ core.warning(`Could not parse audit results: ${e.message}`);
+ return;
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 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; | ||||||||||||||||||||||||||||||
|
Comment on lines
+53
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🌐 Web query:
💡 Result: In the OSV (Open Source Vulnerability) schema, the severity of a vulnerability is represented by the Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate the workflow and inspect the relevant section with line numbers.
git ls-files .github/workflows/check-vulnerabilities.yaml
sed -n '1,220p' .github/workflows/check-vulnerabilities.yaml
# Find any other OSV parsing or severity handling in the repo.
rg -n "database_specific|severity\\]|CVSS_V3|osv.dev/v1/vulns|cvss_v3" .Repository: AgentToolkit/altk-evolve Length of output: 5885 Severity scoring should use OSV 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| } 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.'); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
pull_requesttrigger will write GitHub issues from PR context (fails on fork PRs, noisy on same-repo PRs).The issue create/update in the
Process resultsstep runs on everypull_requestevent when vulns are found. For PRs from forks,GITHUB_TOKENis downgraded to read-only regardless of thepermissions:block, soissues.create/issues.updatereturn 403 — and since thisgithub-scriptstep isn'tcontinue-on-error, the job fails. For same-repo PRs it will create/update the shared security issue on every PR, duplicating the scheduled run.Consider restricting the issue-filing logic to the scheduled event (e.g. gate the write on
github.event_name == 'schedule'), or scope the PR run to reporting-only.🔧 Example: gate issue writes to scheduled runs
Also applies to: 32-33
🤖 Prompt for AI Agents