20 KiB
Evidence Pipeline - Detailed Task Breakdown
Format: JIRA-compatible task definitions with acceptance criteria.
Epic: EVID-001 - Evidence Pipeline Consolidation
User Story: EVID-001-001 - CVE-to-Sink Mapping Service
As a scanner I want to look up vulnerable symbols (sinks) for a given CVE So that I can run targeted reachability analysis
Tasks
EVID-001-001-001: Create ICveSymbolMappingService interface
- Type: Task
- Component: Scanner.Reachability
- Effort: 0.5 days
- Priority: P0
Description: Define the interface for CVE-to-symbol mapping lookup.
Acceptance Criteria:
Given a CVE ID and PURL
When I call GetSinksForCveAsync
Then I receive a list of VulnerableSymbol records
And each symbol has name, canonical_id, file_path, and confidence
Technical Notes:
// Location: Scanner/__Libraries/StellaOps.Scanner.Reachability/Services/ICveSymbolMappingService.cs
public interface ICveSymbolMappingService
{
Task<IReadOnlyList<VulnerableSymbol>> GetSinksForCveAsync(
string cveId,
string purl,
CancellationToken ct);
Task<bool> HasMappingAsync(string cveId, CancellationToken ct);
Task<int> GetMappingCountAsync(CancellationToken ct);
}
public record VulnerableSymbol(
string SymbolName,
string? CanonicalId,
string? FilePath,
int? StartLine,
VulnerabilityType VulnType,
decimal Confidence);
Dependencies: None
EVID-001-001-002: Implement PostgresCveSymbolMappingRepository
- Type: Task
- Component: Scanner.Reachability
- Effort: 2 days
- Priority: P0
Description:
Implement the repository using existing reachability.cve_symbol_mappings schema.
Acceptance Criteria:
Given CVE-2021-44228 exists in the database
When I query for sinks with purl "pkg:maven/org.apache.logging.log4j/log4j-core"
Then I receive JndiLookup.lookup and JndiManager.lookup symbols
And confidence scores are included
Technical Notes:
// Uses existing schema from V20260110__reachability_cve_mapping_schema.sql
// EF Core entity mapping to reachability.cve_symbol_mappings table
Dependencies: EVID-001-001-001
EVID-001-001-003: Create CveSymbolMappingLoader
- Type: Task
- Component: Concelier
- Effort: 3 days
- Priority: P0
Description: Import CVE-to-symbol mappings from OSV, NVD, and patch analysis.
Acceptance Criteria:
Given an OSV advisory with affected symbols
When the loader processes the advisory
Then symbols are inserted into cve_symbol_mappings
And source is set to 'osv_advisory'
Given a git patch URL
When the loader analyzes the diff
Then changed functions are extracted
And inserted with source 'patch_analysis'
Technical Notes:
- Parse OSV
affected[].ranges[].eventsfor version info - Parse
affected[].ecosystem_specific.vulnerable_functionsfor symbols - For patch analysis, use existing diff parsing from Concelier.Analyzers
Dependencies: EVID-001-001-002
EVID-001-001-004: Create PatchAnalysisExtractor
- Type: Task
- Component: Scanner.Reachability
- Effort: 2 days
- Priority: P1
Description: Parse git diffs to extract changed function symbols.
Acceptance Criteria:
Given a git diff URL for a security patch
When I run the extractor
Then I receive a list of changed function names
And file paths and line numbers are included
And language is detected
Technical Notes:
public interface IPatchAnalysisExtractor
{
Task<PatchAnalysisResult> ExtractAsync(string commitUrl, CancellationToken ct);
}
public record PatchAnalysisResult(
string CommitUrl,
string? Language,
IReadOnlyList<ExtractedSymbol> Symbols,
string? Error);
Dependencies: None
EVID-001-001-005: Wire to Concelier CVE enrichment
- Type: Task
- Component: Concelier
- Effort: 2 days
- Priority: P0
Description: Enrich CVE data with sink mappings during ingestion.
Acceptance Criteria:
Given a new CVE is ingested from NVD
When Concelier processes it
Then it checks for OSV symbol data
And creates cve_symbol_mappings entries if found
And marks CVE as "has_reachability_data" = true
Dependencies: EVID-001-001-003
User Story: EVID-001-002 - Reachability Evidence Job
As a scanner I want to queue reachability analysis for a specific CVE+image So that I get an evidence-backed verdict
Tasks
EVID-001-002-001: Create ReachabilityEvidenceJob model
- Type: Task
- Component: Scanner.Queue
- Effort: 1 day
- Priority: P0
Description: Define the job model for queued reachability analysis.
Acceptance Criteria:
Given a reachability job request
When serialized and deserialized
Then all fields are preserved
And job ID is deterministic from inputs hash
Technical Notes:
public record ReachabilityEvidenceJob(
string JobId,
string ImageDigest,
string CveId,
string Purl,
string? SourceCommit,
ReachabilityJobOptions Options,
DateTimeOffset QueuedAt);
public record ReachabilityJobOptions(
bool IncludeL2 = false, // Binary resolution
bool IncludeL3 = false, // Runtime (if available)
int MaxPaths = 5,
int MaxDepth = 256);
Dependencies: None
EVID-001-002-002: Create ReachabilityEvidenceJobExecutor
- Type: Task
- Component: Scanner.Worker
- Effort: 3 days
- Priority: P0
Description: Implement the job executor that orchestrates L1 analysis.
Acceptance Criteria:
Given a queued reachability job
When the executor processes it
Then it retrieves or computes CallGraphSnapshot
And runs ReachabilityAnalyzer with CVE sinks
And produces ReachabilityStack with L1 populated
And stores EvidenceBundle in database
Technical Notes:
public sealed class ReachabilityEvidenceJobExecutor : IJobExecutor<ReachabilityEvidenceJob>
{
// Inject: ICveSymbolMappingService, ICallGraphCache, ReachabilityAnalyzer,
// ReachabilityStackEvaluator, IEvidenceStore
public async Task<ReachabilityStack> ExecuteAsync(
ReachabilityEvidenceJob job,
CancellationToken ct)
{
// 1. Get sinks from CVE mapping service
var sinks = await _cveSymbolService.GetSinksForCveAsync(job.CveId, job.Purl, ct);
// 2. Get or compute call graph
var graph = await _callGraphCache.GetOrComputeAsync(job.ImageDigest, ct);
// 3. Run reachability analysis
var analysisResult = _analyzer.Analyze(graph, new ReachabilityAnalysisOptions
{
ExplicitSinks = sinks.Select(s => s.CanonicalId ?? s.SymbolName).ToImmutableArray(),
MaxTotalPaths = job.Options.MaxPaths,
MaxDepth = job.Options.MaxDepth
});
// 4. Build Layer 1
var layer1 = BuildLayer1(analysisResult);
// 5. Evaluate stack (L2, L3 as Unknown initially)
var stack = _stackEvaluator.Evaluate(
findingId: $"{job.CveId}:{job.Purl}",
symbol: sinks.FirstOrDefault() ?? VulnerableSymbol.Unknown,
layer1: layer1,
layer2: ReachabilityLayer2.Unknown(),
layer3: ReachabilityLayer3.Unknown());
// 6. Store evidence
await _evidenceStore.StoreAsync(stack.ToEvidenceBundle(), ct);
return stack;
}
}
Dependencies: EVID-001-001-001, EVID-001-002-001
EVID-001-002-003: Wire CallGraphSnapshot retrieval
- Type: Task
- Component: Scanner.Worker
- Effort: 2 days
- Priority: P0
Description: Integrate with existing call graph cache/computation.
Acceptance Criteria:
Given an image digest with existing call graph
When the job requests it
Then the cached graph is returned
Given an image digest without cached call graph
When the job requests it
Then the graph is computed
And cached for future requests
Dependencies: None (uses existing CallGraph infrastructure)
EVID-001-002-004: Emit ReachabilityStack with L1 analysis
- Type: Task
- Component: Scanner.Reachability
- Effort: 1 day
- Priority: P0
Description: Convert ReachabilityAnalysisResult to ReachabilityLayer1.
Acceptance Criteria:
Given a ReachabilityAnalysisResult with paths
When converted to Layer1
Then IsReachable is true
And Paths contains the converted paths
And ReachingEntrypoints lists unique entrypoints
And Confidence is High
Given a ReachabilityAnalysisResult with no paths
When converted to Layer1
Then IsReachable is false
And Confidence is Medium
Dependencies: EVID-001-002-002
EVID-001-002-005: Store result in EvidenceDbContext
- Type: Task
- Component: Evidence.Persistence
- Effort: 1 day
- Priority: P0
Description: Persist the ReachabilityStack as an EvidenceBundle.
Acceptance Criteria:
Given a ReachabilityStack
When stored
Then an EvidenceBundle is created
And ReachabilityEvidence is included
And the bundle ID is returned
And it can be retrieved by ID
Dependencies: None (uses existing EvidenceDbContext)
EVID-001-002-006: Add WebService endpoint
- Type: Task
- Component: Scanner.WebService
- Effort: 1 day
- Priority: P0
Description: Expose reachability analysis via REST API.
Acceptance Criteria:
Given a POST to /api/reachability/analyze
With body { imageDigest, cveId, purl }
When the request is processed
Then a job is queued
And the job ID is returned
Given a GET to /api/reachability/result/{jobId}
When the job is complete
Then the ReachabilityStack is returned
And evidence bundle URI is included
Technical Notes:
// Location: Scanner.WebService/Endpoints/ReachabilityEvidenceEndpoints.cs
public static class ReachabilityEvidenceEndpoints
{
public static void MapReachabilityEvidenceEndpoints(this IEndpointRouteBuilder routes)
{
routes.MapPost("/api/reachability/analyze", AnalyzeAsync);
routes.MapGet("/api/reachability/result/{jobId}", GetResultAsync);
}
}
Dependencies: EVID-001-002-002
User Story: EVID-001-003 - VEX Integration
As a security team I want reachability verdicts to automatically update VEX status So that I don't manually triage unreachable vulnerabilities
Tasks
EVID-001-003-001: Create IVexStatusDeterminer interface
- Type: Task
- Component: VexHub
- Effort: 0.5 days
- Priority: P0
Description: Define interface for verdict-to-VEX mapping.
Technical Notes:
public interface IVexStatusDeterminer
{
VexStatus DetermineStatus(ReachabilityVerdict verdict);
VexJustification BuildJustification(
ReachabilityStack stack,
IReadOnlyList<string> evidenceUris);
}
Dependencies: None
EVID-001-003-002: Implement verdict to VEX status mapping
- Type: Task
- Component: VexHub
- Effort: 2 days
- Priority: P0
Description: Map ReachabilityVerdict to CycloneDX VEX status.
Acceptance Criteria:
Given verdict Exploitable
When mapped to VEX
Then status is "affected"
And impact_statement includes path summary
Given verdict Unreachable
When mapped to VEX
Then status is "not_affected"
And justification includes "code_not_reachable"
Technical Notes:
public VexStatus DetermineStatus(ReachabilityVerdict verdict) => verdict switch
{
ReachabilityVerdict.Exploitable => VexStatus.Affected,
ReachabilityVerdict.LikelyExploitable => VexStatus.Affected,
ReachabilityVerdict.PossiblyExploitable => VexStatus.UnderInvestigation,
ReachabilityVerdict.Unreachable => VexStatus.NotAffected,
ReachabilityVerdict.Unknown => VexStatus.UnderInvestigation,
_ => VexStatus.UnderInvestigation
};
Dependencies: EVID-001-003-001
EVID-001-003-003: Create ReachabilityVexJustificationBuilder
- Type: Task
- Component: VexHub
- Effort: 2 days
- Priority: P0
Description: Build VEX justification from reachability evidence.
Acceptance Criteria:
Given a ReachabilityStack with Unreachable verdict
When justification is built
Then detail includes "No call path from entrypoints to vulnerable symbol"
And layer summaries are included
And evidence URIs are referenced
Dependencies: EVID-001-003-002
EVID-001-003-004: Wire to VexHub emission
- Type: Task
- Component: VexHub
- Effort: 3 days
- Priority: P0
Description: Automatically emit VEX documents when reachability evidence is produced.
Acceptance Criteria:
Given a new ReachabilityStack is stored
When the VEX bridge processes it
Then a VEX statement is created for the CVE+component
And it references the evidence bundle
And it's stored in VexHub
Dependencies: EVID-001-003-003
EVID-001-003-005: Add evidence URI to VEX justification
- Type: Task
- Component: VexHub
- Effort: 1 day
- Priority: P0
Description: Include evidence bundle URI in VEX document.
Acceptance Criteria:
Given a VEX document with reachability evidence
When serialized to CycloneDX
Then analysis.detail includes evidence URI
And URI follows stella:// scheme
Dependencies: EVID-001-003-004
User Story: EVID-001-004 - Runtime Observation
As a security team I want runtime execution data to refine reachability verdicts So that I know if vulnerable code is actually running
Tasks
EVID-001-004-001: Create IRuntimeCaptureAdapter interface
- Type: Task
- Component: Scanner.Analyzers.Native
- Effort: 1 day
- Priority: P0
Description: Define the interface for runtime capture backends.
Technical Notes:
// Location: Scanner.Analyzers.Native/RuntimeCapture/IRuntimeCaptureAdapter.cs
public interface IRuntimeCaptureAdapter
{
string Platform { get; } // "linux", "windows", "macos"
string Method { get; } // "ebpf", "etw", "dyld-interpose"
Task<RuntimeCaptureSession> StartSessionAsync(
RuntimeCaptureOptions options,
CancellationToken ct);
Task StopSessionAsync(string sessionId, CancellationToken ct);
IAsyncEnumerable<RuntimeLoadEvent> StreamEventsAsync(
string sessionId,
CancellationToken ct);
Task<RuntimeEvidence> GetEvidenceAsync(
string sessionId,
CancellationToken ct);
}
Dependencies: None (uses existing RuntimeEvidence models)
EVID-001-004-002: Implement TetragonAdapter
- Type: Task
- Component: Scanner.Analyzers.Native
- Effort: 5 days
- Priority: P1
Description: Implement runtime capture using Cilium Tetragon.
Acceptance Criteria:
Given Tetragon is running in the cluster
When a capture session is started
Then tracing policies are applied
And library load events are captured
And events are correlated to container digest
Given a session is stopped
When evidence is requested
Then all captured events are returned
And unique libraries are summarized
Technical Notes:
- Use Tetragon gRPC API for event streaming
- Apply TracingPolicy for library loads
- Correlate events via cgroup/container ID
Dependencies: EVID-001-004-001
EVID-001-004-003: Implement EtwAdapter (Windows)
- Type: Task
- Component: Scanner.Analyzers.Native
- Effort: 3 days
- Priority: P2
Description: Implement runtime capture using Windows ETW.
Acceptance Criteria:
Given ETW providers are available
When a capture session is started
Then DLL load events are captured
And events include process and timestamp
Dependencies: EVID-001-004-001
EVID-001-004-004: Create RuntimeEvidenceCollector service
- Type: Task
- Component: Scanner.Worker
- Effort: 2 days
- Priority: P1
Description: Orchestrate runtime evidence collection for a target.
Acceptance Criteria:
Given a container image running in the cluster
When the collector is invoked
Then it selects the appropriate adapter
And starts a session for the configured duration
And stores the runtime evidence
Dependencies: EVID-001-004-002
EVID-001-004-005: Wire to existing RuntimeEvidence models
- Type: Task
- Component: Scanner.Analyzers.Native
- Effort: 1 day
- Priority: P1
Description: Ensure adapters produce existing RuntimeEvidence types.
Dependencies: EVID-001-004-004
User Story: EVID-001-005 - Binary Patch Verification
As a security team I want to verify if a binary is actually patched So that I trust distro backport claims
Tasks
EVID-001-005-001: Create IBinaryDiffService interface
- Type: Task
- Component: Scanner.Reachability
- Effort: 1 day
- Priority: P1
Description: Define interface for binary comparison.
Technical Notes:
public interface IBinaryDiffService
{
Task<PatchDiffResult> DiffAsync(
Stream vulnerableBinary,
Stream patchedBinary,
IReadOnlyList<string> targetSymbols,
CancellationToken ct);
}
public record PatchDiffResult(
bool IsPatched,
IReadOnlyList<FunctionDiff> ChangedFunctions,
double SimilarityScore,
string DiffSummary,
byte[]? DiffArtifact);
public record FunctionDiff(
string FunctionName,
ulong OriginalAddress,
ulong PatchedAddress,
int InstructionChanges,
double Similarity);
Dependencies: None
EVID-001-005-002: Implement B2R2BinaryDiffService
- Type: Task
- Component: Scanner.Reachability
- Effort: 5 days
- Priority: P1
Description: Implement binary diff using B2R2 (already in dependencies).
Acceptance Criteria:
Given two versions of a binary (vulnerable and patched)
When diffed for a target symbol
Then changed basic blocks are identified
And similarity score is computed
And IsPatched is true if significant changes detected
Technical Notes:
- Use B2R2.FrontEnd for disassembly
- Use B2R2.MiddleEnd for IR comparison
- Compare function CFGs for similarity
Dependencies: EVID-001-005-001
EVID-001-005-003: Create function similarity matching
- Type: Task
- Component: Scanner.Reachability
- Effort: 3 days
- Priority: P1
Description: Match functions between binaries even with different addresses.
Acceptance Criteria:
Given a function name from CVE mapping
When searching in patched binary
Then the function is located by name
Or by signature similarity if renamed
And match confidence is returned
Dependencies: EVID-001-005-002
EVID-001-005-004: Create PatchDiffEvidence model
- Type: Task
- Component: Evidence.Bundle
- Effort: 1 day
- Priority: P1
Description: Add patch diff evidence to the bundle types.
Technical Notes:
// Location: Evidence.Bundle/PatchDiffEvidence.cs
public sealed class PatchDiffEvidence
{
public required EvidenceStatus Status { get; init; }
public string? Hash { get; init; }
public bool IsPatched { get; init; }
public double SimilarityScore { get; init; }
public IReadOnlyList<ChangedFunctionSummary>? ChangedFunctions { get; init; }
public string? DiffSummary { get; init; }
public string? ArtifactUri { get; init; }
}
public record ChangedFunctionSummary(
string FunctionName,
int InstructionChanges,
double Similarity);
Dependencies: None
EVID-001-005-005: Add to evidence bundle
- Type: Task
- Component: Evidence.Bundle
- Effort: 1 day
- Priority: P1
Description: Include PatchDiffEvidence in EvidenceBundle.
Dependencies: EVID-001-005-004
Summary Statistics
| Category | Count |
|---|---|
| Epics | 1 |
| User Stories | 5 |
| Tasks | 25 |
| Total Effort | ~50 days |
Sprint Assignment Suggestion
| Sprint | Stories | Effort |
|---|---|---|
| S1 | EVID-001-001 (CVE Mapping) | 10 days |
| S2 | EVID-001-002 (Evidence Job) | 9 days |
| S3 | EVID-001-003 (VEX Integration) | 8.5 days |
| S4 | EVID-001-004 (Runtime - part 1) | 9 days |
| S5 | EVID-001-004 (Runtime - part 2) + EVID-001-005 (Binary Diff) | 13 days |