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>
8.0 KiB
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:
- Queries the attestation store for FixChain predicates
- Computes risk adjustment based on verdict and confidence
- 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 |