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>
297 lines
8.0 KiB
Markdown
297 lines
8.0 KiB
Markdown
# 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:
|
|
|
|
```csharp
|
|
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:
|
|
|
|
```csharp
|
|
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
|
|
|
|
```yaml
|
|
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
|
|
|
|
```csharp
|
|
services.AddOptions<FixChainRiskOptions>()
|
|
.Bind(config.GetSection("RiskEngine:Providers:FixChain"))
|
|
.ValidateDataAnnotations()
|
|
.ValidateOnStart();
|
|
|
|
services.AddSingleton<IFixChainRiskProvider, FixChainRiskProvider>();
|
|
services.AddHttpClient<IFixChainAttestationClient, FixChainAttestationClient>();
|
|
```
|
|
|
|
## Usage
|
|
|
|
### Getting Fix Status
|
|
|
|
```csharp
|
|
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
|
|
|
|
```csharp
|
|
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:
|
|
|
|
```csharp
|
|
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
|
|
|
|
```csharp
|
|
// 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
|
|
|
|
```csharp
|
|
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
|
|
|
|
```csharp
|
|
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
|
|
|
|
```csharp
|
|
[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
|
|
|
|
```csharp
|
|
[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 |
|
|
|
|
## Related Documentation
|
|
|
|
- [FixChain Attestation Predicate](../attestor/fix-chain-predicate.md)
|
|
- [Golden Set Schema](../binary-index/golden-set-schema.md)
|
|
- [Risk Engine Architecture](./architecture.md)
|