Files
git.stella-ops.org/docs/product-advisories/archived/17-Dec-2025 - Reachability Drift Detection.md
StellaOps Bot 634233dfed feat: Implement distro-native version comparison for RPM, Debian, and Alpine packages
- Add RpmVersionComparer for RPM version comparison with epoch, version, and release handling.
- Introduce DebianVersion for parsing Debian EVR (Epoch:Version-Release) strings.
- Create ApkVersion for parsing Alpine APK version strings with suffix support.
- Define IVersionComparator interface for version comparison with proof-line generation.
- Implement VersionComparisonResult struct to encapsulate comparison results and proof lines.
- Add tests for Debian and RPM version comparers to ensure correct functionality and edge case handling.
- Create project files for the version comparison library and its tests.
2025-12-22 09:50:12 +02:00

18 KiB

Reachability Drift Detection

Date: 2025-12-17 Status: ARCHIVED - Implementation Complete (Sprints 3600.2-3600.3) Archived: 2025-12-22 Related Advisories:

  • 14-Dec-2025 - Smart-Diff Technical Reference
  • 14-Dec-2025 - Reachability Analysis Technical Reference

Implementation Documentation:

  • Architecture: docs/modules/scanner/reachability-drift.md
  • API Reference: docs/api/scanner-drift-api.md
  • Operations Guide: docs/operations/reachability-drift-guide.md

Follow-up Sprints:

  • SPRINT_3600_0004_0001 - Node.js Babel Integration (TODO)
  • SPRINT_3600_0005_0001 - Policy CI Gate Integration (TODO)
  • SPRINT_3600_0006_0001 - Documentation Finalization (TODO)

1. EXECUTIVE SUMMARY

This advisory proposes extending StellaOps' Smart-Diff capabilities to detect reachability drift - changes in whether vulnerable code paths are reachable from application entry points between container image versions.

Core Insight: Raw diffs don't equal risk. Most changed lines don't matter for exploitability. Reachability drift detection fuses call-stack reachability graphs with Smart-Diff metadata to flag only paths that went from unreachable to reachable (or vice-versa), tied to SBOM components and VEX statements.


2. GAP ANALYSIS vs EXISTING INFRASTRUCTURE

2.1 What Already Exists (Leverage Points)

Component Location Status
MaterialRiskChangeDetector Scanner.SmartDiff.Detection DONE - R1-R4 rules
VexCandidateEmitter Scanner.SmartDiff.Detection DONE - Absent API detection
ReachabilityGateBridge Scanner.SmartDiff.Detection DONE - Lattice to 3-bit
ReachabilitySignal Signals.Contracts DONE - Call path model
ReachabilityLatticeState Signals.Contracts.Evidence DONE - 5-state enum
CallPath, CallPathNode Signals.Contracts.Evidence DONE - Path representation
ReachabilityEvidenceChain Signals.Contracts.Evidence DONE - Proof chain
vex.graph_nodes/edges DB Schema DONE - Graph storage
scanner.risk_state_snapshots DB Schema DONE - State storage
scanner.material_risk_changes DB Schema DONE - Change storage
FnDriftCalculator Scanner.Core.Drift DONE - Classification drift
SarifOutputGenerator Scanner.SmartDiff.Output DONE - CI output
Reachability Benchmark bench/reachability-benchmark/ DONE - Ground truth cases
Language Analyzers Scanner.Analyzers.Lang.* PARTIAL - Package detection, limited call graph

2.2 What's Missing (New Implementation Required)

Component Advisory Ref Gap Description Post-Implementation Status
Call Graph Extractor (.NET) §7 C# Roslyn No MSBuildWorkspace/Roslyn analysis exists DONE - DotNetCallGraphExtractor
Call Graph Extractor (Go) §7 Go SSA No golang.org/x/tools/go/ssa integration DONE - GoCallGraphExtractor
Call Graph Extractor (Java) §7 No Soot/WALA integration DONE - JavaCallGraphExtractor (ASM)
Call Graph Extractor (Node) §7 No @babel/traverse integration PARTIAL - Skeleton exists
scanner.code_changes table §4 Smart-Diff AST-level diff facts not persisted DONE - Migration 010
Drift Cause Explainer §6 Timeline No causal attribution on path nodes DONE - DriftCauseExplainer
Path Viewer UI §UX No Angular component for call path visualization DONE - path-viewer.component.ts
Cross-scan Function-level Drift §6 State drift exists, function-level doesn't DONE - ReachabilityDriftDetector
Entrypoint Discovery (per-framework) §3 Limited beyond package.json/manifest parsing DONE - 9 entrypoint types

