# 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**: ```gherkin 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**: ```csharp // Location: Scanner/__Libraries/StellaOps.Scanner.Reachability/Services/ICveSymbolMappingService.cs public interface ICveSymbolMappingService { Task> GetSinksForCveAsync( string cveId, string purl, CancellationToken ct); Task HasMappingAsync(string cveId, CancellationToken ct); Task 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**: ```gherkin 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**: ```csharp // 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**: ```gherkin 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[].events` for version info - Parse `affected[].ecosystem_specific.vulnerable_functions` for 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**: ```gherkin 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**: ```csharp public interface IPatchAnalysisExtractor { Task ExtractAsync(string commitUrl, CancellationToken ct); } public record PatchAnalysisResult( string CommitUrl, string? Language, IReadOnlyList 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**: ```gherkin 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**: ```gherkin Given a reachability job request When serialized and deserialized Then all fields are preserved And job ID is deterministic from inputs hash ``` **Technical Notes**: ```csharp 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**: ```gherkin 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**: ```csharp public sealed class ReachabilityEvidenceJobExecutor : IJobExecutor { // Inject: ICveSymbolMappingService, ICallGraphCache, ReachabilityAnalyzer, // ReachabilityStackEvaluator, IEvidenceStore public async Task 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**: ```gherkin 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**: ```gherkin 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**: ```gherkin 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**: ```gherkin 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**: ```csharp // 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**: ```csharp public interface IVexStatusDeterminer { VexStatus DetermineStatus(ReachabilityVerdict verdict); VexJustification BuildJustification( ReachabilityStack stack, IReadOnlyList 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**: ```gherkin 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**: ```csharp 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**: ```gherkin 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**: ```gherkin 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**: ```gherkin 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**: ```csharp // Location: Scanner.Analyzers.Native/RuntimeCapture/IRuntimeCaptureAdapter.cs public interface IRuntimeCaptureAdapter { string Platform { get; } // "linux", "windows", "macos" string Method { get; } // "ebpf", "etw", "dyld-interpose" Task StartSessionAsync( RuntimeCaptureOptions options, CancellationToken ct); Task StopSessionAsync(string sessionId, CancellationToken ct); IAsyncEnumerable StreamEventsAsync( string sessionId, CancellationToken ct); Task 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**: ```gherkin 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**: ```gherkin 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**: ```gherkin 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**: ```csharp public interface IBinaryDiffService { Task DiffAsync( Stream vulnerableBinary, Stream patchedBinary, IReadOnlyList targetSymbols, CancellationToken ct); } public record PatchDiffResult( bool IsPatched, IReadOnlyList 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**: ```gherkin 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**: ```gherkin 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**: ```csharp // 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? 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 |