Files
git.stella-ops.org/docs/modules/risk-engine/fix-chain-integration.md
master 7f7eb8b228 Complete batch 012 (golden set diff) and 013 (advisory chat), fix build errors
Sprints completed:
- SPRINT_20260110_012_* (golden set diff layer - 10 sprints)
- SPRINT_20260110_013_* (advisory chat - 4 sprints)

Build fixes applied:
- Fix namespace conflicts with Microsoft.Extensions.Options.Options.Create
- Fix VexDecisionReachabilityIntegrationTests API drift (major rewrite)
- Fix VexSchemaValidationTests FluentAssertions method name
- Fix FixChainGateIntegrationTests ambiguous type references
- Fix AdvisoryAI test files required properties and namespace aliases
- Add stub types for CveMappingController (ICveSymbolMappingService)
- Fix VerdictBuilderService static context issue

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 10:09:07 +02:00

8.0 KiB

Risk Engine FixChain Integration

Sprint: SPRINT_20260110_012_007_RISK Last Updated: 10-Jan-2026

Overview

The Risk Engine FixChain integration enables automatic risk score adjustment based on verified fix status from FixChain attestations. When a vulnerability has a verified fix, the risk score is reduced proportionally to the verification confidence level.

Why This Matters

Current State With FixChain Integration
Risk scores ignore fix verification Fix confidence reduces risk
Binary matches = always vulnerable Verified fixes lower severity
No credit for patched backports Backport fixes recognized
Manual risk exceptions needed Automatic risk adjustment

Risk Adjustment Model

Verdict to Risk Modifier Mapping

Verdict Confidence Risk Modifier Rationale
fixed >= 95% -80% to -90% High-confidence verified fix
fixed 85-95% -60% to -80% Verified fix, some uncertainty
fixed 70-85% -40% to -60% Likely fixed, needs confirmation
fixed 60-70% -20% to -40% Possible fix, low confidence
fixed < 60% 0% Below threshold, no adjustment
partial >= 60% -25% to -50% Partial fix applied
inconclusive any 0% Cannot determine, conservative
still_vulnerable any 0% No fix detected
No attestation N/A 0% No verification performed

Modifier Formula

AdjustedRisk = BaseRisk * (1 - (Modifier * ConfidenceWeight))

Where:
  Modifier = verdict-based modifier from table above
  ConfidenceWeight = min(1.0, (Confidence - MinThreshold) / (1.0 - MinThreshold))

Example Calculation

CVE-2024-0727 on pkg:deb/debian/openssl@3.0.11-1~deb12u2:
  BaseRisk = 8.5 (HIGH)
  FixChain Verdict = "fixed"
  FixChain Confidence = 0.97

  Modifier = 0.90 (high confidence tier)
  ConfidenceWeight = (0.97 - 0.60) / (1.0 - 0.60) = 0.925

  AdjustedRisk = 8.5 * (1 - 0.90 * 0.925) = 8.5 * 0.1675 = 1.42 (LOW)

Components

IFixChainRiskProvider

Main interface for FixChain risk integration:

public interface IFixChainRiskProvider
{
    Task<FixVerificationStatus?> GetFixStatusAsync(
        string cveId,
        string binarySha256,
        string? componentPurl = null,
        CancellationToken ct = default);

    double ComputeRiskAdjustment(FixVerificationStatus status);

    FixChainRiskFactor CreateRiskFactor(FixVerificationStatus status);
}

FixChainRiskProvider

Implementation that:

  1. Queries the attestation store for FixChain predicates
  2. Computes risk adjustment based on verdict and confidence
  3. Creates structured risk factors for UI display

IFixChainAttestationClient

Client for querying attestations:

public interface IFixChainAttestationClient
{
    Task<FixChainAttestationData?> GetFixChainAsync(
        string cveId,
        string binarySha256,
        string? componentPurl = null,
        CancellationToken ct = default);

    Task<ImmutableArray<FixChainAttestationData>> GetForComponentAsync(
        string componentPurl,
        CancellationToken ct = default);
}

Configuration

YAML Configuration

RiskEngine:
  Providers:
    FixChain:
      Enabled: true
      HighConfidenceThreshold: 0.95
      MediumConfidenceThreshold: 0.85
      LowConfidenceThreshold: 0.70
      MinConfidenceThreshold: 0.60
      FixedReduction: 0.90
      PartialReduction: 0.50
      MaxRiskReduction: 0.90
      CacheMaxAgeHours: 24

Service Registration

services.AddOptions<FixChainRiskOptions>()
    .Bind(config.GetSection("RiskEngine:Providers:FixChain"))
    .ValidateDataAnnotations()
    .ValidateOnStart();

services.AddSingleton<IFixChainRiskProvider, FixChainRiskProvider>();
services.AddHttpClient<IFixChainAttestationClient, FixChainAttestationClient>();

Usage

Getting Fix Status

var provider = services.GetRequiredService<IFixChainRiskProvider>();

var status = await provider.GetFixStatusAsync(
    "CVE-2024-0727",
    binarySha256,
    componentPurl);

if (status is not null)
{
    var adjustment = provider.ComputeRiskAdjustment(status);
    var adjustedRisk = baseRisk * adjustment;
}

Creating Risk Factors

var status = await provider.GetFixStatusAsync(cveId, binarySha256);
if (status is not null)
{
    var factor = provider.CreateRiskFactor(status);

    // For UI display
    var display = factor.ToDisplay();
    var badge = factor.ToBadge();
    var summary = factor.ToSummary();
}

Signal-Based Scoring

For batch processing via signals:

var signals = new Dictionary<string, double>
{
    [FixChainRiskProvider.SignalFixConfidence] = 0.95,
    [FixChainRiskProvider.SignalFixStatus] = FixChainRiskProvider.EncodeStatus("fixed")
};

var request = new ScoreRequest("fixchain", subject, signals);
var adjustment = await provider.ScoreAsync(request, ct);

Metrics

The integration exposes the following OpenTelemetry metrics:

Metric Type Description
risk_fixchain_lookups_total Counter Total attestation lookups
risk_fixchain_hits_total Counter Attestations found
risk_fixchain_misses_total Counter Lookups with no attestation
risk_fixchain_cache_hits_total Counter Lookups served from cache
risk_fixchain_lookup_duration_seconds Histogram Lookup duration
risk_fixchain_adjustments_total Counter Risk adjustments applied
risk_fixchain_reduction_percent Histogram Reduction percentage distribution
risk_fixchain_errors_total Counter Lookup errors

Recording Metrics

// Automatically recorded by the provider, or manually:
FixChainRiskMetrics.RecordLookup(
    found: true,
    fromCache: false,
    durationSeconds: 0.05,
    verdict: "fixed");

FixChainRiskMetrics.RecordAdjustment(
    verdict: FixChainVerdictStatus.Fixed,
    confidence: 0.95m,
    reductionPercent: 0.80);

UI Integration

Display Model

var display = factor.ToDisplay();
// display.Label = "Fix Verification"
// display.Value = "Fixed (95% confidence)"
// display.Impact = -0.80
// display.ImpactDirection = "decrease"
// display.EvidenceRef = "fixchain://sha256:..."
// display.Details = { verdict, confidence, verified_at, ... }

Badge Component

var badge = factor.ToBadge();
// badge.Status = "Fixed"
// badge.Color = "green"
// badge.Icon = "check-circle"
// badge.Confidence = 0.95m
// badge.Tooltip = "Verified fix (95% confidence)"

Testing

Unit Tests

[Fact]
public async Task FixedVerdict_HighConfidence_ReturnsLowRisk()
{
    var provider = new FixChainRiskProvider(options);
    var status = new FixVerificationStatus
    {
        Verdict = "fixed",
        Confidence = 0.97m,
        VerifiedAt = DateTimeOffset.UtcNow,
        AttestationDigest = "sha256:test"
    };

    var adjustment = provider.ComputeRiskAdjustment(status);

    adjustment.Should().BeLessThan(0.3);
}

Integration Tests

[Fact]
public async Task FullWorkflow_FixedVerdict_ReducesRisk()
{
    var attestationClient = new InMemoryFixChainAttestationClient();
    attestationClient.AddAttestation(cveId, binarySha256, attestation);

    var provider = new FixChainRiskProvider(options, attestationClient, logger);
    var status = await provider.GetFixStatusAsync(cveId, binarySha256);

    status.Should().NotBeNull();
    status!.Verdict.Should().Be("fixed");
}

Decisions and Trade-offs

Decision Rationale
Conservative thresholds Start high, can lower based on accuracy data
No automatic upgrade Inconclusive doesn't increase risk
Cache TTL 30 minutes Balances freshness vs. performance
Attestation required No reduction without verifiable evidence
Minimum confidence 60% Below this, evidence is too weak for adjustment