Files
git.stella-ops.org/docs/guides/epss-integration.md
StellaOps Bot b058dbe031 up
2025-12-14 23:20:14 +02:00

7.7 KiB

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

// Location: src/RiskEngine/StellaOps.RiskEngine/StellaOps.RiskEngine.Core/Providers/IEpssSources.cs

public interface IEpssSource
{
    /// <summary>
    /// Returns EPSS data for the given CVE identifier, or null if unknown.
    /// </summary>
    Task<EpssData?> 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):

// Location: src/RiskEngine/StellaOps.RiskEngine/StellaOps.RiskEngine.Core/Providers/EpssProvider.cs
public const string ProviderName = "epss";

CvssKevEpssProvider - Combined provider using all three signals:

// Location: src/RiskEngine/StellaOps.RiskEngine/StellaOps.RiskEngine.Core/Providers/EpssProvider.cs
public const string ProviderName = "cvss-kev-epss";

Policy Configuration

Enabling EPSS Integration

# 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:

# 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

# 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

{
  "model_date": "2025-12-14",
  "row_count": 248732,
  "sha256": "abc123...",
  "source": "first.org",
  "created_at": "2025-12-14T00:00:00Z"
}

Importing Offline EPSS Data

# 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

# 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

// 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

# 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:

# Check staleness
stellaops epss check-staleness

# Import fresh bundle
stellaops offline import --kit latest-bundle.tar.zst

References