Skip to content

CVE Monitor

CVE Monitor #2

Workflow file for this run

name: CVE Monitor
# Nightly supply-chain CVE monitor — closes Phase 0 audit finding C-14.
# Plan: docs/plan/bfsi-v1/04-commits.md commit C-032 (owner Role 22).
#
# Runs `npm audit --json` (always available) plus `npx osv-scanner -r .`
# (Google's OSV scanner — covers ecosystems npm audit doesn't, eg cargo,
# gradle, swift packages used by the mobile sub-projects).
#
# If either scanner reports any vulnerability with severity `high` or
# `critical`, the workflow opens a GitHub issue with the scanner output
# attached and sends an email alert to the address held in the
# SECURITY_ALERT_EMAIL secret. Both signals are intentional belt-and-
# braces — Slack alerts are wired in by C-129..C-142 (alert tuning).
on:
schedule:
# Nightly at 00:00 UTC (05:30 IST — well before the 09:30 IST standup).
- cron: '0 0 * * *'
workflow_dispatch:
concurrency:
group: cve-monitor
cancel-in-progress: false
permissions:
contents: read
issues: write
jobs:
scan:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: |
package-lock.json
dashboard/package-lock.json
website/package-lock.json
- name: Install root dependencies
run: npm ci
- name: Install dashboard dependencies
run: npm --prefix dashboard ci
- name: Install website dependencies
run: npm --prefix website ci
- name: Run CVE scanners
id: scan
# The helper script exits 0 when no high/critical CVEs are found
# and exits non-zero (1) when at least one is found. We capture
# the exit status into a step output so the follow-up "open
# issue" + "email alert" steps can fire conditionally without
# short-circuiting the whole job.
run: |
set +e
./scripts/cve-monitor.sh > /tmp/cve-monitor.log 2>&1
status=$?
echo "status=${status}" >> "$GITHUB_OUTPUT"
cat /tmp/cve-monitor.log
# Always succeed at this step so artefact upload + alerting
# run; the job itself fails at the final guard step below.
exit 0
- name: Upload scanner output
if: always()
uses: actions/upload-artifact@v4
with:
name: cve-monitor-log
path: /tmp/cve-monitor.log
retention-days: 30
if-no-files-found: warn
- name: Open GitHub issue on high/critical finding
if: steps.scan.outputs.status != '0'
uses: actions/github-script@v7
env:
SCAN_LOG_PATH: /tmp/cve-monitor.log
with:
script: |
const fs = require('fs');
const path = process.env.SCAN_LOG_PATH;
let log = '(scanner log missing)';
try {
log = fs.readFileSync(path, 'utf8');
} catch (err) {
core.warning(`Could not read scanner log at ${path}: ${err.message}`);
}
// Cap at 60 KB so the issue body stays under GitHub's limit.
if (log.length > 60_000) {
log = log.slice(0, 60_000) + '\n\n…truncated…';
}
const today = new Date().toISOString().slice(0, 10);
const title = `CVE monitor: high/critical finding ${today}`;
const body = [
'## Nightly CVE monitor — high/critical finding',
'',
`Run: ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
`Workflow: \`${context.workflow}\``,
`Commit: ${context.sha}`,
'',
'Closes audit-findings.md C-14 instrumentation; see',
'`docs/plan/bfsi-v1/04-commits.md` commit C-032 for context.',
'',
'### Scanner output',
'',
'```',
log,
'```',
].join('\n');
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
body,
labels: ['security', 'cve-monitor'],
});
- name: Send email alert on high/critical finding
if: steps.scan.outputs.status != '0'
env:
SECURITY_ALERT_EMAIL: ${{ secrets.SECURITY_ALERT_EMAIL }}
run: |
if [ -z "${SECURITY_ALERT_EMAIL:-}" ]; then
echo "::warning::SECURITY_ALERT_EMAIL secret is not set; skipping email alert."
exit 0
fi
# The repo's mail relay is exercised by tests/email.test.ts; the
# workflow itself uses a thin sendmail wrapper rather than
# introducing a new GitHub Action dep (DP6: every dep is an ADR).
subject="[ZeroAuth] CVE monitor: high/critical finding $(date -u +%F)"
{
echo "To: ${SECURITY_ALERT_EMAIL}"
echo "From: cve-monitor@zeroauth.dev"
echo "Subject: ${subject}"
echo ""
echo "The nightly CVE monitor (.github/workflows/cve-monitor.yml)"
echo "found at least one vulnerability rated high or critical."
echo ""
echo "Run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
echo ""
echo "Scanner output:"
echo "----"
cat /tmp/cve-monitor.log || echo "(log unavailable)"
} > /tmp/cve-monitor.mail
if command -v sendmail >/dev/null 2>&1; then
sendmail -t < /tmp/cve-monitor.mail
echo "Email queued via sendmail to ${SECURITY_ALERT_EMAIL}."
else
echo "::warning::sendmail not available on runner; falling back to printing the message."
cat /tmp/cve-monitor.mail
fi
- name: Fail job if any high/critical CVE was found
if: steps.scan.outputs.status != '0'
run: |
echo "::error::High or critical CVE detected; see uploaded log and opened issue."
exit 1