Files
mukul975 c21af3347e Complete folder anatomy for all 649 cybersecurity skills + update LICENSE to Mahipal
- Add scripts/agent.py and references/api-reference.md to all remaining skills
- Update all 648 LICENSE files: copyright now reads 'Mahipal'
- Add implementing-security-monitoring-with-datadog (new skill with full anatomy)
- All 649 skills now have: SKILL.md, LICENSE, scripts/agent.py, references/api-reference.md
2026-03-11 00:22:12 +01:00

220 lines
8.1 KiB
Python

#!/usr/bin/env python3
"""Agent for triaging vulnerabilities with the SSVC framework.
Implements CISA's Stakeholder-Specific Vulnerability Categorization
decision tree to produce actionable priorities: Track, Track*,
Attend, or Act based on exploitation status, technical impact,
automatability, and mission prevalence.
"""
import json
import sys
from pathlib import Path
from datetime import datetime
try:
import requests
except ImportError:
requests = None
class ExploitationStatus:
NONE = "none"
POC = "poc"
ACTIVE = "active"
class TechnicalImpact:
PARTIAL = "partial"
TOTAL = "total"
class Automatability:
NO = "no"
YES = "yes"
class MissionPrevalence:
MINIMAL = "minimal"
SUPPORT = "support"
ESSENTIAL = "essential"
class SSVCDecision:
TRACK = "Track"
TRACK_STAR = "Track*"
ATTEND = "Attend"
ACT = "Act"
SSVC_DECISION_TREE = {
(ExploitationStatus.ACTIVE, TechnicalImpact.TOTAL): SSVCDecision.ACT,
(ExploitationStatus.ACTIVE, TechnicalImpact.PARTIAL, Automatability.YES): SSVCDecision.ACT,
(ExploitationStatus.ACTIVE, TechnicalImpact.PARTIAL, Automatability.NO, MissionPrevalence.ESSENTIAL): SSVCDecision.ACT,
(ExploitationStatus.ACTIVE, TechnicalImpact.PARTIAL, Automatability.NO, MissionPrevalence.SUPPORT): SSVCDecision.ATTEND,
(ExploitationStatus.ACTIVE, TechnicalImpact.PARTIAL, Automatability.NO, MissionPrevalence.MINIMAL): SSVCDecision.ATTEND,
(ExploitationStatus.POC, TechnicalImpact.TOTAL, Automatability.YES): SSVCDecision.ATTEND,
(ExploitationStatus.POC, TechnicalImpact.TOTAL, Automatability.NO): SSVCDecision.TRACK_STAR,
(ExploitationStatus.POC, TechnicalImpact.PARTIAL): SSVCDecision.TRACK_STAR,
(ExploitationStatus.NONE, TechnicalImpact.TOTAL): SSVCDecision.TRACK_STAR,
(ExploitationStatus.NONE, TechnicalImpact.PARTIAL): SSVCDecision.TRACK,
}
class SSVCTriageAgent:
"""Triages vulnerabilities using the SSVC decision tree."""
def __init__(self, output_dir="./ssvc_triage"):
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
self.results = []
def check_cisa_kev(self, cve_id):
"""Check if CVE is in CISA Known Exploited Vulnerabilities catalog."""
if not requests:
return None
resp = None
try:
resp = requests.get(
"https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json",
timeout=15,
)
except Exception:
return None
if resp and resp.status_code == 200:
data = resp.json()
for vuln in data.get("vulnerabilities", []):
if vuln.get("cveID") == cve_id:
return {
"in_kev": True,
"vendor": vuln.get("vendorProject"),
"product": vuln.get("product"),
"date_added": vuln.get("dateAdded"),
"due_date": vuln.get("dueDate"),
}
return {"in_kev": False}
def get_epss_score(self, cve_id):
"""Get EPSS probability score from FIRST API."""
if not requests:
return None
try:
resp = requests.get(f"https://api.first.org/data/v1/epss?cve={cve_id}", timeout=10)
if resp and resp.status_code == 200:
data = resp.json().get("data", [])
if data:
return {
"cve": cve_id,
"epss": float(data[0].get("epss", 0)),
"percentile": float(data[0].get("percentile", 0)),
}
except Exception:
pass
return None
def determine_exploitation(self, cve_id):
"""Determine exploitation status using KEV and EPSS."""
kev = self.check_cisa_kev(cve_id)
if kev and kev.get("in_kev"):
return ExploitationStatus.ACTIVE, kev
epss = self.get_epss_score(cve_id)
if epss and epss.get("epss", 0) > 0.5:
return ExploitationStatus.POC, epss
if epss and epss.get("epss", 0) > 0.1:
return ExploitationStatus.POC, epss
return ExploitationStatus.NONE, epss
def evaluate_decision(self, exploitation, technical_impact, automatability=None,
mission_prevalence=None):
"""Walk the SSVC decision tree to produce a prioritization."""
if (exploitation, technical_impact) in SSVC_DECISION_TREE:
return SSVC_DECISION_TREE[(exploitation, technical_impact)]
if automatability:
key = (exploitation, technical_impact, automatability)
if key in SSVC_DECISION_TREE:
return SSVC_DECISION_TREE[key]
if mission_prevalence:
key = (exploitation, technical_impact, automatability, mission_prevalence)
if key in SSVC_DECISION_TREE:
return SSVC_DECISION_TREE[key]
return SSVCDecision.TRACK
def triage_cve(self, cve_id, technical_impact=TechnicalImpact.PARTIAL,
automatability=Automatability.NO,
mission_prevalence=MissionPrevalence.SUPPORT):
"""Full SSVC triage for a single CVE."""
exploitation, enrichment = self.determine_exploitation(cve_id)
decision = self.evaluate_decision(exploitation, technical_impact,
automatability, mission_prevalence)
result = {
"cve_id": cve_id,
"exploitation_status": exploitation,
"technical_impact": technical_impact,
"automatability": automatability,
"mission_prevalence": mission_prevalence,
"decision": decision,
"enrichment": enrichment,
"remediation_timeline": self._get_timeline(decision),
}
self.results.append(result)
return result
def _get_timeline(self, decision):
timelines = {
SSVCDecision.ACT: "Immediate - remediate within 24-48 hours",
SSVCDecision.ATTEND: "Urgent - remediate within 1-2 weeks",
SSVCDecision.TRACK_STAR: "Scheduled - remediate in next patch cycle",
SSVCDecision.TRACK: "Monitor - include in regular vulnerability management",
}
return timelines.get(decision, "Unknown")
def triage_batch(self, cves, defaults=None):
"""Triage a list of CVEs with optional default parameters."""
defaults = defaults or {}
for cve in cves:
self.triage_cve(
cve,
technical_impact=defaults.get("technical_impact", TechnicalImpact.PARTIAL),
automatability=defaults.get("automatability", Automatability.NO),
mission_prevalence=defaults.get("mission_prevalence", MissionPrevalence.SUPPORT),
)
return self.results
def generate_report(self, cves=None):
if cves:
self.triage_batch(cves)
by_decision = {}
for r in self.results:
d = r["decision"]
by_decision[d] = by_decision.get(d, 0) + 1
report = {
"report_date": datetime.utcnow().isoformat(),
"framework": "SSVC (CISA Stakeholder-Specific Vulnerability Categorization)",
"total_triaged": len(self.results),
"by_decision": by_decision,
"triage_results": self.results,
}
out = self.output_dir / "ssvc_triage_report.json"
with open(out, "w") as f:
json.dump(report, f, indent=2)
print(json.dumps(report, indent=2))
return report
def main():
if len(sys.argv) < 2:
print("Usage: agent.py <CVE-ID> [CVE-ID2 ...] [--impact total|partial]")
sys.exit(1)
cves = [a for a in sys.argv[1:] if a.startswith("CVE-")]
impact = TechnicalImpact.PARTIAL
if "--impact" in sys.argv:
val = sys.argv[sys.argv.index("--impact") + 1]
impact = TechnicalImpact.TOTAL if val == "total" else TechnicalImpact.PARTIAL
agent = SSVCTriageAgent()
agent.generate_report(cves)
if __name__ == "__main__":
main()