FN-Drift Metrics Reference

Audience: Scanner and Telemetry engineers, SREs, and policy owners tracking scanner accuracy. Scope: The metric families, classification model, SLO thresholds, alert rules, and storage schema for False-Negative Drift.

Overview

False-Negative Drift (FN-Drift) measures how often vulnerability classifications change from “not affected” or “unknown” to “affected” during rescans. It is the primary signal for how often Stella Ops missed a vulnerability on an earlier scan. This metric is critical for:

Metrics

Gauges (30-day rolling window)

MetricTypeDescription
scanner.fn_drift.percentGauge30-day rolling FN-Drift percentage
scanner.fn_drift.transitions_30dGaugeTotal FN transitions in last 30 days
scanner.fn_drift.evaluated_30dGaugeTotal findings evaluated in last 30 days
scanner.fn_drift.cause.feed_deltaGaugeFN transitions caused by feed updates
scanner.fn_drift.cause.rule_deltaGaugeFN transitions caused by rule changes
scanner.fn_drift.cause.lattice_deltaGaugeFN transitions caused by VEX lattice changes
scanner.fn_drift.cause.reachability_deltaGaugeFN transitions caused by reachability changes
scanner.fn_drift.cause.engineGaugeFN transitions caused by engine changes (should be ~0)

Counters (all-time)

MetricTypeLabelsDescription
scanner.classification_changes_totalCountercauseTotal classification status changes
scanner.fn_transitions_totalCountercauseTotal false-negative transitions

Classification Statuses

StatusDescription
newFirst scan, no previous status
unaffectedConfirmed not affected
unknownStatus unknown/uncertain
affectedConfirmed affected
fixedPreviously affected, now fixed

Drift Causes

CauseDescriptionExpected Impact
feed_deltaVulnerability feed updated (NVD, GHSA, OVAL)High - most common cause
rule_deltaPolicy rules changedMedium - controlled by policy team
lattice_deltaVEX lattice state changedMedium - VEX updates
reachability_deltaReachability analysis changedLow - improved analysis
engineScanner engine change~0 - determinism violation if >0
otherUnknown/unclassified causeLow - investigate if high

FN-Drift Definition

A False-Negative Transition occurs when:

This indicates the scanner previously classified a finding as “not vulnerable” but now classifies it as “vulnerable” - a false negative in the earlier scan.

FN-Drift Rate Calculation

FN-Drift % = (FN Transitions / Total Reclassified) × 100

Where:

SLO Thresholds

SLO LevelFN-Drift ThresholdAlert Severity
Target< 1.0%None
Warning1.0% - 2.5%Warning
Critical> 2.5%Critical
Engine Drift> 0%Page

Alerting Rules

# Example Prometheus alerting rules
groups:
  - name: fn-drift
    rules:
      - alert: FnDriftWarning
        expr: scanner_fn_drift_percent > 1.0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "FN-Drift rate above warning threshold"
          
      - alert: FnDriftCritical
        expr: scanner_fn_drift_percent > 2.5
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "FN-Drift rate above critical threshold"
          
      - alert: EngineDriftDetected
        expr: scanner_fn_drift_cause_engine > 0
        for: 1m
        labels:
          severity: page
        annotations:
          summary: "Engine-caused FN drift detected - determinism violation"

Dashboard Queries

FN-Drift Trend (Grafana)

# 30-day rolling FN-Drift percentage
scanner_fn_drift_percent

# FN transitions by cause
sum by (cause) (rate(scanner_fn_transitions_total[1h]))

# Classification changes rate
sum by (cause) (rate(scanner_classification_changes_total[1h]))

Drift Cause Breakdown

# Pie chart of drift causes
topk(5, 
  sum by (cause) (
    increase(scanner_fn_transitions_total[24h])
  )
)

Database Schema

classification_history Table

CREATE TABLE scanner.classification_history (
    id BIGSERIAL PRIMARY KEY,
    artifact_digest TEXT NOT NULL,
    vuln_id TEXT NOT NULL,
    package_purl TEXT NOT NULL,
    tenant_id UUID NOT NULL,
    manifest_id UUID NOT NULL,
    execution_id UUID NOT NULL,
    previous_status TEXT NOT NULL,
    new_status TEXT NOT NULL,
    is_fn_transition BOOLEAN GENERATED ALWAYS AS (...) STORED,
    cause TEXT NOT NULL,
    cause_detail JSONB,
    changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

fn_drift_stats Materialized View

Aggregated daily statistics for efficient dashboard queries: