skills/patricio0312rev/skills/dependency-vulnerability-triage

dependency-vulnerability-triage

SKILL.md

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

1. **Create rollback tag**
   ```bash
   git tag -a pre-security-patch-$(date +%Y%m%d) -m "Pre-patch checkpoint"
   ```
  1. Document current versions

    npm list --depth=0 > versions-before.txt
    
  2. Run baseline tests

    npm test > test-results-before.txt
    

Rollback Steps

If issues detected after patching:

  1. Immediate revert

    git revert HEAD
    git push origin main
    
  2. Redeploy previous version

    git checkout pre-security-patch-$(date +%Y%m%d)
    npm ci
    npm run build
    npm run deploy
    
  3. Verify rollback

    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
Weekly Installs
10
First Seen
10 days ago
Installed on
claude-code8
gemini-cli7
antigravity7
windsurf7
github-copilot7
codex7