Skip to content

Threat intelligence & enrichment

Once a CVE has been matched to installed software, the engine enriches it with threat intelligence, scores it into a 0–100 risk number and priority, and produces remediation guidance (recommendation, urgency, SLA). Three packages do this work:

  • intelligence/ — the multi-source enrichment aggregator.
  • research/ — direct CVE lookup across NVD, CVE.org and CISA KEV.
  • scoring/ + analysis/ — risk scoring and portfolio/vendor analysis.
  • remediation/ — the finding lifecycle, SLA tracking, and metrics.

Two enrichment paths

The engine has two complementary ways to pull CVE data.

research.CVEResearcher — direct lookup

research/cve_lookup.py is the lightweight researcher used for interactive lookups (vulnmgmt vuln lookup, comprehensive_search). It talks to three free sources directly:

SourceMethodWhat it returns
NIST NVDsearch_nvd()CVSS v3.1→v3.0→v2 (first available), description, references, affected CPEs + version ranges
CVE.org / MITRElookup_cve_org()Authoritative record from cveawg.mitre.org — description, affected products, CVSS v3.1
CISA KEVsearch_cisa_kev()Whether the CVE is in the Known Exploited Vulnerabilities catalog → sets is_exploited=True

Highlights of the implementation:

  • NVD search strategy picks the most precise query available, in order: exact cveIdvirtualMatchString (a CPE vendor:product:version, letting NVD do server-side version-range matching) → keywordSearch fallback.
  • Rate limiting is automatic. Without an NVD_API_KEY the delay is 6.0s (5 req/30s); with a key it’s 0.6s (50 req/30s). A sliding-window limiter plus a single 30s back-off on 403 keep it under NVD’s cap.
  • get_research_urls() builds human links (NVD, MITRE, CVE.org, GitHub CVE JSON, Exploit-DB, X-Force) that end up in the report’s Research Sources sheet.

intelligence.ThreatIntelligenceAggregator — full enrichment

intelligence/aggregator.py is the heavy pipeline used by scan/pipeline. It fans out across the integration clients and merges everything into one EnrichedCVE dataclass.

agg = ThreatIntelligenceAggregator(api_keys, enabled_sources=["otx", "cve_awg", "nvd"])
enriched = agg.enrich_batch(cve_matches, max_workers=5) # parallel, ThreadPoolExecutor

Source selection. enabled_sources defaults to the CVE-specific feeds (otx, cve_awg, nvd). The aggregator splits feeds into two groups:

  • CVE sourcesotx, mitre, cve_awg, nvd — driven by CVE id.
  • IOC sourcesvirustotal, shodan, greynoise, abuseipdb, urlhaus — driven by IPs/URLs/hashes. During CVE enrichment these are initialized but recorded as not_applicable, so a CVE run doesn’t waste time (or rate limit) on them.

From the CLI you pick sources explicitly:

Terminal window
# Default (CVE feeds only): otx + mitre
vulnmgmt vuln lookup --app "Foxit Reader" --version "2024" --enrich
# Fastest, most complete CVE data
vulnmgmt vuln scan apps.json --enrich --sources otx -o results.json
# CVE data + adversary TTPs
vulnmgmt vuln pipeline apps.json --sources otx --sources mitre -o results.json

What each source contributes to EnrichedCVE:

SourceFields populated
CISA KEVis_exploited, kev_due_date
OTXotx_cvss_v2/v3, otx_cwe, otx_references, otx_targeted_products, otx_pulse_count, otx_threat_actors, otx_malware_families, otx_tags
CVE AWGcve_awg_references, cve_awg_affected_products, cve_awg_problem_types, cve_awg_assigner, cve_awg_date_published
NVDnvd_cvss_v2/v30/v31_detailed, nvd_references, nvd_weaknesses, nvd_cpe_matches, plus nvd_cpe_matched (CPE matched to the app with a confidence score)
MITRE ATT&CKmitre_techniques, mitre_tactics
EPSSepss_score, epss_percentile, epss_date
VirusTotal / GreyNoise / Shodan / AbuseIPDB / URLhausIOC fields (vt_malicious_samples, grey_scanner_activity, shodan_exposed_hosts, abuse_reports, urlhaus_malware_urls) — used for scoring inputs when present

Every EnrichedCVE also records provenance: sources_checked, sources_succeeded, sources_failed, and a per-source enrichment_details block. This is what powers the Enrichment Details report sheet and makes a failed feed visible rather than silent.

Performance note (Jan 2026). OTX enrichment used to make a separate pulse search that timed out (30–127s per CVE). Pulse data is already inside the CVE-details response, so that call was removed — a ~100× speedup to ~1–2s per CVE. Prefer --sources otx for scans; avoid IOC feeds for CVE enrichment.

