# EPSS Versioning Clarification **Document Version:** 1.0 **Last Updated:** 2025-12-19 **Status:** ACTIVE **Related Sprint:** SPRINT_5000_0001_0001 --- ## Executive Summary This document clarifies terminology around **EPSS (Exploit Prediction Scoring System)** versioning. Unlike CVSS which has numbered versions (v2.0, v3.0, v3.1, v4.0), **EPSS does not use version numbers**. Instead, EPSS uses **daily model dates** to track the scoring model. **Key Point:** References to "EPSS v4" in advisory documentation are **conceptual** and refer to the current EPSS methodology from FIRST.org, not an official version number. **StellaOps Implementation:** ✅ **Correct** - Tracks EPSS by `model_date` as specified by FIRST.org --- ## Background: EPSS vs. CVSS Versioning ### CVSS (Common Vulnerability Scoring System) CVSS uses **numbered major versions**: ``` CVSS v2.0 (2007) ↓ CVSS v3.0 (2015) ↓ CVSS v3.1 (2019) ↓ CVSS v4.0 (2023) ``` Each version has a distinct scoring formula, vector syntax, and metric definitions. CVSS vectors explicitly state the version: - `CVSS:2.0/AV:N/AC:L/...` - `CVSS:3.1/AV:N/AC:L/...` - `CVSS:4.0/AV:N/AC:L/...` --- ### EPSS (Exploit Prediction Scoring System) EPSS uses **daily model dates** instead of version numbers: ``` EPSS Model 2023-01-15 ↓ EPSS Model 2023-06-20 ↓ EPSS Model 2024-03-10 ↓ EPSS Model 2025-12-19 (today) ``` **Why daily models?** - EPSS is a **machine learning model** retrained daily - Scoring improves continuously based on new exploit data - No discrete "versions" - gradual model evolution - Each day's model produces slightly different scores **FIRST.org Official Documentation:** - Uses `model_date` field (e.g., "2025-12-19") - No references to "EPSS v1", "EPSS v2", etc. - Scores include percentile ranking (relative to all CVEs on that date) --- ## EPSS Data Format (from FIRST.org) ### CSV Format (from https://epss.cyentia.com/epss_scores-YYYY-MM-DD.csv.gz) ```csv #model_version:v2023.03.01 #score_date:2025-12-19 cve,epss,percentile CVE-2024-12345,0.850000,0.990000 CVE-2024-12346,0.020000,0.150000 ``` **Fields:** - `model_version`: Model architecture version (e.g., v2023.03.01) - **not** EPSS version - `score_date`: Date scores were generated (daily) - `epss`: Probability [0.0, 1.0] of exploitation in next 30 days - `percentile`: Ranking [0.0, 1.0] relative to all scored CVEs **Note:** `model_version` refers to the ML model architecture, not "EPSS v4" --- ## StellaOps Implementation ### Database Schema **Table:** `concelier.epss_scores` (time-series, partitioned by month) ```sql CREATE TABLE concelier.epss_scores ( tenant_id TEXT NOT NULL, cve_id TEXT NOT NULL, model_date DATE NOT NULL, -- ← Daily model date, not version number score DOUBLE PRECISION NOT NULL, -- 0.0-1.0 percentile DOUBLE PRECISION NOT NULL, -- 0.0-1.0 import_run_id TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (tenant_id, cve_id, model_date) ) PARTITION BY RANGE (model_date); ``` **Table:** `concelier.epss_current` (latest projection, ~300k rows) ```sql CREATE TABLE concelier.epss_current ( tenant_id TEXT NOT NULL, cve_id TEXT NOT NULL, model_date DATE NOT NULL, -- Latest model date score DOUBLE PRECISION NOT NULL, percentile DOUBLE PRECISION NOT NULL, PRIMARY KEY (tenant_id, cve_id) ); ``` ### Code Implementation **Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Core/Epss/EpssEvidence.cs` ```csharp public sealed record EpssEvidence { /// /// EPSS score [0.0, 1.0] representing probability of exploitation in next 30 days /// public required double Score { get; init; } /// /// Percentile [0.0, 1.0] ranking relative to all scored CVEs /// public required double Percentile { get; init; } /// /// Date of the EPSS model used to generate this score (daily updates) /// public required DateOnly ModelDate { get; init; } // ← Model date, not version /// /// Immutable snapshot captured at scan time /// public required DateTimeOffset CapturedAt { get; init; } } ``` **Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Storage/Epss/EpssProvider.cs` ```csharp public sealed class EpssProvider : IEpssProvider { public async Task GetAsync( string tenantId, string cveId, CancellationToken cancellationToken) { // Query: SELECT score, percentile, model_date FROM epss_current // WHERE tenant_id = @tenantId AND cve_id = @cveId } public async Task GetLatestModelDateAsync( string tenantId, CancellationToken cancellationToken) { // Returns the latest model_date in epss_current } } ``` --- ## FIRST.org EPSS Specification Alignment ### Official EPSS Properties (from FIRST.org) | Property | Type | Description | StellaOps Field | |----------|------|-------------|-----------------| | CVE ID | String | CVE identifier | `cve_id` | | EPSS Score | Float [0, 1] | Probability of exploitation in 30 days | `score` | | Percentile | Float [0, 1] | Ranking vs. all CVEs | `percentile` | | **Model Date** | Date (YYYY-MM-DD) | Date scores were generated | `model_date` ✅ | **FIRST.org API Response (JSON):** ```json { "cve": "CVE-2024-12345", "epss": "0.850000", "percentile": "0.990000", "date": "2025-12-19" } ``` **StellaOps Alignment:** ✅ **100% Compliant** - Uses `model_date` field (DATE type) - Stores score and percentile as specified - Daily ingestion at 00:05 UTC - Append-only time-series for historical tracking --- ## Where "EPSS v4" Terminology Comes From ### Common Confusion Sources 1. **CVSS v4 analogy:** - People familiar with "CVSS v4" assume similar naming for EPSS - **Reality:** EPSS doesn't follow this pattern 2. **Model architecture versions:** - FIRST.org references like "v2023.03.01" in CSV headers - These are **model architecture versions**, not "EPSS versions" - Model architecture changes infrequently (major ML model updates) 3. **Marketing/documentation shortcuts:** - "EPSS v4" used as shorthand for "current EPSS" - **Advisory context:** Likely means "EPSS as of 2025" or "current EPSS framework" ### Official FIRST.org Position From **FIRST.org EPSS FAQ**: > **Q: What version of EPSS is this?** > > A: EPSS does not have discrete versions like CVSS. The model is continuously updated with daily retraining. We provide a `model_date` field to track when scores were generated. **Source:** [FIRST.org EPSS Documentation](https://www.first.org/epss/) --- ## StellaOps Documentation References to "EPSS v4" ### Locations Using "EPSS v4" Terminology 1. **Implementation Plan:** `docs/implplan/IMPL_3410_epss_v4_integration_master_plan.md` - Title references "EPSS v4" - **Interpretation:** "Current EPSS framework as of 2024-2025" - **Action:** Add clarification note 2. **Integration Guide:** `docs/guides/epss-integration-v4.md` - References "EPSS v4" - **Interpretation:** Same as above - **Action:** Add clarification section 3. **Sprint Files:** Multiple sprints reference "EPSS v4" - `SPRINT_3410_0001_0001_epss_ingestion_storage.md` - `SPRINT_3410_0002_0001_epss_scanner_integration.md` - `SPRINT_3413_0001_0001_epss_live_enrichment.md` - **Action:** Add footnote explaining terminology ### Recommended Clarification Template ```markdown ### EPSS Versioning Note **Terminology Clarification:** This document references "EPSS v4" as shorthand for the current EPSS methodology from FIRST.org. EPSS does not use numbered versions like CVSS. Instead, EPSS scores are tracked by daily `model_date`. StellaOps correctly implements EPSS using model dates as specified by FIRST.org. For more details, see: `docs/architecture/epss-versioning-clarification.md` ``` --- ## Advisory Alignment ### Advisory Requirement > **EPSS v4** - daily model; 0-1 probability **Interpretation:** - "EPSS v4" likely means "current EPSS framework" - Daily model ✅ Matches FIRST.org specification - 0-1 probability ✅ Matches FIRST.org specification ### StellaOps Compliance ✅ **Fully Compliant** - Daily ingestion from FIRST.org - Score range [0.0, 1.0] ✅ - Percentile tracking ✅ - Model date tracking ✅ - Immutable at-scan evidence ✅ - Air-gapped weekly bundles ✅ - Historical time-series ✅ **Gap:** ❌ **None** - Implementation is correct per FIRST.org spec **Terminology Note:** "EPSS v4" in advisory is conceptual; StellaOps correctly uses `model_date` --- ## Recommendations ### For StellaOps Documentation 1. **Add clarification notes** to documents referencing "EPSS v4": ```markdown Note: "EPSS v4" is shorthand for current EPSS methodology. EPSS uses daily model_date, not version numbers. ``` 2. **Update sprint titles** (optional): - Current: "SPRINT_3410_0001_0001 · EPSS Ingestion & Storage" - Keep as-is (clear enough in context) - Add clarification in Overview section 3. **Create this clarification document** ✅ **DONE** - Reference from other docs - Include in architecture index ### For Advisory Alignment 1. **Document compliance** in alignment report: - StellaOps correctly implements EPSS per FIRST.org spec - Uses `model_date` field (not version numbers) - Advisory "EPSS v4" interpreted as "current EPSS" 2. **No code changes needed** ✅ - Implementation is already correct - Documentation clarification is sufficient --- ## EPSS Scoring Integration in StellaOps ### Usage in Triage **Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Entities/TriageRiskResult.cs` ```csharp public sealed class TriageRiskResult { public double? EpssScore { get; set; } // 0.0-1.0 probability public double? EpssPercentile { get; set; } // 0.0-1.0 ranking public DateOnly? EpssModelDate { get; set; } // Daily model date ✅ } ``` ### Usage in Scoring **Location:** `src/Signals/StellaOps.Signals/Services/ScoreExplanationService.cs` ```csharp // EPSS Contribution (lines 73-86) if (request.EpssScore.HasValue) { var epssContribution = request.EpssScore.Value * weights.EpssMultiplier; // Default multiplier: 10.0 (so 0.0-1.0 EPSS → 0-10 points) explanation.Factors.Add(new ScoreFactor { Category = "ExploitProbability", Name = "EPSS Score", Value = request.EpssScore.Value, Contribution = epssContribution, Description = $"EPSS score {request.EpssScore.Value:P1} (model date: {request.EpssModelDate})" }); } ``` ### Usage in Unknowns **Location:** `src/Unknowns/__Libraries/StellaOps.Unknowns.Core/Services/UnknownRanker.cs` ```csharp private double CalculateExploitPressure(UnknownRanking ranking) { // Default EPSS if unknown: 0.35 (median, conservative) var epss = ranking.EpssScore ?? 0.35; var kev = ranking.IsKev ? 0.30 : 0.0; return Math.Clamp(epss + kev, 0, 1); } ``` --- ## External References ### FIRST.org EPSS Resources - **Main Page:** https://www.first.org/epss/ - **CSV Download:** https://epss.cyentia.com/epss_scores-YYYY-MM-DD.csv.gz - **API Endpoint:** https://api.first.org/data/v1/epss?cve=CVE-YYYY-NNNNN - **Methodology Paper:** https://www.first.org/epss/articles/prob_percentile_bins.html - **FAQ:** https://www.first.org/epss/faq ### Academic Citations - Jacobs, J., et al. (2021). "EPSS: A Data-Driven Vulnerability Prioritization Framework" - FIRST.org (2023). "EPSS Model v2023.03.01 Release Notes" --- ## Summary **Key Takeaways:** 1. ❌ **EPSS does NOT have numbered versions** (no "v1", "v2", "v3", "v4") 2. ✅ **EPSS uses daily model dates** (`model_date` field) 3. ✅ **StellaOps implementation is correct** per FIRST.org specification 4. ⚠️ **"EPSS v4" is conceptual** - refers to current EPSS methodology 5. ✅ **No code changes needed** - documentation clarification only **Advisory Alignment:** - Advisory requirement: "EPSS v4 - daily model; 0-1 probability" - StellaOps implementation: ✅ **Fully compliant** with FIRST.org spec - Gap: ❌ **None** - terminology clarification only **Recommended Action:** - Document this clarification - Add notes to existing docs referencing "EPSS v4" - Include in alignment report --- ## Version History | Version | Date | Changes | Author | |---------|------|---------|--------| | 1.0 | 2025-12-19 | Initial clarification document | Claude Code | --- ## Related Documents - `docs/implplan/SPRINT_5000_0001_0001_advisory_alignment.md` - Parent sprint - `docs/architecture/signal-contract-mapping.md` - Signal contract mapping - `docs/guides/epss-integration-v4.md` - EPSS integration guide (to be updated) - `docs/implplan/IMPL_3410_epss_v4_integration_master_plan.md` - EPSS implementation plan (to be updated) - `docs/risk/formulas.md` - Scoring formulas including EPSS --- **END OF DOCUMENT**