2.3 Terminology Mapping

Advisory Term StellaOps Equivalent Notes
commit_sha scan_id StellaOps is image-centric, not commit-centric
call_node vex.graph_nodes Existing schema, extend don't duplicate
call_edge vex.graph_edges Existing schema
reachability_drift scanner.material_risk_changes Add cause, path_nodes columns
Risk Drift Material Risk Change Existing term is more precise
Router, Signals Signals module only Router module is not implemented

3.1 What to Ship (Delta from Current State)

NEW TABLES:
├── scanner.code_changes          # AST-level diff facts
└── scanner.call_graph_snapshots  # Per-scan call graph cache

NEW COLUMNS:
├── scanner.material_risk_changes.cause       # TEXT - "guard_removed", "new_route", etc.
├── scanner.material_risk_changes.path_nodes  # JSONB - Compressed path representation
└── scanner.material_risk_changes.base_scan_id # UUID - For cross-scan comparison

NEW SERVICES:
├── CallGraphExtractor.DotNet     # Roslyn-based for .NET projects
├── CallGraphExtractor.Node       # AST-based for Node.js
├── DriftCauseExplainer           # Attribute causes to code changes
└── PathCompressor                 # Compress paths for storage/UI

NEW UI:
└── PathViewerComponent           # Angular component for call path visualization

3.2 What NOT to Ship (Avoid Duplication)

  • Don't create call_node/call_edge tables - Use existing vex.graph_nodes/vex.graph_edges
  • Don't add commit_sha columns - Use scan_id consistently
  • Don't build React components - Angular v17 is the stack

3.3 Use Valkey for Graph Caching

Valkey is already integrated in Router.Gateway.RateLimit. Use it for:

  • Call graph snapshot caching - Fast cross-instance lookups
  • Reachability result caching - Avoid recomputation
  • Key pattern: stella:callgraph:{scan_id}:{lang}:{digest}
# Configuration pattern (align with existing Router rate limiting)
reachability:
  valkey_connection: "localhost:6379"
  valkey_bucket: "stella-reachability"
  cache_ttl_hours: 24
  circuit_breaker:
    failure_threshold: 5
    timeout_seconds: 30

4. TECHNICAL DESIGN

4.1 Call Graph Extraction Model

/// <summary>
/// Per-scan call graph snapshot for drift comparison.
/// </summary>
public sealed record CallGraphSnapshot
{
    public required string ScanId { get; init; }
    public required string GraphDigest { get; init; }  // Content hash
    public required string Language { get; init; }
    public required DateTimeOffset ExtractedAt { get; init; }
    public required ImmutableArray<CallGraphNode> Nodes { get; init; }
    public required ImmutableArray<CallGraphEdge> Edges { get; init; }
    public required ImmutableArray<string> EntrypointIds { get; init; }
}

public sealed record CallGraphNode
{
    public required string NodeId { get; init; }      // Stable identifier
    public required string Symbol { get; init; }      // Fully qualified name
    public required string File { get; init; }
    public required int Line { get; init; }
    public required string Package { get; init; }
    public required string Visibility { get; init; }  // public/internal/private
    public required bool IsEntrypoint { get; init; }
    public required bool IsSink { get; init; }
    public string? SinkCategory { get; init; }        // CMD_EXEC, SQL_RAW, etc.
}

public sealed record CallGraphEdge
{
    public required string SourceId { get; init; }
    public required string TargetId { get; init; }
    public required string CallKind { get; init; }    // direct/virtual/delegate
}

4.2 Code Change Facts Model

/// <summary>
/// AST-level code change facts from Smart-Diff.
/// </summary>
public sealed record CodeChangeFact
{
    public required string ScanId { get; init; }
    public required string File { get; init; }
    public required string Symbol { get; init; }
    public required CodeChangeKind Kind { get; init; }
    public required JsonDocument Details { get; init; }
}