Free vs. paid sources

The engine is designed to run on free intelligence. The reference notes (FREE_THREAT_INTEL_APIS.md, ENRICHMENT_SOURCES.md) split them as:

  • Tier 1 – completely free: NVD (5 req/30s free, 50 with a free key), MITRE ATT&CK, CISA KEV, EPSS, URLhaus, PhishTank.
  • Tier 2 – free tier with limits: AlienVault OTX (generous), VirusTotal (4/min, 500/day), Shodan (~100/mo), GreyNoise (~5,000/mo), AbuseIPDB (1,000/day).
  • Paid: IBM X-Force (Standard tier / $99+ mo — Freemium blocks the API), and other commercial feeds (Recorded Future, ReversingLabs, …) not integrated.

The single required key for good CVE enrichment is OTX_API_KEY; NVD_API_KEY is optional but strongly recommended for throughput. Keys are read from .env.

Scoring: from enriched CVE to risk

scoring/risk_engine.py (RiskScoringEngine) converts each EnrichedCVE into a ScoredCVE with a 0–100 risk_score and a Priority. The score is a weighted sum of six components:

ComponentWeightInput
CVSS base score20%cvss_score / 10 × 100
EPSS15%epss_score × 100 (probability of exploitation)
Active exploitation25%100 if KEV-exploited, 75 if GreyNoise scanner activity, 50 if OTX pulses > 10
Threat-actor activity15%min(#OTX threat actors × 25, 100)
Malware association15%min(#malware families × 20, 100), +30 VirusTotal, +20 URLhaus
Asset exposure10%tiered by asset_count (1/10/50/100+), +25 if Shodan-exposed

Priority is derived from the score (≥80 CRITICAL, ≥60 HIGH, ≥40 MEDIUM, ≥20 LOW, else INFO) — with one override: an actively exploited CVE with CVSS ≥ 9.0 is always CRITICAL. The engine also emits a recommendation string and a remediation_urgency timeline directly off the priority and exploitation signals.

There are two other, simpler scorers used elsewhere:

  • analysis/risk_scoring.py (RiskAnalyzer) scores applications (not single CVEs) — counting critical/high/medium/low + exploited vulns, weighting by a device-count exposure multiplier (core.models.RiskScore.calculate_risk_score), and producing per-app recommendations and vendor-risk rollups (DeepAnalyzer.analyze_vendor_risk, generate_executive_summary).
  • output.core.models.RiskMetrics.from_findings() computes the report-level severity roll-up and overall risk level shown on report cover pages.

Remediation guidance

Remediation output has two layers: the guidance text produced during scoring, and the lifecycle tracking in remediation/.

Guidance text

RiskScoringEngine._generate_recommendation() / _calculate_urgency() produce the human guidance that lands in reports and tickets. Examples:

PriorityRecommendationUrgency
CRITICAL (exploited)“PATCH IMMEDIATELY - Actively exploited in the wild”IMMEDIATE (0–24 hours)
CRITICAL (high EPSS)“URGENT: Patch within 24 hours - N% exploitation probability”24–48 hours
HIGH”Patch within 7 days - …” (cites EPSS % or named threat actors)7 days
MEDIUM”Patch within 30 days - Medium risk”30 days
LOW”Patch within 90 days - Low risk”90 days

Lifecycle & SLA — remediation/workflow.py

RemediationWorkflow is a state machine over a VulnerabilityFinding (a CVE on a specific asset). States: DISCOVERED → ASSESSED → ASSIGNED → IN_PROGRESS → VERIFIED → CLOSED, plus ACCEPTED_RISK, DEFERRED, EXCEPTION, REOPENED. VALID_TRANSITIONS enforces legal moves and every change appends a StateTransition (who/when/why) to the finding’s history; timestamps (assessed_at, remediated_at, …) are stamped automatically.

SLA targets are priority-driven:

PrioritySLA (days)
CRITICAL3
HIGH7
MEDIUM30
LOW90

calculate_sla_due_date() sets the due date; check_sla_breach() flags an open finding past due. Findings also carry cw_ticket_id / jira_ticket_id so a finding links back to the ticket created via ConnectWise Manage.

Metrics — remediation/metrics.py

SLATracker rolls a set of findings into a RemediationMetrics report:

  • Time metrics — MTTR (mean/median time to remediate), time-to-assign, time-to-close.
  • SLA compliance — compliance rate, breach rate, within-SLA count.
  • Distributions — open counts by priority and by state.
  • Operational views — aging report (get_aging_report), SLAs at risk (get_sla_at_risk), overdue findings, and per-assignee workload.