Dependency Vulnerability Triage
Convert vulnerability reports into actionable patch plans.
Vulnerability Severity Matrix
// severity-matrix.ts export interface Vulnerability { id: string; package: string; currentVersion: string; patchedVersion: string; severity: "critical" | "high" | "medium" | "low"; cvss: number; cwe: string[]; exploitability: "high" | "medium" | "low"; impact: string; path: string[]; }
export interface PatchPriority { vulnerability: Vulnerability; priority: 1 | 2 | 3 | 4; reasoning: string; patchWindow: "24h" | "1week" | "1month" | "next-release"; breakingChange: boolean; testingRequired: "minimal" | "moderate" | "extensive"; }
export function calculatePriority(vuln: Vulnerability): PatchPriority { let priority: 1 | 2 | 3 | 4 = 4; let patchWindow: "24h" | "1week" | "1month" | "next-release" = "next-release"; let testingRequired: "minimal" | "moderate" | "extensive" = "minimal";
// P1: Critical + High Exploitability + Production if ( vuln.severity === "critical" && vuln.exploitability === "high" && isProductionDependency(vuln.package) ) { priority = 1; patchWindow = "24h"; testingRequired = "moderate"; } // P2: High + Medium/High Exploitability else if ( vuln.severity === "high" && ["high", "medium"].includes(vuln.exploitability) ) { priority = 2; patchWindow = "1week"; testingRequired = "moderate"; } // P3: Medium or Low Exploitability High else if ( vuln.severity === "medium" || (vuln.severity === "high" && vuln.exploitability === "low") ) { priority = 3; patchWindow = "1month"; testingRequired = "minimal"; }
return {
vulnerability: vuln,
priority,
reasoning: ${vuln.severity} severity, ${vuln.exploitability} exploitability,
patchWindow,
breakingChange: isBreakingChange(vuln.currentVersion, vuln.patchedVersion),
testingRequired,
};
}
Audit Report Parser
// scripts/parse-audit.ts import { execSync } from "child_process";
interface NpmAuditResult { vulnerabilities: Record<string, any>; metadata: { vulnerabilities: { critical: number; high: number; moderate: number; low: number; }; }; }
function parseNpmAudit(): Vulnerability[] { const auditOutput = execSync("npm audit --json", { encoding: "utf-8" }); const audit: NpmAuditResult = JSON.parse(auditOutput);
const vulnerabilities: Vulnerability[] = [];
Object.entries(audit.vulnerabilities).forEach(([pkg, data]) => {
vulnerabilities.push({
id: data.via[0]?.url || vuln-${pkg},
package: pkg,
currentVersion: data.range,
patchedVersion: data.fixAvailable?.version || "N/A",
severity: data.severity,
cvss: data.via[0]?.cvss?.score || 0,
cwe: data.via[0]?.cwe || [],
exploitability: determineExploitability(data),
impact: data.via[0]?.title || "Unknown",
path: data.via.map((v: any) => v.name),
});
});
return vulnerabilities; }
function determineExploitability(data: any): "high" | "medium" | "low" { // Check if actively exploited if ( data.via[0]?.findings?.some((f: any) => f.exploit === "proof-of-concept") ) { return "high"; }
// Check CVSS exploitability subscore const exploitScore = data.via[0]?.cvss?.exploitabilityScore; if (exploitScore > 3.5) return "high"; if (exploitScore > 2.0) return "medium"; return "low"; }
Patch Plan Generator
// scripts/generate-patch-plan.ts interface PatchPlan { immediate: PatchPriority[]; // P1 - 24h shortTerm: PatchPriority[]; // P2 - 1 week mediumTerm: PatchPriority[]; // P3 - 1 month longTerm: PatchPriority[]; // P4 - next release breakingChanges: PatchPriority[]; safeUpgrades: PatchPriority[]; }
function generatePatchPlan(vulnerabilities: Vulnerability[]): PatchPlan { const prioritized = vulnerabilities.map(calculatePriority);
return { immediate: prioritized.filter((p) => p.priority === 1), shortTerm: prioritized.filter((p) => p.priority === 2), mediumTerm: prioritized.filter((p) => p.priority === 3), longTerm: prioritized.filter((p) => p.priority === 4), breakingChanges: prioritized.filter((p) => p.breakingChange), safeUpgrades: prioritized.filter((p) => !p.breakingChange), }; }
function printPatchPlan(plan: PatchPlan) { console.log("# Security Patch Plan\n");
console.log("## 🚨 Immediate (24 hours)\n");
plan.immediate.forEach((p) => {
console.log(
- **${p.vulnerability.package}** ${p.vulnerability.currentVersion} → ${p.vulnerability.patchedVersion}
);
console.log( ${p.vulnerability.impact});
console.log( Breaking: ${p.breakingChange ? "YES ⚠️" : "No"});
});
console.log("\n## ⚡ Short-term (1 week)\n");
plan.shortTerm.forEach((p) => {
console.log(
- **${p.vulnerability.package}** ${p.vulnerability.currentVersion} → ${p.vulnerability.patchedVersion}
);
});
console.log("\n## 📅 Medium-term (1 month)\n");
plan.mediumTerm.forEach((p) => {
console.log(
- ${p.vulnerability.package} ${p.vulnerability.currentVersion} → ${p.vulnerability.patchedVersion}
);
});
console.log("\n## ⚠️ Breaking Changes\n");
plan.breakingChanges.forEach((p) => {
console.log(- ${p.vulnerability.package}: Review migration guide);
});
}
Safe Upgrade Order
// Dependency graph-based upgrade order interface DependencyGraph { [pkg: string]: string[]; }
function determineSafeUpgradeOrder( patches: PatchPriority[] ): PatchPriority[][] { const graph = buildDependencyGraph(); const ordered: PatchPriority[][] = [];
// Level 0: No dependencies on other patches const level0 = patches.filter( (p) => !hasUpgradeDependencies(p.vulnerability.package, patches, graph) ); ordered.push(level0);
// Level 1+: Depends on previous levels let remaining = patches.filter((p) => !level0.includes(p)); let level = 1;
while (remaining.length > 0 && level < 10) { const currentLevel = remaining.filter((p) => canUpgradeNow(p.vulnerability.package, ordered.flat(), graph) );
if (currentLevel.length === 0) break; // Circular dependency
ordered.push(currentLevel);
remaining = remaining.filter((p) => !currentLevel.includes(p));
level++;
}
return ordered; }
// Example output: // Level 0: [lodash, axios] - No dependencies // Level 1: [express] - Depends on lodash // Level 2: [next] - Depends on express
Risk Assessment
interface RiskAssessment { package: string; riskFactors: string[]; riskScore: number; // 1-10 mitigations: string[]; }
function assessUpgradeRisk(patch: PatchPriority): RiskAssessment { const risks: string[] = []; let score = 0;
// Check for breaking changes if (patch.breakingChange) { risks.push("Breaking changes detected"); score += 4; }
// Check for major version bump if ( isMajorVersionBump( patch.vulnerability.currentVersion, patch.vulnerability.patchedVersion ) ) { risks.push("Major version upgrade"); score += 3; }
// Check usage patterns const usage = analyzePackageUsage(patch.vulnerability.package); if (usage.importCount > 50) { risks.push("Heavily used in codebase"); score += 2; }
// Check test coverage if (usage.testCoverage < 0.7) { risks.push("Low test coverage"); score += 2; }
return { package: patch.vulnerability.package, riskFactors: risks, riskScore: Math.min(score, 10), mitigations: generateMitigations(risks), }; }
function generateMitigations(risks: string[]): string[] { const mitigations: string[] = [];
if (risks.includes("Breaking changes detected")) { mitigations.push("Read CHANGELOG and migration guide"); mitigations.push("Test on feature branch first"); mitigations.push("Deploy to staging before production"); }
if (risks.includes("Heavily used in codebase")) { mitigations.push("Run full test suite"); mitigations.push("Perform manual smoke tests"); mitigations.push("Monitor error rates after deploy"); }
if (risks.includes("Low test coverage")) { mitigations.push("Add tests for critical paths"); mitigations.push("Extend monitoring"); }
return mitigations; }
Automated Patch Script
#!/bin/bash
scripts/apply-patches.sh
set -e
PRIORITY=$1 # immediate, short-term, medium-term
if [ -z "$PRIORITY" ]; then echo "Usage: ./apply-patches.sh [immediate|short-term|medium-term]" exit 1 fi
echo "🔧 Applying $PRIORITY patches..."
Generate patch plan
npm audit --json > audit-report.json node scripts/generate-patch-plan.js --priority=$PRIORITY > patch-plan.json
Apply patches
while IFS= read -r package; do echo "Updating $package..."
Try to apply fix
npm audit fix --package-lock-only --package=$package
Run tests
if npm test; then echo "✅ Tests passed for $package" git add package.json package-lock.json git commit -m "security: patch $package vulnerability" else echo "❌ Tests failed for $package - reverting" git checkout package.json package-lock.json fi done < <(jq -r '.packages[]' patch-plan.json)
echo "✅ Patches applied"
CI Vulnerability Gating
.github/workflows/security-audit.yml
name: Security Audit
on: pull_request: schedule: - cron: "0 0 * * *" # Daily
jobs: audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install dependencies
run: npm ci
- name: Run npm audit
run: npm audit --audit-level=moderate
continue-on-error: true
- name: Run Snyk test
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
- name: Generate patch plan
if: failure()
run: npm run generate-patch-plan
- name: Comment PR
if: failure() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const plan = fs.readFileSync('patch-plan.md', 'utf8');
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: plan
});
Patch Rollback Strategy
Vulnerability Patch Rollback Plan
Before Patching
- Create rollback tag
git tag -a pre-security-patch-$(date +%Y%m%d) -m "Pre-patch checkpoint"
Document current versions
npm list --depth=0 > versions-before.txt
Run baseline tests
npm test > test-results-before.txt
Rollback Steps
If issues detected after patching:
Immediate revert
git revert HEAD git push origin main
Redeploy previous version
git checkout pre-security-patch-$(date +%Y%m%d) npm ci npm run build npm run deploy
Verify rollback
npm test npm run smoke-tests
Incident report
-
Document what failed
-
Update patch plan with new risk factors
-
Schedule retry with additional testing
Best Practices
- Triage weekly: Review new vulnerabilities
- Prioritize by impact: Not just severity score
- Test before merging: Automated + manual testing
- Stage deployments: Dev → Staging → Production
- Monitor after patch: Watch error rates
- Document breaking changes: Migration guides
- Keep dependencies updated: Reduce vulnerability surface
Output Checklist
- Severity matrix defined
- Audit parser implemented
- Patch plan generated
- Safe upgrade order determined
- Risk assessment completed
- Breaking changes identified
- Automated patch script
- CI vulnerability gating
- Rollback strategy documented
- Team notified of critical patches