public enum CodeChangeKind
{
    Added,
    Removed,
    SignatureChanged,
    GuardChanged,        // Boolean condition around call modified
    DependencyChanged,   // Callee package/version changed
    VisibilityChanged    // public<->internal<->private
}

4.3 Drift Cause Attribution

/// <summary>
/// Explains why a reachability flip occurred.
/// </summary>
public sealed class DriftCauseExplainer
{
    public DriftCause Explain(
        CallGraphSnapshot baseGraph,
        CallGraphSnapshot headGraph,
        string sinkSymbol,
        IReadOnlyList<CodeChangeFact> codeChanges)
    {
        // Find shortest path to sink in head graph
        var path = ShortestPath(headGraph.EntrypointIds, sinkSymbol, headGraph);
        if (path is null)
            return DriftCause.Unknown;

        // Check each node on path for code changes
        foreach (var nodeId in path.NodeIds)
        {
            var node = headGraph.Nodes.First(n => n.NodeId == nodeId);
            var change = codeChanges.FirstOrDefault(c => c.Symbol == node.Symbol);

            if (change is not null)
            {
                return change.Kind switch
                {
                    CodeChangeKind.GuardChanged => DriftCause.GuardRemoved(node.Symbol, node.File, node.Line),
                    CodeChangeKind.Added => DriftCause.NewPublicRoute(node.Symbol),
                    CodeChangeKind.VisibilityChanged => DriftCause.VisibilityEscalated(node.Symbol),
                    CodeChangeKind.DependencyChanged => DriftCause.DepUpgraded(change.Details),
                    _ => DriftCause.CodeModified(node.Symbol)
                };
            }
        }

        return DriftCause.Unknown;
    }
}

4.4 Database Schema Extensions

-- New table: Code change facts from AST-level Smart-Diff
CREATE TABLE scanner.code_changes (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,
    scan_id TEXT NOT NULL,
    file TEXT NOT NULL,
    symbol TEXT NOT NULL,
    change_kind TEXT NOT NULL,  -- added|removed|signature|guard|dep|visibility
    details JSONB,
    detected_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT code_changes_unique UNIQUE (tenant_id, scan_id, file, symbol)
);

CREATE INDEX idx_code_changes_scan ON scanner.code_changes(scan_id);
CREATE INDEX idx_code_changes_symbol ON scanner.code_changes(symbol);

-- New table: Per-scan call graph snapshots (compressed)
CREATE TABLE scanner.call_graph_snapshots (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,
    scan_id TEXT NOT NULL,
    language TEXT NOT NULL,
    graph_digest TEXT NOT NULL,  -- Content hash for dedup
    node_count INT NOT NULL,
    edge_count INT NOT NULL,
    entrypoint_count INT NOT NULL,
    extracted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    cas_uri TEXT NOT NULL,       -- Reference to CAS for full graph

    CONSTRAINT call_graph_snapshots_unique UNIQUE (tenant_id, scan_id, language)
);

CREATE INDEX idx_call_graph_snapshots_digest ON scanner.call_graph_snapshots(graph_digest);

-- Extend existing material_risk_changes table
ALTER TABLE scanner.material_risk_changes
ADD COLUMN IF NOT EXISTS cause TEXT,
ADD COLUMN IF NOT EXISTS path_nodes JSONB,
ADD COLUMN IF NOT EXISTS base_scan_id TEXT;

CREATE INDEX IF NOT EXISTS idx_material_risk_changes_cause
ON scanner.material_risk_changes(cause) WHERE cause IS NOT NULL;

5. UI DESIGN

5.1 Risk Drift Card (PR/Commit View)

