Files
git.stella-ops.org/docs/modules/policy/contracts/reachability-input-contract.md
StellaOps Bot 5fc469ad98 feat: Add VEX Status Chip component and integration tests for reachability drift detection
- Introduced `VexStatusChipComponent` to display VEX status with color coding and tooltips.
- Implemented integration tests for reachability drift detection, covering various scenarios including drift detection, determinism, and error handling.
- Enhanced `ScannerToSignalsReachabilityTests` with a null implementation of `ICallGraphSyncService` for better test isolation.
- Updated project references to include the new Reachability Drift library.
2025-12-20 01:26:42 +02:00

14 KiB

Reachability Input Contract v1.0.0

Status: APPROVED Version: 1.0.0 Effective: 2025-12-19 Owner: Policy Guild + Signals Guild Sprint: SPRINT_0126_0001_0001 (unblocks POLICY-ENGINE-80-001 through 80-004)


1. Purpose

This contract defines the integration between the Signals service (reachability analysis) and the Policy Engine. It specifies how reachability and exploitability facts flow into policy evaluation, enabling risk-aware decisions based on static analysis, runtime observations, and exploit intelligence.

2. Schema Reference

The canonical JSON schema is at:

docs/schemas/reachability-input.schema.json

3. Data Flow

┌─────────────┐     ┌──────────────┐     ┌───────────────┐     ┌──────────────┐
│ Scanner     │────▶│ Signals      │────▶│ Reachability  │────▶│ Policy       │
│ (callgraph) │     │ Service      │     │ Facts Store   │     │ Engine       │
└─────────────┘     └──────────────┘     └───────────────┘     └──────────────┘
                           │                     ▲
                           │                     │
                    ┌──────▼──────┐              │
                    │ Runtime     │──────────────┘
                    │ Agent       │
                    └─────────────┘

4. Core Types

4.1 ReachabilityInput

The input payload submitted to Policy Engine for evaluation:

public sealed record ReachabilityInput
{
    /// <summary>Subject being evaluated (component + vulnerability).</summary>
    public required Subject Subject { get; init; }

    /// <summary>Static reachability analysis results.</summary>
    public required ImmutableArray<ReachabilityFact> ReachabilityFacts { get; init; }

    /// <summary>Exploitability assessments from KEV, EPSS, vendor advisories.</summary>
    public ImmutableArray<ExploitabilityFact> ExploitabilityFacts { get; init; }

    /// <summary>References to stored callgraphs.</summary>
    public ImmutableArray<CallgraphRef> CallgraphRefs { get; init; }

    /// <summary>Runtime observation facts.</summary>
    public ImmutableArray<RuntimeFact> RuntimeFacts { get; init; }

    /// <summary>Scanner entropy/trust score for confidence weighting.</summary>
    public EntropyScore? EntropyScore { get; init; }

    /// <summary>Input timestamp (UTC).</summary>
    public required DateTimeOffset Timestamp { get; init; }
}

4.2 Subject

public sealed record Subject
{
    /// <summary>Package URL of the component.</summary>
    public required string Purl { get; init; }

    /// <summary>CVE identifier (e.g., CVE-2024-1234).</summary>
    public string? CveId { get; init; }

    /// <summary>GitHub Security Advisory ID.</summary>
    public string? GhsaId { get; init; }

    /// <summary>Internal vulnerability identifier.</summary>
    public string? VulnerabilityId { get; init; }

    /// <summary>Vulnerable symbols/functions in the component.</summary>
    public ImmutableArray<string> AffectedSymbols { get; init; }

    /// <summary>Affected version range (e.g., "<1.2.3").</summary>
    public string? VersionRange { get; init; }
}

4.3 ReachabilityFact

public sealed record ReachabilityFact
{
    /// <summary>Reachability state determination.</summary>
    public required ReachabilityState State { get; init; }

    /// <summary>Confidence score (0.0-1.0).</summary>
    public required decimal Confidence { get; init; }

    /// <summary>Source of determination.</summary>
    public required ReachabilitySource Source { get; init; }

    /// <summary>Analyzer that produced this fact.</summary>
    public string? Analyzer { get; init; }

    /// <summary>Analyzer version.</summary>
    public string? AnalyzerVersion { get; init; }

    /// <summary>Call path from entry point to vulnerable symbol.</summary>
    public CallPath? CallPath { get; init; }

    /// <summary>Entry points that can reach vulnerable code.</summary>
    public ImmutableArray<EntryPoint> EntryPoints { get; init; }

    /// <summary>Supporting evidence.</summary>
    public ReachabilityEvidence? Evidence { get; init; }

    /// <summary>When this fact was evaluated.</summary>
    public DateTimeOffset? EvaluatedAt { get; init; }
}

public enum ReachabilityState
{
    Reachable = 0,
    Unreachable = 1,
    PotentiallyReachable = 2,
    Unknown = 3
}

