# EPSS Integration Guide ## Overview EPSS (Exploit Prediction Scoring System) is a FIRST.org initiative that provides probability scores for vulnerability exploitation within 30 days. StellaOps integrates EPSS as a risk signal alongside CVSS and KEV (Known Exploited Vulnerabilities) to provide more accurate vulnerability prioritization. ## How EPSS Works EPSS uses machine learning to predict the probability that a CVE will be exploited in the wild within the next 30 days. The model considers: - Vulnerability characteristics (CVSS metrics, CWE, etc.) - Social signals (Twitter mentions, GitHub issues, etc.) - Exploit database entries - Historical exploitation patterns EPSS outputs two values: - **Score** (0.0-1.0): Probability of exploitation in next 30 days - **Percentile** (0-100): Ranking relative to all other CVEs ## How EPSS Affects Risk Scoring in StellaOps ### Combined Risk Formula StellaOps combines CVSS, KEV, and EPSS signals into a unified risk score: ``` risk_score = clamp01( (cvss / 10) + # Base severity (0-1) kevBonus + # +0.20 if in CISA KEV epssBonus # +0.02 to +0.10 based on percentile ) ``` ### EPSS Bonus Thresholds | EPSS Percentile | Bonus | Rationale | |-----------------|-------|-----------| | >= 99th | +10% | Top 1% most likely to be exploited; urgent priority | | >= 90th | +5% | Top 10%; high exploitation probability | | >= 50th | +2% | Above median; moderate additional risk | | < 50th | 0% | Below median; no bonus applied | ### Example Calculations | CVE | CVSS | KEV | EPSS Percentile | Risk Score | |-----|------|-----|-----------------|------------| | CVE-2024-1234 | 9.8 | Yes | 99.5th | 1.00 (clamped) | | CVE-2024-5678 | 7.5 | No | 95th | 0.80 | | CVE-2024-9012 | 6.0 | No | 60th | 0.62 | | CVE-2024-3456 | 8.0 | No | 30th | 0.80 | ## Implementation Reference ### IEpssSource Interface ```csharp // Location: src/RiskEngine/StellaOps.RiskEngine/StellaOps.RiskEngine.Core/Providers/IEpssSources.cs public interface IEpssSource { /// /// Returns EPSS data for the given CVE identifier, or null if unknown. /// Task GetEpssAsync(string cveId, CancellationToken cancellationToken); } public sealed record EpssData(double Score, double Percentile, DateTimeOffset? ModelVersion = null); ``` ### Risk Providers **EpssProvider** - Uses EPSS score directly as risk (0.0-1.0): ```csharp // Location: src/RiskEngine/StellaOps.RiskEngine/StellaOps.RiskEngine.Core/Providers/EpssProvider.cs public const string ProviderName = "epss"; ``` **CvssKevEpssProvider** - Combined provider using all three signals: ```csharp // Location: src/RiskEngine/StellaOps.RiskEngine/StellaOps.RiskEngine.Core/Providers/EpssProvider.cs public const string ProviderName = "cvss-kev-epss"; ``` ## Policy Configuration ### Enabling EPSS Integration ```yaml # etc/risk-engine.yaml risk: providers: - name: cvss-kev-epss enabled: true priority: 1 epss: enabled: true source: database # or "api" for live FIRST API cache_ttl: 24h # Percentile-based bonus thresholds thresholds: - percentile: 99 bonus: 0.10 - percentile: 90 bonus: 0.05 - percentile: 50 bonus: 0.02 ``` ### Custom Threshold Configuration Organizations can customize EPSS bonus thresholds based on their risk tolerance: ```yaml # More aggressive (higher bonuses for high-risk vulns) epss: thresholds: - percentile: 99 bonus: 0.15 - percentile: 95 bonus: 0.10 - percentile: 75 bonus: 0.05 # More conservative (smaller bonuses) epss: thresholds: - percentile: 99 bonus: 0.05 - percentile: 95 bonus: 0.02 ``` ## EPSS in Lattice Decisions EPSS influences VEX lattice state transitions for vulnerability triage: | Current State | EPSS >= 90th Percentile | Recommended Action | |---------------|-------------------------|-------------------| | SR (Static Reachable) | Yes | Escalate to CR (Confirmed Reachable) priority | | SU (Static Unreachable) | Yes | Flag for review - high exploit probability despite unreachable | | DV (Denied by Vendor VEX) | Yes | Review denial validity - exploit activity contradicts vendor | | U (Unknown) | Yes | Prioritize for reachability analysis | ### VEX Policy Example ```yaml # etc/vex-policy.yaml lattice: transitions: - from: SR to: CR condition: epss_percentile: ">= 90" action: auto_escalate - from: SU to: REVIEW condition: epss_percentile: ">= 95" action: flag_for_review reason: "High EPSS despite static unreachability" ``` ## Offline EPSS Data EPSS data is included in offline risk bundles for air-gapped environments. ### Bundle Structure ``` risk-bundle-2025-12-14/ ├── manifest.json ├── kev/ │ └── kev-catalog.json ├── epss/ │ ├── epss-scores.csv.zst # Compressed EPSS data │ └── epss-metadata.json # Model date, row count, checksum └── signatures/ └── bundle.dsse.json ``` ### EPSS Metadata ```json { "model_date": "2025-12-14", "row_count": 248732, "sha256": "abc123...", "source": "first.org", "created_at": "2025-12-14T00:00:00Z" } ``` ### Importing Offline EPSS Data ```bash # Import risk bundle (includes EPSS) stellaops offline import --kit risk-bundle-2025-12-14.tar.zst # Verify EPSS data imported stellaops epss status # Output: # EPSS Data Status: # Model Date: 2025-12-14 # CVE Count: 248,732 # Last Import: 2025-12-14T10:30:00Z ``` ## Accuracy Considerations | Metric | Value | Notes | |--------|-------|-------| | EPSS Coverage | ~95% of NVD CVEs | Some very new CVEs (<24h) not yet scored | | Model Refresh | Daily | Scores can change day-to-day | | Prediction Window | 30 days | Probability of exploit in next 30 days | | Historical Accuracy | ~85% AUC | Based on FIRST published evaluations | ### Limitations 1. **New CVEs**: Very recent CVEs may not have EPSS scores yet 2. **Model Lag**: EPSS model updates daily; real-world exploit activity may be faster 3. **Zero-Days**: Pre-disclosure vulnerabilities cannot be scored 4. **Context Blind**: EPSS doesn't consider your specific environment ## Best Practices 1. **Combine Signals**: Always use EPSS alongside CVSS and KEV, not in isolation 2. **Review High EPSS**: Manually review vulnerabilities with EPSS >= 95th percentile 3. **Track Changes**: Monitor EPSS score changes over time for trending threats 4. **Update Regularly**: Keep EPSS data fresh (daily in online mode, weekly for offline) 5. **Verify High-Risk**: For critical decisions, verify EPSS data against FIRST API ## API Usage ### Query EPSS Score ```bash # Get EPSS score for a specific CVE stellaops epss get CVE-2024-12345 # Batch query stellaops epss batch --file cves.txt --output epss-scores.json ``` ### Programmatic Access ```csharp // Using IEpssSource var epssData = await epssSource.GetEpssAsync("CVE-2024-12345", cancellationToken); if (epssData is not null) { Console.WriteLine($"Score: {epssData.Score:P2}"); Console.WriteLine($"Percentile: {epssData.Percentile:F1}th"); } ``` ## Troubleshooting ### EPSS Data Not Available ```bash # Check EPSS source status stellaops epss status # Force refresh from FIRST API stellaops epss refresh --force # Check for specific CVE stellaops epss get CVE-2024-12345 --verbose ``` ### Stale EPSS Data If EPSS data is older than 7 days: ```bash # Check staleness stellaops epss check-staleness # Import fresh bundle stellaops offline import --kit latest-bundle.tar.zst ``` ## References - [FIRST EPSS Model](https://www.first.org/epss/) - [EPSS API Documentation](https://www.first.org/epss/api) - [EPSS FAQ](https://www.first.org/epss/faq) - [StellaOps Risk Engine Architecture](../modules/risk-engine/architecture.md)