┌─────────────────────────────────────────────────────────────────────┐
│  RISK DRIFT                                                    ▼   │
├─────────────────────────────────────────────────────────────────────┤
│  +3 new reachable paths    -2 mitigated paths                       │
│                                                                     │
│  ┌─ NEW REACHABLE ──────────────────────────────────────────────┐  │
│  │ POST /payments → PaymentsController.Capture → ... →           │  │
│  │   crypto.Verify(legacy)                                       │  │
│  │                                                               │  │
│  │ [pkg:payments@1.8.2] [CVE-2024-1234] [EPSS 0.72] [VEX:affected]│  │
│  │                                                               │  │
│  │ Cause: guard removed in AuthFilter.cs:42                      │  │
│  │                                                               │  │
│  │ [View Path] [Quarantine Route] [Pin Version] [Add Exception]  │  │
│  └───────────────────────────────────────────────────────────────┘  │
│                                                                     │
│  ┌─ MITIGATED ──────────────────────────────────────────────────┐  │
│  │ GET /admin → AdminController.Execute → ... → cmd.Run          │  │
│  │                                                               │  │
│  │ [pkg:admin@2.0.0] [CVE-2024-5678] [VEX:not_affected]          │  │
│  │                                                               │  │
│  │ Reason: Vulnerable API removed in upgrade                     │  │
│  └───────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘

5.2 Path Viewer Component

┌─────────────────────────────────────────────────────────────────────┐
│  CALL PATH: POST /payments → crypto.Verify(legacy)      [Collapse] │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ○ POST /payments                              [ENTRYPOINT]         │
│  │   PaymentsController.cs:45                                       │
│  │                                                                  │
│  ├──○ PaymentsController.Capture()                                  │
│  │  │   PaymentsController.cs:89                                    │
│  │  │                                                               │
│  │  ├──○ PaymentService.ProcessPayment()                            │
│  │  │  │   PaymentService.cs:156                                    │
│  │  │  │                                                            │
│  │  │  ├──● CryptoHelper.Verify() ← GUARD REMOVED                   │
│  │  │  │  │   CryptoHelper.cs:42   [Changed: AuthFilter removed]    │
│  │  │  │  │                                                         │
│  │  │  │  └──◆ crypto.Verify(legacy)              [VULNERABLE SINK] │
│  │  │  │         pkg:crypto@1.2.3                                   │
│  │  │  │         CVE-2024-1234 (CVSS 9.8)                           │
│                                                                     │
│  Legend: ○ Node  ● Changed  ◆ Sink  ─ Call                          │
└─────────────────────────────────────────────────────────────────────┘

6. POLICY INTEGRATION

6.1 CI Gate Behavior

# Policy wiring for drift detection
smart_diff:
  gates:
    # Fail PR when new reachable paths to affected sinks
    - condition: "delta_reachable > 0 AND vex_status IN ['affected', 'under_investigation']"
      action: block
      message: "New reachable paths to vulnerable sinks detected"

    # Warn when new paths to any sink
    - condition: "delta_reachable > 0"
      action: warn
      message: "New reachable paths detected - review recommended"

    # Auto-mitigate when VEX confirms not_affected
    - condition: "vex_status == 'not_affected' AND vex_justification IN ['component_not_present', 'fix_applied']"
      action: allow
      auto_mitigate: true

6.2 Exit Codes

Code Meaning
0 Success, no material drift
1 Success, material drift found (info)
2 Success, hardening regression detected
3 Success, new KEV reachable
10+ Errors

7. SPRINT STRUCTURE

7.1 Master Sprint: SPRINT_3600_0001_0001

Topic: Reachability Drift Detection Dependencies: SPRINT_3500 (Smart-Diff) - COMPLETE

7.2 Sub-Sprints

ID Topic Priority Effort Dependencies Status
SPRINT_3600_0002_0001 Call Graph Infrastructure P0 Large Master DONE
SPRINT_3600_0003_0001 Drift Detection Engine P0 Medium 3600.2 DONE
SPRINT_3600_0004_0001 Node.js Babel Integration P1 Medium 3600.3 TODO
SPRINT_3600_0005_0001 Policy CI Gate Integration P1 Small 3600.3 TODO
SPRINT_3600_0006_0001 Documentation Finalization P0 Medium 3600.3 TODO

8. REFERENCES

  • docs/product-advisories/14-Dec-2025 - Smart-Diff Technical Reference.md
  • docs/product-advisories/14-Dec-2025 - Reachability Analysis Technical Reference.md
  • docs/implplan/SPRINT_3500_0001_0001_smart_diff_master.md
  • docs/reachability/lattice.md
  • bench/reachability-benchmark/README.md