public enum ReachabilitySource
{
    StaticAnalysis = 0,
    DynamicAnalysis = 1,
    SbomInference = 2,
    Manual = 3,
    External = 4
}

4.4 ExploitabilityFact

public sealed record ExploitabilityFact
{
    /// <summary>Exploitability state.</summary>
    public required ExploitabilityState State { get; init; }

    /// <summary>Confidence score (0.0-1.0).</summary>
    public required decimal Confidence { get; init; }

    /// <summary>Source of determination.</summary>
    public required ExploitabilitySource Source { get; init; }

    /// <summary>EPSS probability score (0.0-1.0).</summary>
    public decimal? EpssScore { get; init; }

    /// <summary>EPSS percentile (0-100).</summary>
    public decimal? EpssPercentile { get; init; }

    /// <summary>Listed in CISA Known Exploited Vulnerabilities.</summary>
    public bool? KevListed { get; init; }

    /// <summary>KEV remediation due date.</summary>
    public DateOnly? KevDueDate { get; init; }

    /// <summary>Exploit maturity level (per CVSS).</summary>
    public ExploitMaturity? ExploitMaturity { get; init; }

    /// <summary>References to known exploits.</summary>
    public ImmutableArray<Uri> ExploitRefs { get; init; }

    /// <summary>Conditions required for exploitation.</summary>
    public ImmutableArray<ExploitCondition> Conditions { get; init; }

    /// <summary>When this fact was evaluated.</summary>
    public DateTimeOffset? EvaluatedAt { get; init; }
}

public enum ExploitabilityState
{
    Exploitable = 0,
    NotExploitable = 1,
    ConditionallyExploitable = 2,
    Unknown = 3
}

public enum ExploitabilitySource
{
    Kev = 0,
    Epss = 1,
    VendorAdvisory = 2,
    InternalAnalysis = 3,
    ExploitDb = 4
}

public enum ExploitMaturity
{
    NotDefined = 0,
    Unproven = 1,
    Poc = 2,
    Functional = 3,
    High = 4
}

4.5 RuntimeFact

public sealed record RuntimeFact
{
    /// <summary>Type of runtime observation.</summary>
    public required RuntimeFactType Type { get; init; }

    /// <summary>Observed symbol/function.</summary>
    public string? Symbol { get; init; }

    /// <summary>Observed module.</summary>
    public string? Module { get; init; }

    /// <summary>Number of times called.</summary>
    public int? CallCount { get; init; }

    /// <summary>Last invocation time.</summary>
    public DateTimeOffset? LastCalled { get; init; }

    /// <summary>When observation was recorded.</summary>
    public required DateTimeOffset ObservedAt { get; init; }

    /// <summary>Observation window duration (e.g., "7d").</summary>
    public string? ObservationWindow { get; init; }

    /// <summary>Environment where observed.</summary>
    public RuntimeEnvironment? Environment { get; init; }
}

public enum RuntimeFactType
{
    FunctionCalled = 0,
    FunctionNotCalled = 1,
    PathExecuted = 2,
    PathNotExecuted = 3,
    ModuleLoaded = 4,
    ModuleNotLoaded = 5
}

public enum RuntimeEnvironment
{
    Production = 0,
    Staging = 1,
    Development = 2,
    Test = 3
}

5. Policy Engine Integration

5.1 ReachabilityFactsJoiningService

The ReachabilityFactsJoiningService provides efficient batch lookups with caching:

public interface IReachabilityFactsJoiningService
{
    /// <summary>
    /// Gets reachability facts for a batch of component-advisory pairs.
    /// Uses cache-first strategy with store fallback.
    /// </summary>
    Task<ReachabilityFactsBatch> GetFactsBatchAsync(
        string tenantId,
        IReadOnlyList<ReachabilityFactsRequest> items,
        CancellationToken cancellationToken = default);

    /// <summary>
    /// Enriches signal context with reachability facts.
    /// </summary>
    Task<bool> EnrichSignalsAsync(
        string tenantId,
        string componentPurl,
        string advisoryId,
        IDictionary<string, object?> signals,
        CancellationToken cancellationToken = default);
}

5.2 SPL Predicates

Reachability is exposed in SPL (StellaOps Policy Language) via the reachability scope:

# Example SPL rule using reachability predicates
rules:
  - name: "Suppress unreachable critical CVEs"
    when:
      all:
        - severity >= critical
        - reachability.state == "unreachable"
        - reachability.confidence >= 0.9
    then:
      effect: suppress
      justification: "Unreachable code path with high confidence"

  - name: "Escalate reachable with exploit"
    when:
      all:
        - reachability.state == "reachable"
        - exploitability.kev_listed == true
    then:
      effect: escalate
      priority: critical

Available predicates:

Predicate Type Description
reachability.state string "reachable", "unreachable", "potentially_reachable", "unknown"
reachability.confidence decimal Confidence score 0.0-1.0
reachability.score decimal Computed risk score
reachability.has_runtime_evidence bool Whether runtime facts support determination
reachability.is_high_confidence bool Confidence >= 0.8
reachability.source string Source of determination
reachability.method string Analysis method used
exploitability.state string "exploitable", "not_exploitable", "conditionally_exploitable", "unknown"
exploitability.epss_score decimal EPSS probability 0.0-1.0
exploitability.epss_percentile decimal EPSS percentile 0-100
exploitability.kev_listed bool In CISA KEV catalog
exploitability.kev_due_date date KEV remediation deadline
exploitability.maturity string "not_defined", "unproven", "poc", "functional", "high"

5.3 ReachabilityOutput

Policy evaluation produces enriched output:

public sealed record ReachabilityOutput
{
    /// <summary>Subject evaluated.</summary>
    public required Subject Subject { get; init; }

    /// <summary>Effective reachability state after policy rules.</summary>
    public required ReachabilityState EffectiveState { get; init; }

    /// <summary>Effective exploitability after policy rules.</summary>
    public ExploitabilityState? EffectiveExploitability { get; init; }

    /// <summary>Risk adjustment from policy evaluation.</summary>
    public required RiskAdjustment RiskAdjustment { get; init; }

    /// <summary>Policy rule trace.</summary>
    public ImmutableArray<PolicyRuleTrace> PolicyTrace { get; init; }

    /// <summary>When evaluation occurred.</summary>
    public required DateTimeOffset EvaluatedAt { get; init; }
}

public sealed record RiskAdjustment
{
    /// <summary>Risk multiplier (0=suppress, 1=neutral, >1=amplify).</summary>
    public required decimal Factor { get; init; }

    /// <summary>Severity override if rules dictate.</summary>
    public Severity? SeverityOverride { get; init; }

    /// <summary>Justification for adjustment.</summary>
    public string? Justification { get; init; }
}

6. API Endpoints

6.1 Signals Service Endpoints

Endpoint Method Description
POST /signals/reachability/recompute POST Recompute reachability for a subject
GET /signals/facts/{subjectKey} GET Get reachability facts for a subject
POST /signals/runtime-facts POST Ingest runtime observations

6.2 Policy Engine Endpoints

Endpoint Method Description
POST /api/policy/evaluate POST Evaluate with reachability enrichment
POST /api/policy/simulate POST Simulate with reachability overrides
GET /api/policy/reachability/stats GET Get reachability integration metrics

7. Caching Strategy

7.1 Cache Layers

  1. L1: In-Memory Overlay Cache

    • Per-request deduplication
    • TTL: Request lifetime
    • Key: {tenantId}:{componentPurl}:{advisoryId}
  2. L2: Redis Distributed Cache

    • Shared across Policy Engine instances
    • TTL: 5 minutes (configurable)
    • Key: rf:{tenantId}:{sha256(purl+advisoryId)}
  3. L3: Postgres Facts Store

    • Authoritative source
    • Indexed by (tenant_id, component_purl, advisory_id)

7.2 Cache Invalidation

  • Facts are invalidated when:
    • New callgraph is ingested
    • Runtime facts are updated
    • Manual override is applied
    • TTL expires

8. Telemetry

8.1 Metrics

Metric Type Labels Description
policy_reachability_applied_total counter state Facts applied to evaluations
policy_reachability_cache_hits_total counter - Cache hits
policy_reachability_cache_misses_total counter - Cache misses
policy_reachability_cache_hit_ratio gauge - Hit ratio (0.0-1.0)
policy_reachability_lookups_total counter outcome Lookup attempts
policy_reachability_lookup_seconds histogram - Lookup latency

8.2 Traces

Activity: reachability_facts.batch_lookup Tags:

  • tenant: Tenant ID
  • batch_size: Number of items requested
  • cache_hits: Items found in cache
  • cache_misses: Items not in cache
  • store_hits: Items fetched from store

9. Configuration

# etc/policy-engine.yaml
PolicyEngine:
  Reachability:
    Enabled: true
    CacheTtlSeconds: 300
    MaxBatchSize: 1000
    DefaultConfidenceThreshold: 0.7
    HighConfidenceThreshold: 0.9

  ReachabilityCache:
    Type: "redis"  # or "memory"
    RedisConnectionString: "${REDIS_URL}"
    KeyPrefix: "rf:"

10. Validation Rules

  1. Subject.Purl must be a valid Package URL
  2. ReachabilityFact.Confidence must be 0.0-1.0
  3. ReachabilityFact.State must be a valid enum value
  4. Timestamp must be valid UTC ISO-8601
  5. At least one of CveId, GhsaId, or VulnerabilityId must be present

Changelog

Version Date Changes
1.0.0 2025-12-19 Initial release