feat: add stella-callgraph-node for JavaScript/TypeScript call graph extraction
- Implemented a new tool `stella-callgraph-node` that extracts call graphs from JavaScript/TypeScript projects using Babel AST. - Added command-line interface with options for JSON output and help. - Included functionality to analyze project structure, detect functions, and build call graphs. - Created a package.json file for dependency management. feat: introduce stella-callgraph-python for Python call graph extraction - Developed `stella-callgraph-python` to extract call graphs from Python projects using AST analysis. - Implemented command-line interface with options for JSON output and verbose logging. - Added framework detection to identify popular web frameworks and their entry points. - Created an AST analyzer to traverse Python code and extract function definitions and calls. - Included requirements.txt for project dependencies. chore: add framework detection for Python projects - Implemented framework detection logic to identify frameworks like Flask, FastAPI, Django, and others based on project files and import patterns. - Enhanced the AST analyzer to recognize entry points based on decorators and function definitions.
This commit is contained in:
964
docs/architecture/signal-contract-mapping.md
Normal file
964
docs/architecture/signal-contract-mapping.md
Normal file
@@ -0,0 +1,964 @@
|
||||
# Signal Contract Mapping: Advisory ↔ StellaOps
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** 2025-12-19
|
||||
**Status:** ACTIVE
|
||||
**Related Sprint:** SPRINT_5000_0001_0001
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document provides a comprehensive mapping between the reference advisory's **Signal-based message contracts (10/12/14/16/18)** and the **StellaOps implementation**. While StellaOps uses domain-specific terminology, all signal concepts are fully implemented with equivalent or superior functionality.
|
||||
|
||||
**Key Insight:** StellaOps implements the same architectural patterns as the advisory but uses domain-specific entity names instead of generic "Signal-X" labels. This provides better type safety, code readability, and domain modeling while maintaining conceptual alignment.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Table
|
||||
|
||||
| Advisory Signal | StellaOps Equivalent | Module | Key Files |
|
||||
|----------------|---------------------|---------|-----------|
|
||||
| **Signal-10** (SBOM Intake) | `CallgraphIngestRequest`, `ISbomIngestionService` | Scanner, Signals | `SbomIngestionService.cs`, `CallgraphIngestRequest.cs` |
|
||||
| **Signal-12** (Evidence/Attestation) | in-toto `Statement` + DSSE | Attestor, Signer | `InTotoStatement.cs`, `DsseEnvelope.cs`, 19 predicate types |
|
||||
| **Signal-14** (Triage Fact) | `TriageFinding` + related entities | Scanner.Triage | `TriageFinding.cs`, `TriageReachabilityResult.cs`, `TriageRiskResult.cs`, `TriageEffectiveVex.cs` |
|
||||
| **Signal-16** (Diff Delta) | `TriageSnapshot`, `MaterialRiskChange`, `DriftCause` | Scanner.SmartDiff, ReachabilityDrift | `MaterialRiskChangeDetector.cs`, `ReachabilityDriftDetector.cs`, `TriageSnapshot.cs` |
|
||||
| **Signal-18** (Decision) | `TriageDecision` + DSSE signatures | Scanner.Triage | `TriageDecision.cs`, `TriageEvidenceArtifact.cs` |
|
||||
|
||||
---
|
||||
|
||||
## Signal-10: SBOM Intake
|
||||
|
||||
### Advisory Specification
|
||||
|
||||
```json
|
||||
{
|
||||
"bom": "(cyclonedx:1.7)",
|
||||
"subject": {
|
||||
"image": "ghcr.io/org/app@sha256:...",
|
||||
"digest": "sha256:..."
|
||||
},
|
||||
"source": "scanner-instance-1",
|
||||
"scanProfile": "default",
|
||||
"createdAt": "2025-12-19T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Purpose:** Initial SBOM ingestion with subject identification
|
||||
|
||||
---
|
||||
|
||||
### StellaOps Implementation
|
||||
|
||||
**Primary Contract:** `CallgraphIngestRequest`
|
||||
|
||||
**Location:** `src/Signals/StellaOps.Signals/Models/CallgraphIngestRequest.cs`
|
||||
|
||||
```csharp
|
||||
public sealed record CallgraphIngestRequest
|
||||
{
|
||||
public required string TenantId { get; init; }
|
||||
public required string ArtifactDigest { get; init; } // Maps to "subject.digest"
|
||||
public required string Language { get; init; }
|
||||
public required string Component { get; init; }
|
||||
public required string? Version { get; init; }
|
||||
public required string ArtifactContentBase64 { get; init; } // Maps to "bom" (encoded)
|
||||
public string? SchemaVersion { get; init; }
|
||||
public IReadOnlyDictionary<string, string>? Metadata { get; init; } // Includes "source", "scanProfile"
|
||||
}
|
||||
```
|
||||
|
||||
**Service Interface:** `ISbomIngestionService`
|
||||
|
||||
**Location:** `src/Scanner/StellaOps.Scanner.WebService/Services/ISbomIngestionService.cs`
|
||||
|
||||
```csharp
|
||||
public interface ISbomIngestionService
|
||||
{
|
||||
Task<SbomIngestionResult> IngestCycloneDxAsync(
|
||||
string tenantId,
|
||||
Stream cycloneDxJson,
|
||||
SbomIngestionOptions options,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
Task<SbomIngestionResult> IngestSpdxAsync(
|
||||
string tenantId,
|
||||
Stream spdxJson,
|
||||
SbomIngestionOptions options,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
```
|
||||
|
||||
**Data Flow:**
|
||||
|
||||
```
|
||||
[Scanner] → SbomIngestionService → [CycloneDxComposer/SpdxComposer]
|
||||
↓
|
||||
PostgreSQL (scanner.sboms)
|
||||
↓
|
||||
Event: "sbom.ingested"
|
||||
↓
|
||||
[Downstream processors]
|
||||
```
|
||||
|
||||
**API Endpoints:**
|
||||
|
||||
- `POST /api/scanner/sboms/ingest` - Direct SBOM ingestion
|
||||
- `POST /api/signals/callgraph/ingest` - Call graph + SBOM ingestion
|
||||
|
||||
**Related Files:**
|
||||
- `src/Scanner/StellaOps.Scanner.WebService/Endpoints/SbomEndpoints.cs`
|
||||
- `src/Signals/StellaOps.Signals/Services/CallgraphIngestionService.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Emit/Composition/CycloneDxComposer.cs`
|
||||
|
||||
**Equivalence Proof:**
|
||||
- ✅ BOM content: CycloneDX 1.6 (upgrading to 1.7)
|
||||
- ✅ Subject identification: `ArtifactDigest` (SHA-256)
|
||||
- ✅ Source tracking: `Metadata["source"]`
|
||||
- ✅ Profile support: `SbomIngestionOptions.ScanProfile`
|
||||
- ✅ Timestamp: `CreatedAt` in database entity
|
||||
|
||||
---
|
||||
|
||||
## Signal-12: Evidence/Attestation (in-toto Statement)
|
||||
|
||||
### Advisory Specification
|
||||
|
||||
```json
|
||||
{
|
||||
"subject": {"digest": {"sha256": "..."}},
|
||||
"type": "attestation",
|
||||
"predicateType": "https://slsa.dev/provenance/v1",
|
||||
"predicate": {...},
|
||||
"materials": [],
|
||||
"tool": "scanner@1.0.0",
|
||||
"runId": "run-123",
|
||||
"startedAt": "2025-12-19T10:00:00Z",
|
||||
"finishedAt": "2025-12-19T10:05:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Purpose:** Evidence envelopes for attestations (DSSE-wrapped)
|
||||
|
||||
---
|
||||
|
||||
### StellaOps Implementation
|
||||
|
||||
**Primary Contract:** `InTotoStatement` (abstract base)
|
||||
|
||||
**Location:** `src/Attestor/__Libraries/StellaOps.Attestor.ProofChain/Statements/InTotoStatement.cs`
|
||||
|
||||
```csharp
|
||||
public abstract record InTotoStatement
|
||||
{
|
||||
[JsonPropertyName("_type")]
|
||||
public string Type => "https://in-toto.io/Statement/v1";
|
||||
|
||||
[JsonPropertyName("subject")]
|
||||
public required IReadOnlyList<Subject> Subject { get; init; }
|
||||
|
||||
[JsonPropertyName("predicateType")]
|
||||
public abstract string PredicateType { get; }
|
||||
}
|
||||
```
|
||||
|
||||
**DSSE Envelope:** `DsseEnvelope`
|
||||
|
||||
**Location:** `src/Attestor/StellaOps.Attestor.Envelope/DsseEnvelope.cs`
|
||||
|
||||
```csharp
|
||||
public sealed record DsseEnvelope
|
||||
{
|
||||
[JsonPropertyName("payload")]
|
||||
public required string Payload { get; init; } // Base64url(canonical JSON of Statement)
|
||||
|
||||
[JsonPropertyName("payloadType")]
|
||||
public required string PayloadType { get; init; } // "application/vnd.in-toto+json"
|
||||
|
||||
[JsonPropertyName("signatures")]
|
||||
public required IReadOnlyList<DsseSignature> Signatures { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
**Predicate Types Registry:** 19 types supported
|
||||
|
||||
**Location:** `src/Signer/StellaOps.Signer/StellaOps.Signer.Core/PredicateTypes.cs`
|
||||
|
||||
```csharp
|
||||
public static class PredicateTypes
|
||||
{
|
||||
// SLSA (Standard)
|
||||
public const string SlsaProvenanceV02 = "https://slsa.dev/provenance/v0.2";
|
||||
public const string SlsaProvenanceV1 = "https://slsa.dev/provenance/v1";
|
||||
|
||||
// StellaOps Custom
|
||||
public const string StellaOpsSbom = "stella.ops/sbom@v1";
|
||||
public const string StellaOpsVex = "stella.ops/vex@v1";
|
||||
public const string StellaOpsEvidence = "stella.ops/evidence@v1";
|
||||
public const string StellaOpsPathWitness = "stella.ops/pathWitness@v1";
|
||||
public const string StellaOpsReachabilityWitness = "stella.ops/reachabilityWitness@v1";
|
||||
public const string StellaOpsReachabilityDrift = "stellaops.dev/predicates/reachability-drift@v1";
|
||||
public const string StellaOpsPolicyDecision = "stella.ops/policy-decision@v1";
|
||||
// ... 12 more predicate types
|
||||
}
|
||||
```
|
||||
|
||||
**Signing Service:** `CryptoDsseSigner`
|
||||
|
||||
**Location:** `src/Signer/StellaOps.Signer/StellaOps.Signer.Infrastructure/Signing/CryptoDsseSigner.cs`
|
||||
|
||||
**Data Flow:**
|
||||
|
||||
```
|
||||
[Component] → ProofChainSigner → [Build in-toto Statement]
|
||||
↓
|
||||
Canonical JSON serialization
|
||||
↓
|
||||
DSSE PAE construction
|
||||
↓
|
||||
CryptoDsseSigner (KMS/Keyless)
|
||||
↓
|
||||
DsseEnvelope (signed)
|
||||
↓
|
||||
PostgreSQL (attestor.envelopes)
|
||||
↓
|
||||
Optional: Rekor transparency log
|
||||
```
|
||||
|
||||
**Sample Attestation Files:**
|
||||
- `src/Attestor/StellaOps.Attestor.Types/samples/build-provenance.v1.json`
|
||||
- `src/Attestor/StellaOps.Attestor.Types/samples/vex-attestation.v1.json`
|
||||
- `src/Attestor/StellaOps.Attestor.Types/samples/scan-results.v1.json`
|
||||
|
||||
**Equivalence Proof:**
|
||||
- ✅ Subject: `Subject` list with digests
|
||||
- ✅ Type: `https://in-toto.io/Statement/v1`
|
||||
- ✅ PredicateType: 19 supported types
|
||||
- ✅ Predicate: Custom per type
|
||||
- ✅ Tool: Embedded in predicate metadata
|
||||
- ✅ RunId: `TraceId` / `CorrelationId`
|
||||
- ✅ Timestamps: In predicate metadata
|
||||
- ✅ DSSE wrapping: Full implementation
|
||||
|
||||
---
|
||||
|
||||
## Signal-14: Triage Fact
|
||||
|
||||
### Advisory Specification
|
||||
|
||||
```json
|
||||
{
|
||||
"subject": "pkg:npm/lodash@4.17.0",
|
||||
"cve": "CVE-2024-12345",
|
||||
"findingId": "cve@package@symbol@subjectDigest",
|
||||
"location": {
|
||||
"file": "src/index.js",
|
||||
"package": "lodash",
|
||||
"symbol": "template"
|
||||
},
|
||||
"reachability": {
|
||||
"status": "reachable",
|
||||
"callStackId": "cs-abc123"
|
||||
},
|
||||
"epss": 0.85,
|
||||
"cvss": {
|
||||
"version": "4.0",
|
||||
"vector": "CVSS:4.0/AV:N/AC:L/...",
|
||||
"score": 7.5
|
||||
},
|
||||
"vexStatus": "affected",
|
||||
"notes": "...",
|
||||
"evidenceRefs": ["dsse://sha256:..."]
|
||||
}
|
||||
```
|
||||
|
||||
**Purpose:** Triage facts per CVE with reachability, scoring, and VEX status
|
||||
|
||||
---
|
||||
|
||||
### StellaOps Implementation
|
||||
|
||||
**Primary Entity:** `TriageFinding` (core entity tying all triage data)
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Entities/TriageFinding.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageFinding
|
||||
{
|
||||
public string FindingId { get; set; } // Stable ID: "cve@purl@scanId"
|
||||
public string TenantId { get; set; }
|
||||
public string AssetId { get; set; } // Maps to "subject"
|
||||
public string? Purl { get; set; } // Package URL
|
||||
public string? CveId { get; set; } // Maps to "cve"
|
||||
public string? RuleId { get; set; } // For non-CVE findings
|
||||
|
||||
public DateTimeOffset FirstSeenAt { get; set; }
|
||||
public DateTimeOffset LastSeenAt { get; set; }
|
||||
|
||||
// Navigation properties
|
||||
public TriageReachabilityResult? Reachability { get; set; }
|
||||
public TriageRiskResult? Risk { get; set; }
|
||||
public TriageEffectiveVex? EffectiveVex { get; set; }
|
||||
public ICollection<TriageEvidenceArtifact> EvidenceArtifacts { get; set; }
|
||||
public ICollection<TriageDecision> Decisions { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**Reachability Component:** `TriageReachabilityResult`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Entities/TriageReachabilityResult.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageReachabilityResult
|
||||
{
|
||||
public string ResultId { get; set; }
|
||||
public string FindingId { get; set; }
|
||||
|
||||
public TriageReachability Reachability { get; set; } // Yes, No, Unknown
|
||||
public int Confidence { get; set; } // 0-100
|
||||
|
||||
public string? StaticProofRef { get; set; } // Maps to "callStackId"
|
||||
public string? RuntimeProofRef { get; set; }
|
||||
|
||||
public string InputsHash { get; set; } // For caching/diffing
|
||||
public DateTimeOffset ComputedAt { get; set; }
|
||||
|
||||
// Lattice evaluation
|
||||
public double? LatticeScore { get; set; }
|
||||
public string? LatticeState { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**Risk/Scoring Component:** `TriageRiskResult`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Entities/TriageRiskResult.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageRiskResult
|
||||
{
|
||||
public string ResultId { get; set; }
|
||||
public string FindingId { get; set; }
|
||||
|
||||
// Scoring
|
||||
public double RiskScore { get; set; } // Combined score
|
||||
public double? CvssBaseScore { get; set; }
|
||||
public string? CvssVector { get; set; } // CVSS:4.0/AV:N/...
|
||||
public string? CvssVersion { get; set; } // "4.0"
|
||||
|
||||
public double? EpssScore { get; set; } // Maps to "epss"
|
||||
public double? EpssPercentile { get; set; }
|
||||
public DateOnly? EpssModelDate { get; set; }
|
||||
|
||||
// Policy decision
|
||||
public TriageVerdict Verdict { get; set; } // Ship, Block, Exception
|
||||
public string? PolicyId { get; set; }
|
||||
public string Lane { get; set; } // Critical, High, Medium, Low
|
||||
|
||||
public string InputsHash { get; set; }
|
||||
public string? LatticeExplanationJson { get; set; } // Maps to "notes"
|
||||
public DateTimeOffset ComputedAt { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**VEX Component:** `TriageEffectiveVex`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Entities/TriageEffectiveVex.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageEffectiveVex
|
||||
{
|
||||
public string VexId { get; set; }
|
||||
public string FindingId { get; set; }
|
||||
|
||||
public VexClaimStatus Status { get; set; } // Maps to "vexStatus"
|
||||
public VexJustification? Justification { get; set; }
|
||||
|
||||
public string? ProvenancePointer { get; set; } // Linkset reference
|
||||
public string? DsseEnvelopeHash { get; set; } // Maps to "evidenceRefs"
|
||||
|
||||
public DateTimeOffset EffectiveAt { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**Evidence Artifacts:** `TriageEvidenceArtifact`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Entities/TriageEvidenceArtifact.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageEvidenceArtifact
|
||||
{
|
||||
public string ArtifactId { get; set; }
|
||||
public string FindingId { get; set; }
|
||||
|
||||
public string ContentHash { get; set; } // SHA-256
|
||||
public string? SignatureRef { get; set; } // DSSE envelope reference
|
||||
public string? CasUri { get; set; } // cas://reachability/graphs/{hash}
|
||||
|
||||
public string MediaType { get; set; }
|
||||
public long SizeBytes { get; set; }
|
||||
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**Database Schema:**
|
||||
- Table: `scanner.triage_findings` (core table)
|
||||
- Table: `scanner.triage_reachability_results` (1:1 with findings)
|
||||
- Table: `scanner.triage_risk_results` (1:1 with findings)
|
||||
- Table: `scanner.triage_effective_vex` (1:1 with findings)
|
||||
- Table: `scanner.triage_evidence_artifacts` (1:N with findings)
|
||||
|
||||
**Equivalence Proof:**
|
||||
- ✅ Subject: `AssetId` + `Purl`
|
||||
- ✅ CVE: `CveId`
|
||||
- ✅ Finding ID: `FindingId` (stable scheme)
|
||||
- ✅ Location: Embedded in evidence artifacts
|
||||
- ✅ Reachability: Full `TriageReachabilityResult` entity
|
||||
- ✅ EPSS: `EpssScore`, `EpssPercentile`, `EpssModelDate`
|
||||
- ✅ CVSS: `CvssBaseScore`, `CvssVector`, `CvssVersion`
|
||||
- ✅ VEX Status: `TriageEffectiveVex.Status`
|
||||
- ✅ Notes: `LatticeExplanationJson`
|
||||
- ✅ Evidence Refs: `TriageEvidenceArtifact` with `ContentHash`, `CasUri`
|
||||
|
||||
---
|
||||
|
||||
## Signal-16: Diff Delta
|
||||
|
||||
### Advisory Specification
|
||||
|
||||
```json
|
||||
{
|
||||
"subject": "ghcr.io/org/app",
|
||||
"fromVersion": "1.0.0",
|
||||
"toVersion": "1.1.0",
|
||||
"changed": {
|
||||
"packages": ["lodash@4.17.0→4.17.21"],
|
||||
"files": ["src/util.js"],
|
||||
"symbols": ["template"],
|
||||
"vulns": [{"cve": "CVE-2024-12345", "action": "fixed"}]
|
||||
},
|
||||
"explainableReasons": [
|
||||
{
|
||||
"reasonCode": "VEX_STATUS_FLIP",
|
||||
"params": {"from": "affected", "to": "fixed"},
|
||||
"evidenceRefs": ["dsse://..."]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Purpose:** Minimal deltas between SBOM snapshots with explainable reasons
|
||||
|
||||
---
|
||||
|
||||
### StellaOps Implementation
|
||||
|
||||
**Primary Entity:** `TriageSnapshot`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Entities/TriageSnapshot.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageSnapshot
|
||||
{
|
||||
public string SnapshotId { get; set; }
|
||||
public string TenantId { get; set; }
|
||||
public string AssetId { get; set; }
|
||||
|
||||
// Version tracking
|
||||
public string? FromVersion { get; set; } // Maps to "fromVersion"
|
||||
public string? ToVersion { get; set; } // Maps to "toVersion"
|
||||
public string FromScanId { get; set; }
|
||||
public string ToScanId { get; set; }
|
||||
|
||||
// Input/output hashes for diffing
|
||||
public string FromInputsHash { get; set; }
|
||||
public string ToInputsHash { get; set; }
|
||||
|
||||
// Precomputed diff
|
||||
public string? DiffJson { get; set; } // Maps to "changed"
|
||||
|
||||
// Trigger tracking
|
||||
public string? Trigger { get; set; } // Manual, Scheduled, EventDriven
|
||||
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**Smart-Diff Detector:** `MaterialRiskChangeDetector`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.SmartDiff/Detection/MaterialRiskChangeDetector.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class MaterialRiskChangeDetector
|
||||
{
|
||||
// Detection rules
|
||||
public IReadOnlyList<DetectedChange> Detect(
|
||||
RiskStateSnapshot previous,
|
||||
RiskStateSnapshot current)
|
||||
{
|
||||
// R1: Reachability flip
|
||||
// R2: VEX status flip
|
||||
// R3: Range boundary cross
|
||||
// R4: Intelligence/Policy flip
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Risk State Snapshot:** `RiskStateSnapshot`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.SmartDiff/Detection/RiskStateSnapshot.cs`
|
||||
|
||||
```csharp
|
||||
public sealed record RiskStateSnapshot
|
||||
{
|
||||
public bool? Reachable { get; init; }
|
||||
public VexClaimStatus VexStatus { get; init; }
|
||||
public bool? InAffectedRange { get; init; }
|
||||
public bool Kev { get; init; }
|
||||
public double? EpssScore { get; init; }
|
||||
public PolicyDecision PolicyDecision { get; init; }
|
||||
public string? LatticeState { get; init; }
|
||||
|
||||
// SHA-256 hash for deterministic change detection
|
||||
public string ComputeHash() => SHA256.Hash(CanonicalJson);
|
||||
}
|
||||
```
|
||||
|
||||
**Reachability Drift Detector:** `ReachabilityDriftDetector`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.ReachabilityDrift/Services/ReachabilityDriftDetector.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class ReachabilityDriftDetector
|
||||
{
|
||||
public Task<DriftDetectionResult> DetectAsync(
|
||||
string baseScanId,
|
||||
string headScanId,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
```
|
||||
|
||||
**Drift Cause Explainer:** `DriftCauseExplainer`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.ReachabilityDrift/Services/DriftCauseExplainer.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class DriftCauseExplainer
|
||||
{
|
||||
// Explains why reachability changed
|
||||
public DriftCause Explain(
|
||||
CallGraphSnapshot baseGraph,
|
||||
CallGraphSnapshot headGraph,
|
||||
string sinkId);
|
||||
}
|
||||
|
||||
public sealed record DriftCause
|
||||
{
|
||||
public DriftCauseKind Kind { get; init; } // Maps to "reasonCode"
|
||||
public string Description { get; init; }
|
||||
public string? ChangedSymbol { get; init; }
|
||||
public string? ChangedFile { get; init; }
|
||||
public int? ChangedLine { get; init; }
|
||||
public string? CodeChangeId { get; init; } // Maps to "evidenceRefs"
|
||||
}
|
||||
|
||||
public enum DriftCauseKind
|
||||
{
|
||||
GuardRemoved, // "GUARD_REMOVED"
|
||||
NewPublicRoute, // "NEW_PUBLIC_ROUTE"
|
||||
VisibilityEscalated, // "VISIBILITY_ESCALATED"
|
||||
DependencyUpgraded, // "DEPENDENCY_UPGRADED"
|
||||
SymbolRemoved, // "SYMBOL_REMOVED"
|
||||
GuardAdded, // "GUARD_ADDED"
|
||||
Unknown // "UNKNOWN"
|
||||
}
|
||||
```
|
||||
|
||||
**API Endpoints:**
|
||||
- `GET /smart-diff/scans/{scanId}/changes` - Material risk changes
|
||||
- `GET /smart-diff/scans/{scanId}/sarif` - SARIF 2.1.0 format
|
||||
- `GET /smart-diff/images/{digest}/candidates` - VEX candidates
|
||||
|
||||
**Database Schema:**
|
||||
- Table: `scanner.triage_snapshots`
|
||||
- Table: `scanner.risk_state_snapshots`
|
||||
- Table: `scanner.material_risk_changes`
|
||||
- Table: `scanner.call_graph_snapshots`
|
||||
|
||||
**Equivalence Proof:**
|
||||
- ✅ Subject: `AssetId`
|
||||
- ✅ From/To Version: `FromVersion`, `ToVersion`
|
||||
- ✅ Changed packages: In `DiffJson` + package-level diffs
|
||||
- ✅ Changed symbols: Reachability drift detection
|
||||
- ✅ Changed vulns: Material risk changes
|
||||
- ✅ Explainable reasons: `DriftCause` with `Kind` (reason code)
|
||||
- ✅ Evidence refs: `CodeChangeId`, evidence artifacts
|
||||
|
||||
---
|
||||
|
||||
## Signal-18: Decision
|
||||
|
||||
### Advisory Specification
|
||||
|
||||
```json
|
||||
{
|
||||
"subject": "pkg:npm/lodash@4.17.0",
|
||||
"decisionId": "dec-abc123",
|
||||
"severity": "HIGH",
|
||||
"priority": 85,
|
||||
"rationale": [
|
||||
"Reachable from public API",
|
||||
"EPSS above threshold (0.85)",
|
||||
"No VEX from vendor"
|
||||
],
|
||||
"actions": ["Block deployment", "Notify security team"],
|
||||
"dsseSignatures": ["dsse://sha256:..."]
|
||||
}
|
||||
```
|
||||
|
||||
**Purpose:** Policy decisions with rationale and signatures
|
||||
|
||||
---
|
||||
|
||||
### StellaOps Implementation
|
||||
|
||||
**Primary Entity:** `TriageDecision`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Entities/TriageDecision.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageDecision
|
||||
{
|
||||
public string DecisionId { get; set; } // Maps to "decisionId"
|
||||
public string FindingId { get; set; } // Links to TriageFinding (subject)
|
||||
public string TenantId { get; set; }
|
||||
|
||||
// Decision details
|
||||
public TriageDecisionKind Kind { get; set; } // Mute, Acknowledge, Exception
|
||||
public string? Reason { get; set; } // Maps to "rationale"
|
||||
public string? ReasonCode { get; set; }
|
||||
|
||||
// Actor
|
||||
public string ActorSubject { get; set; }
|
||||
public string? ActorDisplayName { get; set; }
|
||||
|
||||
// Policy reference
|
||||
public string? PolicyRef { get; set; }
|
||||
|
||||
// Lifetime
|
||||
public DateTimeOffset EffectiveAt { get; set; }
|
||||
public DateTimeOffset? ExpiresAt { get; set; }
|
||||
public int? TtlDays { get; set; }
|
||||
|
||||
// Reversibility
|
||||
public bool Revoked { get; set; }
|
||||
public DateTimeOffset? RevokedAt { get; set; }
|
||||
public string? RevokedBy { get; set; }
|
||||
|
||||
// Signatures
|
||||
public string? DsseEnvelopeHash { get; set; } // Maps to "dsseSignatures"
|
||||
public string? SignatureRef { get; set; }
|
||||
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**Risk Result (includes severity/priority):** From `TriageRiskResult`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageRiskResult
|
||||
{
|
||||
public double RiskScore { get; set; } // Maps to "priority" (0-100)
|
||||
public string Lane { get; set; } // Maps to "severity" (Critical/High/Medium/Low)
|
||||
public TriageVerdict Verdict { get; set; } // Maps to "actions" (Ship/Block/Exception)
|
||||
public string? LatticeExplanationJson { get; set; } // Maps to "rationale" (structured)
|
||||
}
|
||||
```
|
||||
|
||||
**Score Explanation Service:** `ScoreExplanationService`
|
||||
|
||||
**Location:** `src/Signals/StellaOps.Signals/Services/ScoreExplanationService.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class ScoreExplanationService
|
||||
{
|
||||
// Generates structured rationale
|
||||
public ScoreExplanation Explain(ScoreExplanationRequest request)
|
||||
{
|
||||
// Returns breakdown of:
|
||||
// - CVSS contribution
|
||||
// - EPSS contribution
|
||||
// - Reachability contribution
|
||||
// - VEX reduction
|
||||
// - Gate discounts
|
||||
// - KEV bonus
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Decision Predicate Type:** `stella.ops/policy-decision@v1`
|
||||
|
||||
**Location:** Defined in `PredicateTypes.cs`, implemented in attestations
|
||||
|
||||
**Database Schema:**
|
||||
- Table: `scanner.triage_decisions`
|
||||
- Table: `scanner.triage_risk_results` (for severity/priority)
|
||||
|
||||
**API Endpoints:**
|
||||
- `POST /triage/decisions` - Create decision
|
||||
- `DELETE /triage/decisions/{decisionId}` - Revoke decision
|
||||
- `GET /triage/findings/{findingId}/decisions` - List decisions for finding
|
||||
|
||||
**Equivalence Proof:**
|
||||
- ✅ Subject: Linked via `FindingId` → `TriageFinding.Purl`
|
||||
- ✅ Decision ID: `DecisionId`
|
||||
- ✅ Severity: `TriageRiskResult.Lane`
|
||||
- ✅ Priority: `TriageRiskResult.RiskScore`
|
||||
- ✅ Rationale: `Reason` + `LatticeExplanationJson` (structured)
|
||||
- ✅ Actions: `Verdict` (Ship/Block/Exception)
|
||||
- ✅ DSSE Signatures: `DsseEnvelopeHash`, `SignatureRef`
|
||||
|
||||
---
|
||||
|
||||
## Idempotency Key Handling
|
||||
|
||||
### Advisory Pattern
|
||||
|
||||
```
|
||||
idemKey = hash(subjectDigest || type || runId || cve || windowStart)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### StellaOps Implementation
|
||||
|
||||
**Event Envelope Idempotency:**
|
||||
|
||||
**Location:** `src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Domain/Events/EventEnvelope.cs`
|
||||
|
||||
```csharp
|
||||
public static string GenerateIdempotencyKey(
|
||||
OrchestratorEventType eventType,
|
||||
string? jobId,
|
||||
int attempt)
|
||||
{
|
||||
var jobPart = jobId ?? "none";
|
||||
return $"orch-{eventType.ToEventTypeName()}-{jobPart}-{attempt}";
|
||||
}
|
||||
```
|
||||
|
||||
**Pattern:** `{domain}-{event_type}-{entity_id}-{attempt}`
|
||||
|
||||
**Orchestrator Event Idempotency:**
|
||||
|
||||
**Location:** `src/Scanner/StellaOps.Scanner.WebService/Contracts/OrchestratorEventContracts.cs`
|
||||
|
||||
```csharp
|
||||
public sealed record OrchestratorEvent
|
||||
{
|
||||
public required string EventId { get; init; }
|
||||
public required string EventKind { get; init; }
|
||||
public required string IdempotencyKey { get; init; } // Explicitly tracked
|
||||
public required string CorrelationId { get; init; }
|
||||
public required string TraceId { get; init; }
|
||||
public required string SpanId { get; init; }
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Finding ID Stability (Signal-14):**
|
||||
|
||||
**Pattern:** `{cve}@{purl}@{scanId}`
|
||||
|
||||
**Location:** `TriageFinding.FindingId` generation logic
|
||||
|
||||
**Equivalence:**
|
||||
- ✅ Subject digest: Included in `scanId` or `AssetId`
|
||||
- ✅ Type: `EventKind` or `EventType`
|
||||
- ✅ Run ID: `TraceId`, `CorrelationId`, `attempt`
|
||||
- ✅ CVE: Included in finding ID
|
||||
- ✅ Window: Implicit in scan/job timing
|
||||
|
||||
---
|
||||
|
||||
## Evidence Reference Mechanisms
|
||||
|
||||
### Advisory Pattern
|
||||
|
||||
```
|
||||
evidenceRefs[i] = dsse://sha256:<payloadHash>
|
||||
```
|
||||
|
||||
**Storage:** DSSE payloads stored as blobs, indexed by `payloadHash` and `subjectDigest`
|
||||
|
||||
---
|
||||
|
||||
### StellaOps Implementation
|
||||
|
||||
**CAS URI Pattern:**
|
||||
|
||||
```
|
||||
cas://reachability/graphs/{blake3_hash}
|
||||
cas://runtime/traces/{blake3_hash}
|
||||
```
|
||||
|
||||
**DSSE Reference Pattern:**
|
||||
|
||||
```
|
||||
{DsseEnvelopeHash} = SHA-256 of DSSE envelope
|
||||
{SignatureRef} = Reference to attestor.envelopes table
|
||||
```
|
||||
|
||||
**Evidence Artifact Entity:** `TriageEvidenceArtifact`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageEvidenceArtifact
|
||||
{
|
||||
public string ContentHash { get; set; } // SHA-256 of content
|
||||
public string? SignatureRef { get; set; } // DSSE envelope reference
|
||||
public string? CasUri { get; set; } // CAS URI for content
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Reachability Evidence Chain:** `ReachabilityEvidenceChain`
|
||||
|
||||
**Location:** `src/__Libraries/StellaOps.Signals.Contracts/Models/Evidence/ReachabilityEvidenceChain.cs`
|
||||
|
||||
```csharp
|
||||
public sealed record ReachabilityEvidenceChain
|
||||
{
|
||||
public GraphEvidence? GraphEvidence { get; init; }
|
||||
public RuntimeEvidence? RuntimeEvidence { get; init; }
|
||||
public ImmutableArray<CodeAnchor> CodeAnchors { get; init; }
|
||||
public ImmutableArray<Unknown> Unknowns { get; init; }
|
||||
}
|
||||
|
||||
public sealed record GraphEvidence
|
||||
{
|
||||
public required string GraphHash { get; init; } // BLAKE3
|
||||
public required string GraphCasUri { get; init; } // cas://...
|
||||
public required string AnalyzerName { get; init; }
|
||||
public required string AnalyzerVersion { get; init; }
|
||||
public DateTimeOffset AnalyzedAt { get; init; }
|
||||
}
|
||||
|
||||
public sealed record RuntimeEvidence
|
||||
{
|
||||
public required string TraceHash { get; init; } // BLAKE3
|
||||
public required string TraceCasUri { get; init; } // cas://...
|
||||
public required string ProbeType { get; init; }
|
||||
public required int HitCount { get; init; }
|
||||
public DateTimeOffset LastSeenAt { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
**Storage:**
|
||||
- PostgreSQL: `attestor.envelopes` table for DSSE envelopes
|
||||
- PostgreSQL: `scanner.triage_evidence_artifacts` for evidence metadata
|
||||
- S3/MinIO: CAS storage for evidence blobs
|
||||
|
||||
**Equivalence:**
|
||||
- ✅ Hash-addressed storage: SHA-256, BLAKE3
|
||||
- ✅ DSSE references: `DsseEnvelopeHash`, `SignatureRef`
|
||||
- ✅ CAS URIs: `cas://` scheme for content-addressable storage
|
||||
- ✅ Blob storage: S3-compatible object store
|
||||
- ✅ Index by subject: `FindingId` links to evidence
|
||||
|
||||
---
|
||||
|
||||
## API Endpoint Mapping
|
||||
|
||||
| Signal | Advisory Endpoint | StellaOps Endpoint |
|
||||
|--------|------------------|-------------------|
|
||||
| Signal-10 | `POST /sbom/intake` | `POST /api/scanner/sboms/ingest`<br>`POST /api/signals/callgraph/ingest` |
|
||||
| Signal-12 | `POST /attestations` | Implicit via signing services<br>`GET /api/attestor/envelopes/{hash}` |
|
||||
| Signal-14 | `GET /triage/facts/{findingId}` | `GET /api/scanner/triage/findings/{findingId}`<br>`GET /api/scanner/triage/findings/{findingId}/evidence` |
|
||||
| Signal-16 | `GET /diff/{from}/{to}` | `GET /api/smart-diff/scans/{scanId}/changes`<br>`GET /api/smart-diff/images/{digest}/candidates` |
|
||||
| Signal-18 | `POST /decisions` | `POST /api/triage/decisions`<br>`GET /api/triage/findings/{findingId}/decisions` |
|
||||
|
||||
---
|
||||
|
||||
## Component Architecture Alignment
|
||||
|
||||
### Advisory Architecture
|
||||
|
||||
```
|
||||
[ Sbomer ] → Signal-10 → [ Router ]
|
||||
[ Attestor ] → Signal-12 → [ Router ]
|
||||
[ Scanner.Worker ] → Signal-14 → [ Triage Store ]
|
||||
[ Reachability.Engine ] → updates Signal-14
|
||||
[ Smart-Diff ] → Signal-16 → [ Router ]
|
||||
[ Deterministic-Scorer ] → Signal-18 → [ Router/Notify ]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### StellaOps Architecture
|
||||
|
||||
```
|
||||
[ Scanner.Emit ] → SbomIngestionService → PostgreSQL (scanner.sboms)
|
||||
[ Attestor.ProofChain ] → DsseEnvelopeSigner → PostgreSQL (attestor.envelopes)
|
||||
[ Scanner.Triage ] → TriageFinding + related entities → PostgreSQL (scanner.triage_*)
|
||||
[ ReachabilityAnalyzer ] → PathWitnessBuilder → TriageReachabilityResult
|
||||
[ SmartDiff + ReachabilityDrift ] → MaterialRiskChangeDetector → TriageSnapshot
|
||||
[ Policy.Scoring engines ] → ScoreExplanationService → TriageRiskResult + TriageDecision
|
||||
[ Router.Gateway ] → TransportDispatchMiddleware → Inter-service routing
|
||||
[ TimelineIndexer ] → TimelineEventEnvelope → Event ordering & storage
|
||||
```
|
||||
|
||||
**Mapping:**
|
||||
- Sbomer ↔ Scanner.Emit
|
||||
- Attestor ↔ Attestor.ProofChain
|
||||
- Scanner.Worker ↔ Scanner.Triage
|
||||
- Reachability.Engine ↔ ReachabilityAnalyzer
|
||||
- Smart-Diff ↔ SmartDiff + ReachabilityDrift
|
||||
- Deterministic-Scorer ↔ Policy.Scoring engines
|
||||
- Router/Timeline ↔ Router.Gateway + TimelineIndexer
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**Alignment Status:** ✅ **Fully Aligned (Conceptually)**
|
||||
|
||||
While StellaOps uses domain-specific entity names instead of generic "Signal-X" labels, all signal concepts are implemented with equivalent or superior functionality:
|
||||
|
||||
- ✅ **Signal-10:** SBOM intake via `CallgraphIngestRequest`, `ISbomIngestionService`
|
||||
- ✅ **Signal-12:** in-toto attestations with 19 predicate types, DSSE signing
|
||||
- ✅ **Signal-14:** Comprehensive triage entities (`TriageFinding`, `TriageReachabilityResult`, `TriageRiskResult`, `TriageEffectiveVex`)
|
||||
- ✅ **Signal-16:** Smart-diff with `TriageSnapshot`, `MaterialRiskChange`, explainable drift causes
|
||||
- ✅ **Signal-18:** `TriageDecision` with DSSE signatures and structured rationale
|
||||
|
||||
**Key Advantages of StellaOps Implementation:**
|
||||
1. **Type Safety:** Strong entity types vs. generic JSON blobs
|
||||
2. **Relational Integrity:** PostgreSQL foreign keys enforce referential integrity
|
||||
3. **Query Performance:** Indexed tables for fast lookups
|
||||
4. **Domain Clarity:** Names reflect business concepts (Triage, Risk, Evidence)
|
||||
5. **Extensibility:** Easy to add new fields/entities without breaking contracts
|
||||
|
||||
**Recommendation:** Maintain current architecture and entity naming. Provide this mapping document to demonstrate compliance with advisory signal patterns.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
### StellaOps Code Files
|
||||
- `src/Signals/StellaOps.Signals/Models/CallgraphIngestRequest.cs`
|
||||
- `src/Attestor/__Libraries/StellaOps.Attestor.ProofChain/Statements/InTotoStatement.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Entities/*.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.SmartDiff/Detection/MaterialRiskChangeDetector.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.ReachabilityDrift/Services/*.cs`
|
||||
- `src/Signer/StellaOps.Signer/StellaOps.Signer.Core/PredicateTypes.cs`
|
||||
|
||||
### Advisory References
|
||||
- Advisory architecture document (CycloneDX 1.7 / VEX-first / in-toto)
|
||||
- Signal contracts specification (10/12/14/16/18)
|
||||
- DSSE specification: https://github.com/secure-systems-lab/dsse
|
||||
- in-toto attestation framework: https://github.com/in-toto/attestation
|
||||
|
||||
### StellaOps Documentation
|
||||
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
|
||||
- `docs/modules/scanner/architecture.md`
|
||||
- `docs/modules/attestor/transparency.md`
|
||||
- `docs/contracts/witness-v1.md`
|
||||
Reference in New Issue
Block a user