--- name: dependency-vulnerability-triage description: Turns npm audit/Snyk results into prioritized patch plans with severity assessment, safe upgrade paths, breaking change analysis, and rollback strategies. Use for "dependency security", "vulnerability patching", "npm audit", or "security updates". --- # Dependency Vulnerability Triage Convert vulnerability reports into actionable patch plans. ## Vulnerability Severity Matrix ```typescript // 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 ```typescript // scripts/parse-audit.ts import { execSync } from "child_process"; interface NpmAuditResult { vulnerabilities: Record; 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 ```typescript // 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 ```typescript // 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 ```typescript 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 ```bash #!/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 ```yaml # .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 ````markdown ## Vulnerability Patch Rollback Plan ### Before Patching 1. **Create rollback tag** ```bash git tag -a pre-security-patch-$(date +%Y%m%d) -m "Pre-patch checkpoint" ``` ```` 2. **Document current versions** ```bash npm list --depth=0 > versions-before.txt ``` 3. **Run baseline tests** ```bash npm test > test-results-before.txt ``` ### Rollback Steps If issues detected after patching: 1. **Immediate revert** ```bash git revert HEAD git push origin main ``` 2. **Redeploy previous version** ```bash git checkout pre-security-patch-$(date +%Y%m%d) npm ci npm run build npm run deploy ``` 3. **Verify rollback** ```bash npm test npm run smoke-tests ``` 4. **Incident report** - Document what failed - Update patch plan with new risk factors - Schedule retry with additional testing ``` ## Best Practices 1. **Triage weekly**: Review new vulnerabilities 2. **Prioritize by impact**: Not just severity score 3. **Test before merging**: Automated + manual testing 4. **Stage deployments**: Dev → Staging → Production 5. **Monitor after patch**: Watch error rates 6. **Document breaking changes**: Migration guides 7. **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 ```