Add comprehensive security tests for OWASP A02, A05, A07, and A08 categories
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled

- Implemented tests for Cryptographic Failures (A02) to ensure proper handling of sensitive data, secure algorithms, and key management.
- Added tests for Security Misconfiguration (A05) to validate production configurations, security headers, CORS settings, and feature management.
- Developed tests for Authentication Failures (A07) to enforce strong password policies, rate limiting, session management, and MFA support.
- Created tests for Software and Data Integrity Failures (A08) to verify artifact signatures, SBOM integrity, attestation chains, and feed updates.
This commit is contained in:
master
2025-12-16 16:40:19 +02:00
parent 415eff1207
commit 2170a58734
206 changed files with 30547 additions and 534 deletions

View File

@@ -898,6 +898,8 @@ Both commands honour CLI observability hooks: Spectre tables for human output, `
| `stellaops-cli graph explain` | Show reachability call path for a finding | `--finding <purl:cve>` (required)<br>`--scan-id <id>`<br>`--format table\|json` | Displays `latticeState`, call path with `symbol_id`/`code_id`, runtime hits, `graph_hash`, and DSSE attestation refs |
| `stellaops-cli graph export` | Export reachability graph bundle | `--scan-id <id>` (required)<br>`--output <dir>`<br>`--include-runtime` | Creates `richgraph-v1.json`, `.dsse`, `meta.json`, and optional `runtime-facts.ndjson` |
| `stellaops-cli graph verify` | Verify graph DSSE signature and Rekor entry | `--graph <path>` (required)<br>`--dsse <path>`<br>`--rekor-log` | Recomputes BLAKE3 hash, validates DSSE envelope, checks Rekor inclusion proof |
| `stellaops-cli proof verify` | Verify an artifact's proof chain | `<artifact>` (required)<br>`--sbom <file>`<br>`--vex <file>`<br>`--anchor <uuid>`<br>`--offline`<br>`--output text\|json`<br>`-v/-vv` | Validates proof spine, Merkle inclusion, VEX statements, and Rekor entries. Returns exit code 0 (pass), 1 (policy violation), or 2 (system error). Designed for CI/CD integration. |
| `stellaops-cli proof spine` | Display proof spine for an artifact | `<artifact>` (required)<br>`--format table\|json`<br>`--show-merkle` | Shows assembled proof spine with evidence statements, VEX verdicts, and Merkle tree structure. |
| `stellaops-cli replay verify` | Verify replay manifest determinism | `--manifest <path>` (required)<br>`--sealed`<br>`--verbose` | Recomputes all artifact hashes and compares against manifest; exit 0 on match |
| `stellaops-cli runtime policy test` | Ask Scanner.WebService for runtime verdicts (Webhook parity) | `--image/-i <digest>` (repeatable, comma/space lists supported)<br>`--file/-f <path>`<br>`--namespace/--ns <name>`<br>`--label/-l key=value` (repeatable)<br>`--json` | Posts to `POST /api/v1/scanner/policy/runtime`, deduplicates image digests, and prints TTL/policy revision plus per-image columns for signed state, SBOM referrers, quieted-by metadata, confidence, Rekor attestation (uuid + verified flag), and recently observed build IDs (shortened for readability). Accepts newline/whitespace-delimited stdin when piped; `--json` emits the raw response without additional logging. |

View File

@@ -0,0 +1,213 @@
# Offline Bundle Format (.stella.bundle.tgz)
> Sprint: SPRINT_3603_0001_0001
> Module: ExportCenter
This document describes the `.stella.bundle.tgz` format for portable, signed, verifiable evidence packages.
## Overview
The offline bundle is a self-contained archive containing all evidence and artifacts needed for offline triage of security findings. Bundles are:
- **Portable**: Single file that can be transferred to air-gapped environments
- **Signed**: DSSE-signed manifest for authenticity verification
- **Verifiable**: Content-addressable with SHA-256 hashes for integrity
- **Complete**: Contains all data needed for offline decision-making
## File Format
```
{alert-id}.stella.bundle.tgz
├── manifest.json # Bundle manifest (DSSE-signed)
├── metadata/
│ ├── alert.json # Alert metadata snapshot
│ └── generation-info.json # Bundle generation metadata
├── evidence/
│ ├── reachability-proof.json # Call-graph reachability evidence
│ ├── callstack.json # Exploitability call stacks
│ └── provenance.json # Build provenance attestations
├── vex/
│ ├── decisions.ndjson # VEX decision history (NDJSON)
│ └── current-status.json # Current VEX status
├── sbom/
│ ├── current.cdx.json # Current SBOM slice (CycloneDX)
│ └── baseline.cdx.json # Baseline SBOM for diff
├── diff/
│ └── sbom-delta.json # SBOM delta changes
└── attestations/
├── bundle.dsse.json # DSSE envelope for bundle
└── evidence.dsse.json # Evidence attestation chain
```
## Manifest Schema
The `manifest.json` file follows this schema:
```json
{
"bundle_format_version": "1.0.0",
"bundle_id": "abc123def456...",
"alert_id": "alert-789",
"created_at": "2024-12-15T10:00:00Z",
"created_by": "user@example.com",
"stellaops_version": "1.5.0",
"entries": [
{
"path": "metadata/alert.json",
"hash": "sha256:...",
"size": 1234,
"content_type": "application/json"
}
],
"root_hash": "sha256:...",
"signature": {
"algorithm": "ES256",
"key_id": "signing-key-001",
"value": "..."
}
}
```
### Manifest Fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `bundle_format_version` | string | Yes | Format version (semver) |
| `bundle_id` | string | Yes | Unique bundle identifier |
| `alert_id` | string | Yes | Source alert identifier |
| `created_at` | ISO 8601 | Yes | Bundle creation timestamp (UTC) |
| `created_by` | string | Yes | Actor who created the bundle |
| `stellaops_version` | string | Yes | StellaOps version that created bundle |
| `entries` | array | Yes | List of content entries with hashes |
| `root_hash` | string | Yes | Merkle root of all entry hashes |
| `signature` | object | No | DSSE signature (if signed) |
## Entry Schema
Each entry in the manifest:
```json
{
"path": "evidence/reachability-proof.json",
"hash": "sha256:abc123...",
"size": 2048,
"content_type": "application/json",
"compression": null
}
```
## DSSE Signing
Bundles support DSSE (Dead Simple Signing Envelope) signing:
```json
{
"payloadType": "application/vnd.stellaops.bundle.manifest+json",
"payload": "<base64-encoded manifest>",
"signatures": [
{
"keyid": "signing-key-001",
"sig": "<base64-encoded signature>"
}
]
}
```
## Creation
### API Endpoint
```http
GET /v1/alerts/{alertId}/bundle
Authorization: Bearer <token>
Response: application/gzip
Content-Disposition: attachment; filename="alert-123.stella.bundle.tgz"
```
### Programmatic
```csharp
var packager = services.GetRequiredService<IOfflineBundlePackager>();
var result = await packager.CreateBundleAsync(new BundleRequest
{
AlertId = "alert-123",
ActorId = "user@example.com",
IncludeVexHistory = true,
IncludeSbomSlice = true
});
// result.Content contains the tarball stream
// result.ManifestHash contains the verification hash
```
## Verification
### API Endpoint
```http
POST /v1/alerts/{alertId}/bundle/verify
Content-Type: application/json
{
"bundle_hash": "sha256:abc123...",
"signature": "<optional DSSE signature>"
}
Response:
{
"is_valid": true,
"hash_valid": true,
"chain_valid": true,
"signature_valid": true,
"verified_at": "2024-12-15T10:00:00Z"
}
```
### Programmatic
```csharp
var verification = await packager.VerifyBundleAsync(
bundlePath: "/path/to/bundle.stella.bundle.tgz",
expectedHash: "sha256:abc123...");
if (!verification.IsValid)
{
Console.WriteLine($"Verification failed: {string.Join(", ", verification.Errors)}");
}
```
## CLI Usage
```bash
# Export bundle
stellaops alert bundle export --alert-id alert-123 --output ./bundles/
# Verify bundle
stellaops alert bundle verify --file ./bundles/alert-123.stella.bundle.tgz
# Import bundle (air-gapped instance)
stellaops alert bundle import --file ./bundles/alert-123.stella.bundle.tgz
```
## Security Considerations
1. **Hash Verification**: Always verify bundle hash before processing
2. **Signature Validation**: Verify DSSE signature if present
3. **Content Validation**: Validate JSON schemas after extraction
4. **Size Limits**: Enforce maximum bundle size limits (default: 100MB)
5. **Path Traversal**: Tarball extraction must prevent path traversal attacks
## Versioning
| Format Version | Changes | Min StellaOps Version |
|----------------|---------|----------------------|
| 1.0.0 | Initial format | 1.0.0 |
## Related Documentation
- [Evidence Bundle Envelope](./evidence-bundle-envelope.md)
- [DSSE Signing Guide](./dsse-signing.md)
- [Offline Kit Guide](../10_OFFLINE_KIT.md)
- [API Reference](../api/evidence-decision-api.openapi.yaml)

View File

@@ -0,0 +1,434 @@
openapi: 3.1.0
info:
title: StellaOps Evidence & Decision API
description: |
REST API for evidence retrieval and decision recording.
Sprint: SPRINT_3602_0001_0001
version: 1.0.0
license:
name: AGPL-3.0-or-later
url: https://www.gnu.org/licenses/agpl-3.0.html
servers:
- url: /v1
description: API v1
security:
- bearerAuth: []
paths:
/alerts:
get:
operationId: listAlerts
summary: List alerts with filtering and pagination
tags:
- Alerts
parameters:
- name: band
in: query
schema:
type: string
enum: [critical, high, medium, low, info]
- name: severity
in: query
schema:
type: string
- name: status
in: query
schema:
type: string
enum: [open, acknowledged, resolved, suppressed]
- name: artifactId
in: query
schema:
type: string
- name: vulnId
in: query
schema:
type: string
- name: componentPurl
in: query
schema:
type: string
- name: limit
in: query
schema:
type: integer
default: 50
maximum: 500
- name: offset
in: query
schema:
type: integer
default: 0
responses:
'200':
description: Alert list
content:
application/json:
schema:
$ref: '#/components/schemas/AlertListResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
/alerts/{alertId}:
get:
operationId: getAlert
summary: Get alert details
tags:
- Alerts
parameters:
- $ref: '#/components/parameters/alertId'
responses:
'200':
description: Alert details
content:
application/json:
schema:
$ref: '#/components/schemas/AlertSummary'
'404':
$ref: '#/components/responses/NotFound'
/alerts/{alertId}/evidence:
get:
operationId: getAlertEvidence
summary: Get evidence bundle for an alert
tags:
- Evidence
parameters:
- $ref: '#/components/parameters/alertId'
responses:
'200':
description: Evidence payload
content:
application/json:
schema:
$ref: '#/components/schemas/EvidencePayloadResponse'
'404':
$ref: '#/components/responses/NotFound'
/alerts/{alertId}/decisions:
post:
operationId: recordDecision
summary: Record a decision for an alert
tags:
- Decisions
parameters:
- $ref: '#/components/parameters/alertId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DecisionRequest'
responses:
'201':
description: Decision recorded
content:
application/json:
schema:
$ref: '#/components/schemas/DecisionResponse'
'404':
$ref: '#/components/responses/NotFound'
'400':
$ref: '#/components/responses/BadRequest'
/alerts/{alertId}/audit:
get:
operationId: getAlertAudit
summary: Get audit timeline for an alert
tags:
- Audit
parameters:
- $ref: '#/components/parameters/alertId'
responses:
'200':
description: Audit timeline
content:
application/json:
schema:
$ref: '#/components/schemas/AuditTimelineResponse'
'404':
$ref: '#/components/responses/NotFound'
/alerts/{alertId}/bundle:
get:
operationId: downloadAlertBundle
summary: Download evidence bundle as tar.gz
tags:
- Bundles
parameters:
- $ref: '#/components/parameters/alertId'
responses:
'200':
description: Evidence bundle file
content:
application/gzip:
schema:
type: string
format: binary
'404':
$ref: '#/components/responses/NotFound'
/alerts/{alertId}/bundle/verify:
post:
operationId: verifyAlertBundle
summary: Verify evidence bundle integrity
tags:
- Bundles
parameters:
- $ref: '#/components/parameters/alertId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/BundleVerificationRequest'
responses:
'200':
description: Verification result
content:
application/json:
schema:
$ref: '#/components/schemas/BundleVerificationResponse'
'404':
$ref: '#/components/responses/NotFound'
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
parameters:
alertId:
name: alertId
in: path
required: true
schema:
type: string
description: Alert identifier
responses:
BadRequest:
description: Bad request
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetails'
Unauthorized:
description: Unauthorized
NotFound:
description: Resource not found
schemas:
AlertListResponse:
type: object
required:
- items
- total_count
properties:
items:
type: array
items:
$ref: '#/components/schemas/AlertSummary'
total_count:
type: integer
next_page_token:
type: string
AlertSummary:
type: object
required:
- alert_id
- artifact_id
- vuln_id
- severity
- band
- status
- created_at
properties:
alert_id:
type: string
artifact_id:
type: string
vuln_id:
type: string
component_purl:
type: string
severity:
type: string
band:
type: string
enum: [critical, high, medium, low, info]
status:
type: string
enum: [open, acknowledged, resolved, suppressed]
score:
type: number
format: double
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
decision_count:
type: integer
EvidencePayloadResponse:
type: object
required:
- alert_id
properties:
alert_id:
type: string
reachability:
$ref: '#/components/schemas/EvidenceSection'
callstack:
$ref: '#/components/schemas/EvidenceSection'
vex:
$ref: '#/components/schemas/EvidenceSection'
EvidenceSection:
type: object
properties:
data:
type: object
hash:
type: string
source:
type: string
DecisionRequest:
type: object
required:
- decision
- rationale
properties:
decision:
type: string
enum: [accept_risk, mitigate, suppress, escalate]
rationale:
type: string
minLength: 10
maxLength: 2000
justification_code:
type: string
metadata:
type: object
DecisionResponse:
type: object
required:
- decision_id
- alert_id
- decision
- recorded_at
properties:
decision_id:
type: string
alert_id:
type: string
decision:
type: string
rationale:
type: string
recorded_at:
type: string
format: date-time
recorded_by:
type: string
replay_token:
type: string
AuditTimelineResponse:
type: object
required:
- alert_id
- events
- total_count
properties:
alert_id:
type: string
events:
type: array
items:
$ref: '#/components/schemas/AuditEvent'
total_count:
type: integer
AuditEvent:
type: object
required:
- event_id
- event_type
- timestamp
properties:
event_id:
type: string
event_type:
type: string
timestamp:
type: string
format: date-time
actor:
type: string
details:
type: object
replay_token:
type: string
BundleVerificationRequest:
type: object
required:
- bundle_hash
properties:
bundle_hash:
type: string
description: SHA-256 hash of the bundle
signature:
type: string
description: Optional DSSE signature
BundleVerificationResponse:
type: object
required:
- alert_id
- is_valid
- verified_at
properties:
alert_id:
type: string
is_valid:
type: boolean
verified_at:
type: string
format: date-time
signature_valid:
type: boolean
hash_valid:
type: boolean
chain_valid:
type: boolean
errors:
type: array
items:
type: string
ProblemDetails:
type: object
properties:
type:
type: string
title:
type: string
status:
type: integer
detail:
type: string
instance:
type: string

View File

@@ -0,0 +1,325 @@
# Smart-Diff API Types
> Sprint: SPRINT_3500_0002_0001
> Module: Scanner, Policy, Attestor
This document describes the Smart-Diff types exposed through APIs.
## Smart-Diff Predicate
The Smart-Diff predicate is a DSSE-signed attestation describing differential analysis between two scans.
### Predicate Type URI
```
stellaops.dev/predicates/smart-diff@v1
```
### OpenAPI Schema Fragment
```yaml
SmartDiffPredicate:
type: object
required:
- schemaVersion
- baseImage
- targetImage
- diff
- reachabilityGate
- scanner
properties:
schemaVersion:
type: string
pattern: "^[0-9]+\\.[0-9]+\\.[0-9]+$"
example: "1.0.0"
description: Schema version (semver)
baseImage:
$ref: '#/components/schemas/ImageReference'
targetImage:
$ref: '#/components/schemas/ImageReference'
diff:
$ref: '#/components/schemas/DiffPayload'
reachabilityGate:
$ref: '#/components/schemas/ReachabilityGate'
scanner:
$ref: '#/components/schemas/ScannerInfo'
context:
$ref: '#/components/schemas/RuntimeContext'
suppressedCount:
type: integer
minimum: 0
description: Number of findings suppressed by pre-filters
materialChanges:
type: array
items:
$ref: '#/components/schemas/MaterialChange'
ImageReference:
type: object
required:
- digest
properties:
digest:
type: string
pattern: "^sha256:[a-f0-9]{64}$"
example: "sha256:abc123..."
repository:
type: string
example: "ghcr.io/org/image"
tag:
type: string
example: "v1.2.3"
DiffPayload:
type: object
required:
- added
- removed
- modified
properties:
added:
type: array
items:
$ref: '#/components/schemas/DiffEntry'
description: New vulnerabilities in target
removed:
type: array
items:
$ref: '#/components/schemas/DiffEntry'
description: Vulnerabilities fixed in target
modified:
type: array
items:
$ref: '#/components/schemas/DiffEntry'
description: Changed vulnerability status
DiffEntry:
type: object
required:
- vulnId
- componentPurl
properties:
vulnId:
type: string
example: "CVE-2024-1234"
componentPurl:
type: string
example: "pkg:npm/lodash@4.17.21"
severity:
type: string
enum: [CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN]
changeType:
type: string
enum: [added, removed, severity_changed, status_changed]
ReachabilityGate:
type: object
required:
- class
- isSinkReachable
- isEntryReachable
properties:
class:
type: integer
minimum: 0
maximum: 7
description: |
3-bit reachability class:
- Bit 0: Entry point reachable
- Bit 1: Sink reachable
- Bit 2: Direct path exists
isSinkReachable:
type: boolean
description: Whether a sensitive sink is reachable
isEntryReachable:
type: boolean
description: Whether an entry point is reachable
sinkCategory:
type: string
enum: [file, network, crypto, command, sql, ldap, xpath, ssrf, log, deserialization, reflection]
description: Category of the matched sink
ScannerInfo:
type: object
required:
- name
- version
properties:
name:
type: string
example: "stellaops-scanner"
version:
type: string
example: "1.5.0"
commit:
type: string
example: "abc123"
RuntimeContext:
type: object
additionalProperties: true
description: Optional runtime context for the scan
example:
env: "production"
namespace: "default"
cluster: "us-east-1"
MaterialChange:
type: object
properties:
type:
type: string
enum: [file, package, config]
path:
type: string
hash:
type: string
changeKind:
type: string
enum: [added, removed, modified]
```
## Reachability Gate Classes
| Class | Entry | Sink | Direct | Description |
|-------|-------|------|--------|-------------|
| 0 | ❌ | ❌ | ❌ | Not reachable |
| 1 | ✅ | ❌ | ❌ | Entry point only |
| 2 | ❌ | ✅ | ❌ | Sink only |
| 3 | ✅ | ✅ | ❌ | Both, no direct path |
| 4 | ❌ | ❌ | ✅ | Direct path, no endpoints |
| 5 | ✅ | ❌ | ✅ | Entry + direct |
| 6 | ❌ | ✅ | ✅ | Sink + direct |
| 7 | ✅ | ✅ | ✅ | Full reachability confirmed |
## Sink Categories
| Category | Description | Examples |
|----------|-------------|----------|
| `file` | File system operations | `File.Open`, `fopen` |
| `network` | Network I/O | `HttpClient`, `socket` |
| `crypto` | Cryptographic operations | `SHA256`, `AES` |
| `command` | Command execution | `Process.Start`, `exec` |
| `sql` | SQL queries | `SqlCommand`, query builders |
| `ldap` | LDAP operations | `DirectoryEntry` |
| `xpath` | XPath queries | `XPathNavigator` |
| `ssrf` | Server-side request forgery | HTTP clients with user input |
| `log` | Logging operations | `ILogger`, `Console.Write` |
| `deserialization` | Deserialization | `JsonSerializer`, `BinaryFormatter` |
| `reflection` | Reflection operations | `Type.GetType`, `Assembly.Load` |
## Suppression Rules
### OpenAPI Schema Fragment
```yaml
SuppressionRule:
type: object
required:
- id
- type
properties:
id:
type: string
description: Unique rule identifier
type:
type: string
enum:
- cve_pattern
- purl_pattern
- severity_below
- patch_churn
- sink_category
- reachability_class
pattern:
type: string
description: Regex pattern (for pattern rules)
threshold:
type: string
description: Threshold value (for severity/class rules)
enabled:
type: boolean
default: true
reason:
type: string
description: Human-readable reason for suppression
expires:
type: string
format: date-time
description: Optional expiration timestamp
SuppressionResult:
type: object
properties:
suppressed:
type: boolean
matchedRuleId:
type: string
reason:
type: string
```
## Usage Examples
### Creating a Smart-Diff Predicate
```csharp
var predicate = new SmartDiffPredicate
{
SchemaVersion = "1.0.0",
BaseImage = new ImageReference
{
Digest = "sha256:abc123...",
Repository = "ghcr.io/org/image",
Tag = "v1.0.0"
},
TargetImage = new ImageReference
{
Digest = "sha256:def456...",
Repository = "ghcr.io/org/image",
Tag = "v1.1.0"
},
Diff = new DiffPayload
{
Added = [new DiffEntry { VulnId = "CVE-2024-1234", ... }],
Removed = [],
Modified = []
},
ReachabilityGate = new ReachabilityGate
{
Class = 7,
IsSinkReachable = true,
IsEntryReachable = true,
SinkCategory = SinkCategory.Network
},
Scanner = new ScannerInfo
{
Name = "stellaops-scanner",
Version = "1.5.0"
},
SuppressedCount = 5
};
```
### Evaluating Suppression Rules
```csharp
var evaluator = services.GetRequiredService<ISuppressionRuleEvaluator>();
var result = await evaluator.EvaluateAsync(finding, rules);
if (result.Suppressed)
{
logger.LogInformation(
"Finding {VulnId} suppressed by rule {RuleId}: {Reason}",
finding.VulnId,
result.MatchedRuleId,
result.Reason);
}
```
## Related Documentation
- [Smart-Diff Technical Reference](../product-advisories/14-Dec-2025%20-%20Smart-Diff%20Technical%20Reference.md)
- [Scanner Architecture](../modules/scanner/architecture.md)
- [Policy Architecture](../modules/policy/architecture.md)

View File

@@ -0,0 +1,191 @@
# Fidelity Metrics Framework
> Sprint: SPRINT_3403_0001_0001_fidelity_metrics
This document describes the three-tier fidelity metrics framework for measuring deterministic reproducibility in StellaOps scanner outputs.
## Overview
Fidelity metrics quantify how consistently the scanner produces outputs across replay runs. The framework provides three tiers of measurement, each capturing different aspects of reproducibility:
| Metric | Abbrev. | Description | Target |
|--------|---------|-------------|--------|
| Bitwise Fidelity | BF | Byte-for-byte identical outputs | ≥ 0.98 |
| Semantic Fidelity | SF | Normalized object equivalence | ≥ 0.99 |
| Policy Fidelity | PF | Policy decision consistency | ≈ 1.0 |
## Metric Definitions
### Bitwise Fidelity (BF)
Measures the proportion of replay runs that produce byte-for-byte identical outputs.
```
BF = identical_outputs / total_replays
```
**What it captures:**
- SHA-256 hash equivalence of all output artifacts
- Timestamp consistency
- JSON formatting consistency
- Field ordering consistency
**When BF < 1.0:**
- Timestamps embedded in outputs
- Non-deterministic field ordering
- Floating-point rounding differences
- Random identifiers (UUIDs)
### Semantic Fidelity (SF)
Measures the proportion of replay runs that produce semantically equivalent outputs, ignoring formatting differences.
```
SF = semantic_matches / total_replays
```
**What it compares:**
- Package PURLs and versions
- CVE identifiers
- Severity levels (normalized to uppercase)
- VEX verdicts
- Reason codes
**When SF < 1.0 but BF = SF:**
- No actual content differences
- Only formatting differences
**When SF < 1.0:**
- Different packages detected
- Different CVEs matched
- Different severity assignments
### Policy Fidelity (PF)
Measures the proportion of replay runs that produce matching policy decisions.
```
PF = policy_matches / total_replays
```
**What it compares:**
- Final pass/fail decision
- Reason codes (sorted for comparison)
- Policy rule triggering
**When PF < 1.0:**
- Policy outcome differs between runs
- Indicates a non-determinism bug that affects user-visible decisions
## Prometheus Metrics
The fidelity framework exports the following metrics:
| Metric Name | Type | Labels | Description |
|-------------|------|--------|-------------|
| `fidelity_bitwise_ratio` | Gauge | tenant_id, surface_id | Bitwise fidelity ratio |
| `fidelity_semantic_ratio` | Gauge | tenant_id, surface_id | Semantic fidelity ratio |
| `fidelity_policy_ratio` | Gauge | tenant_id, surface_id | Policy fidelity ratio |
| `fidelity_total_replays` | Gauge | tenant_id, surface_id | Number of replays |
| `fidelity_slo_breach_total` | Counter | breach_type, tenant_id | SLO breach count |
## SLO Thresholds
Default SLO thresholds (configurable):
| Metric | Warning | Critical |
|--------|---------|----------|
| Bitwise Fidelity | < 0.98 | < 0.90 |
| Semantic Fidelity | < 0.99 | < 0.95 |
| Policy Fidelity | < 1.0 | < 0.99 |
## Integration with DeterminismReport
Fidelity metrics are integrated into the `DeterminismReport` record:
```csharp
public sealed record DeterminismReport(
// ... existing fields ...
FidelityMetrics? Fidelity = null);
public sealed record DeterminismImageReport(
// ... existing fields ...
FidelityMetrics? Fidelity = null);
```
## Usage Example
```csharp
// Create fidelity metrics service
var service = new FidelityMetricsService(
new BitwiseFidelityCalculator(),
new SemanticFidelityCalculator(),
new PolicyFidelityCalculator());
// Compute fidelity from baseline and replays
var baseline = LoadScanResult("scan-baseline.json");
var replays = LoadReplayScanResults();
var fidelity = service.Compute(baseline, replays);
// Check thresholds
if (fidelity.BitwiseFidelity < 0.98)
{
logger.LogWarning("BF below threshold: {BF}", fidelity.BitwiseFidelity);
}
// Include in determinism report
var report = new DeterminismReport(
// ... other fields ...
Fidelity: fidelity);
```
## Mismatch Diagnostics
When fidelity is below threshold, the framework provides diagnostic information:
```csharp
public sealed record FidelityMismatch
{
public required int RunIndex { get; init; }
public required FidelityMismatchType Type { get; init; }
public required string Description { get; init; }
public IReadOnlyList<string>? AffectedArtifacts { get; init; }
}
public enum FidelityMismatchType
{
BitwiseOnly, // Hash differs but content equivalent
SemanticOnly, // Content differs but policy matches
PolicyDrift // Policy decision differs
}
```
## Configuration
Configure fidelity options via `FidelityThresholds`:
```json
{
"Fidelity": {
"BitwiseThreshold": 0.98,
"SemanticThreshold": 0.99,
"PolicyThreshold": 1.0,
"EnableDiagnostics": true,
"MaxMismatchesRecorded": 100
}
}
```
## Related Documentation
- [Determinism and Reproducibility Technical Reference](../product-advisories/14-Dec-2025%20-%20Determinism%20and%20Reproducibility%20Technical%20Reference.md)
- [Determinism Scoring Foundations Sprint](../implplan/SPRINT_3401_0001_0001_determinism_scoring_foundations.md)
- [Scanner Architecture](../modules/scanner/architecture.md)
## Source Files
- `src/Scanner/StellaOps.Scanner.Worker/Determinism/FidelityMetrics.cs`
- `src/Scanner/StellaOps.Scanner.Worker/Determinism/FidelityMetricsService.cs`
- `src/Scanner/StellaOps.Scanner.Worker/Determinism/Calculators/`
- `src/Telemetry/StellaOps.Telemetry.Core/FidelityMetricsTelemetry.cs`
- `src/Telemetry/StellaOps.Telemetry.Core/FidelitySloAlertingService.cs`

View File

@@ -39,18 +39,18 @@ This sprint delivers enhancements to the TTFS system including predictive failur
| T1 | Create `failure_signatures` table | Agent | DONE | Added to scheduler.sql |
| T2 | Create `IFailureSignatureRepository` | Agent | DONE | Interface + Postgres impl |
| T3 | Implement `FailureSignatureIndexer` | Agent | DONE | Background indexer service |
| T4 | Integrate signatures into FirstSignal | — | TODO | lastKnownOutcome |
| T5 | Add "Verify locally" commands to EvidencePanel | — | TODO | Copy affordances |
| T6 | Create ProofSpine sub-component | — | TODO | Bundle hashes |
| T7 | Create verification command templates | — | TODO | Cosign/Rekor |
| T8 | Create micro-interactions.spec.ts | — | TODO | Playwright tests |
| T9 | Create TTFS Grafana dashboard | — | TODO | Observability |
| T10 | Create TTFS alert rules | — | TODO | SLO monitoring |
| T11 | Update documentation | — | TODO | Cross-links |
| T12 | Create secondary metrics tracking | — | TODO | Open→Action, bounce rate |
| T13 | Create load test suite | — | TODO | k6 tests for /first-signal |
| T14 | Add one-click evidence export | — | TODO | Export .tar.gz bundle |
| T15 | Create deterministic test fixtures | — | TODO | Frozen time, seeded RNG |
| T4 | Integrate signatures into FirstSignal | — | BLOCKED | Requires cross-module integration design (Orchestrator -> Scheduler). Added GetBestMatchAsync to IFailureSignatureRepository. Need abstraction/client pattern. |
| T5 | Add "Verify locally" commands to EvidencePanel | Agent | DONE | Copy affordances |
| T6 | Create ProofSpine sub-component | Agent | DONE | Bundle hashes |
| T7 | Create verification command templates | Agent | DONE | Cosign/Rekor |
| T8 | Create micro-interactions.spec.ts | Agent | DONE | Playwright tests in tests/e2e/playwright/evidence-panel-micro-interactions.spec.ts |
| T9 | Create TTFS Grafana dashboard | Agent | DONE | Created ttfs-observability.json |
| T10 | Create TTFS alert rules | Agent | DONE | Created ttfs-alerts.yaml |
| T11 | Update documentation | Agent | DONE | Added observability section to ttfs-architecture.md |
| T12 | Create secondary metrics tracking | Agent | DONE | EvidencePanelMetricsService: Open→Action, bounce rate in src/Web/.../core/analytics/ |
| T13 | Create load test suite | Agent | DONE | Created tests/load/ttfs-load-test.js |
| T14 | Add one-click evidence export | Agent | DONE | onExportEvidenceBundle() in EvidencePanel, exportEvidenceBundle API |
| T15 | Create deterministic test fixtures | Agent | DONE | DeterministicTestFixtures.cs + TypeScript fixtures |
---
@@ -1881,6 +1881,7 @@ export async function setupPlaywrightDeterministic(page: Page): Promise<void> {
| Signature table growth | 90-day retention policy, prune job | |
| Regex extraction misses patterns | Allow manual token override | |
| Clipboard not available | Show modal with selectable text | |
| **T4 cross-module dependency** | FirstSignalService (Orchestrator) needs IFailureSignatureRepository (Scheduler). Needs abstraction/client pattern or shared interface. Added GetBestMatchAsync to repository. Design decision pending. | Architect |
---
@@ -1894,3 +1895,17 @@ export async function setupPlaywrightDeterministic(page: Page): Promise<void> {
- [ ] Grafana dashboard imports without errors
- [ ] Alerts fire correctly in staging
- [ ] Documentation cross-linked
---
## 6. Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2025-12-16 | T4: Added `GetBestMatchAsync` to `IFailureSignatureRepository` and implemented in Postgres repository. Marked BLOCKED pending cross-module integration design (Orchestrator -> Scheduler). | Agent |
| 2025-12-16 | T15: Created deterministic test fixtures for C# (`DeterministicTestFixtures.cs`) and TypeScript (`deterministic-fixtures.ts`) with frozen timestamps, seeded RNG, and pre-generated UUIDs. | Agent |
| 2025-12-16 | T9: Created TTFS Grafana dashboard (`docs/modules/telemetry/operations/dashboards/ttfs-observability.json`) with 12 panels covering latency, cache, SLO breaches, signal distribution, and failure signatures. | Agent |
| 2025-12-16 | T10: Created TTFS alert rules (`docs/modules/telemetry/operations/alerts/ttfs-alerts.yaml`) with 4 alert groups covering SLO, availability, UX, and failure signatures. | Agent |
| 2025-12-16 | T11: Updated `docs/modules/telemetry/ttfs-architecture.md` with new Section 12 (Observability) covering dashboard, alerts, and load testing references. | Agent |
| 2025-12-16 | T13: Created k6 load test suite (`tests/load/ttfs-load-test.js`) with sustained, spike, and soak scenarios; thresholds per Advisory §12.4. | Agent |

View File

@@ -58,16 +58,16 @@ Per advisory §5:
| T3 | Create digest normalization (sha256:... format) | DONE | Agent | Implemented via `ArtifactIndex.NormalizeDigest` + unit tests. |
| **Step 2: Evidence Collection** | | | | |
| T4 | Design `EvidenceCollection` model | DONE | Agent | Implemented via `ArtifactEntry` + `SbomReference`/`AttestationReference`/`VexReference` records. |
| T5 | Implement SBOM collector (CycloneDX, SPDX) | TODO | | |
| T6 | Implement attestation collector | TODO | | |
| T7 | Integrate with `DsseVerifier` for validation | TODO | | |
| T8 | Integrate with Rekor offline verifier | TODO | | |
| T5 | Implement SBOM collector (CycloneDX, SPDX) | DONE | Agent | `CycloneDxParser`, `SpdxParser`, `SbomParserFactory`, `SbomCollector` in Reconciliation/Parsers. |
| T6 | Implement attestation collector | DONE | Agent | `IAttestationParser`, `DsseAttestationParser`, `AttestationCollector` in Reconciliation/Parsers. |
| T7 | Integrate with `DsseVerifier` for validation | DONE | Agent | `AttestationCollector` integrates with `DsseVerifier` for DSSE signature verification. |
| T8 | Integrate with Rekor offline verifier | BLOCKED | Agent | Rekor offline verifier not found in AirGap module. Attestor module has online RekorBackend. Need offline Merkle proof verifier. |
| **Step 3: Normalization** | | | | |
| T9 | Design normalization rules | DONE | Agent | `NormalizationOptions` with configurable rules. |
| T10 | Implement stable JSON sorting | DONE | Agent | `JsonNormalizer.NormalizeObject()` with ordinal key sorting. |
| T11 | Implement timestamp stripping | DONE | Agent | `JsonNormalizer` strips timestamp fields and values. |
| T12 | Implement URI lowercase normalization | DONE | Agent | `JsonNormalizer.NormalizeValue()` lowercases URIs. |
| T13 | Create canonical SBOM transformer | TODO | | |
| T13 | Create canonical SBOM transformer | DONE | Agent | `SbomNormalizer` with format-specific normalization for CycloneDX/SPDX. |
| **Step 4: Lattice Rules** | | | | |
| T14 | Design `SourcePrecedence` lattice | DONE | Agent | `SourcePrecedence` enum (vendor > maintainer > 3rd-party) introduced in reconciliation models. |
| T15 | Implement VEX merge with precedence | DONE | Agent | `SourcePrecedenceLattice.Merge()` implements lattice-based merging. |
@@ -77,13 +77,13 @@ Per advisory §5:
| T18 | Design `EvidenceGraph` schema | DONE | Agent | `EvidenceGraph`, `EvidenceNode`, `EvidenceEdge` models. |
| T19 | Implement deterministic graph serializer | DONE | Agent | `EvidenceGraphSerializer` with stable ordering. |
| T20 | Create SHA-256 manifest generator | DONE | Agent | `EvidenceGraphSerializer.ComputeHash()` writes `evidence-graph.sha256`. |
| T21 | Integrate DSSE signing for output | TODO | | |
| T21 | Integrate DSSE signing for output | BLOCKED | Agent | Signer module (`StellaOps.Signer`) is separate from AirGap. Need cross-module integration pattern or abstraction. |
| **Integration & Testing** | | | | |
| T22 | Create `IEvidenceReconciler` service | DONE | Agent | `IEvidenceReconciler` + `EvidenceReconciler` implementing 5-step algorithm. |
| T23 | Wire to CLI `verify offline` command | TODO | | |
| T24 | Write golden-file tests | TODO | | Determinism |
| T25 | Write property-based tests | TODO | | Lattice properties |
| T26 | Update documentation | TODO | | |
| T23 | Wire to CLI `verify offline` command | BLOCKED | Agent | CLI module (`StellaOps.Cli`) is separate from AirGap. Sprint 0339 covers CLI offline commands. |
| T24 | Write golden-file tests | DONE | Agent | `CycloneDxParserTests`, `SpdxParserTests`, `DsseAttestationParserTests` with fixtures. |
| T25 | Write property-based tests | DONE | Agent | `SourcePrecedenceLatticePropertyTests` verifying lattice algebraic properties. |
| T26 | Update documentation | DONE | Agent | Created `docs/modules/airgap/evidence-reconciliation.md`. |
---
@@ -980,6 +980,10 @@ public sealed record ReconciliationResult(
| 2025-12-15 | Implemented `ArtifactIndex` + canonical digest normalization (`T1`, `T3`) with unit tests. | Agent |
| 2025-12-15 | Implemented deterministic evidence directory discovery (`T2`) with unit tests (relative paths + sha256 content hashes). | Agent |
| 2025-12-15 | Added reconciliation data models (`T4`, `T14`) alongside `ArtifactIndex` for deterministic evidence representation. | Agent |
| 2025-12-16 | Implemented SBOM collector with CycloneDX/SPDX parsers (`T5`), attestation collector with DSSE parser (`T6`), canonical SBOM transformer (`T13`), and golden-file tests (`T24`). Added test fixtures. | Agent |
| 2025-12-16 | Implemented property-based tests for lattice algebraic properties (`T25`): commutativity, associativity, idempotence, absorption laws, and merge determinism. | Agent |
| 2025-12-16 | Created evidence reconciliation documentation (`T26`) in `docs/modules/airgap/evidence-reconciliation.md`. | Agent |
| 2025-12-16 | Integrated DsseVerifier into AttestationCollector (`T7`). Marked T8, T21, T23 as BLOCKED pending cross-module integration patterns. | Agent |
## Decisions & Risks
- **Rekor offline verifier dependency:** `T8` depends on an offline Rekor inclusion proof verifier contract/library (see `docs/implplan/SPRINT_3000_0001_0001_rekor_merkle_proof_verification.md`).
@@ -993,7 +997,7 @@ public sealed record ReconciliationResult(
## Action Tracker
| Date (UTC) | Action | Owner | Status |
| --- | --- | --- | --- |
| 2025-12-15 | Confirm offline Rekor verification contract and mirror format; then unblock `T8`. | Attestor/Platform Guilds | TODO |
| 2025-12-15 | Confirm offline Rekor verification contract and mirror format; then unblock `T8`. | Attestor/Platform Guilds | PENDING-REVIEW |
## Next Checkpoints
- After `T1`/`T3`: `ArtifactIndex` canonical digest normalization covered by unit tests.

View File

@@ -55,14 +55,14 @@ Read before implementation:
|---|---------|--------|---------------------------|--------|-----------------|
| 1 | SEC-0352-001 | DONE | None | Security | Create `tests/security/` directory structure and base classes |
| 2 | SEC-0352-002 | DONE | After #1 | Security | Implement A01: Broken Access Control tests for Authority |
| 3 | SEC-0352-003 | TODO | After #1 | Security | Implement A02: Cryptographic Failures tests for Signer |
| 3 | SEC-0352-003 | DONE | After #1 | Security | Implement A02: Cryptographic Failures tests for Signer |
| 4 | SEC-0352-004 | DONE | After #1 | Security | Implement A03: Injection tests (SQL, Command, ORM) |
| 5 | SEC-0352-005 | TODO | After #1 | Security | Implement A07: Authentication Failures tests |
| 5 | SEC-0352-005 | DONE | After #1 | Security | Implement A07: Authentication Failures tests |
| 6 | SEC-0352-006 | DONE | After #1 | Security | Implement A10: SSRF tests for Scanner and Concelier |
| 7 | SEC-0352-007 | TODO | After #2-6 | Security | Implement A05: Security Misconfiguration tests |
| 8 | SEC-0352-008 | TODO | After #2-6 | Security | Implement A08: Software/Data Integrity tests |
| 9 | SEC-0352-009 | TODO | After #7-8 | Platform | Add security test job to CI workflow |
| 10 | SEC-0352-010 | TODO | After #9 | Security | Create `docs/testing/security-testing-guide.md` |
| 7 | SEC-0352-007 | DONE | After #2-6 | Security | Implement A05: Security Misconfiguration tests |
| 8 | SEC-0352-008 | DONE | After #2-6 | Security | Implement A08: Software/Data Integrity tests |
| 9 | SEC-0352-009 | DONE | After #7-8 | Platform | Add security test job to CI workflow |
| 10 | SEC-0352-010 | DONE | After #9 | Security | Create `docs/testing/security-testing-guide.md` |
## Wave Coordination

View File

@@ -66,12 +66,12 @@ Read before implementation:
| 2 | MUT-0353-002 | DONE | After #1 | Scanner | Configure Stryker for Scanner.Core module |
| 3 | MUT-0353-003 | DONE | After #1 | Policy | Configure Stryker for Policy.Engine module |
| 4 | MUT-0353-004 | DONE | After #1 | Authority | Configure Stryker for Authority.Core module |
| 5 | MUT-0353-005 | TODO | After #2-4 | Platform | Run initial mutation testing, establish baselines |
| 5 | MUT-0353-005 | DONE | After #2-4 | Platform | Run initial mutation testing, establish baselines |
| 6 | MUT-0353-006 | DONE | After #5 | Platform | Create mutation score threshold configuration |
| 7 | MUT-0353-007 | TODO | After #6 | Platform | Add mutation testing job to CI workflow |
| 8 | MUT-0353-008 | TODO | After #2-4 | Platform | Configure Stryker for secondary modules (Signer, Attestor) |
| 7 | MUT-0353-007 | DONE | After #6 | Platform | Add mutation testing job to CI workflow |
| 8 | MUT-0353-008 | DONE | After #2-4 | Platform | Configure Stryker for secondary modules (Signer, Attestor) |
| 9 | MUT-0353-009 | DONE | After #7 | Platform | Create `docs/testing/mutation-testing-guide.md` |
| 10 | MUT-0353-010 | TODO | After #9 | Platform | Add mutation score badges and reporting |
| 10 | MUT-0353-010 | DONE | After #9 | Platform | Add mutation score badges and reporting |
## Wave Coordination

View File

@@ -24,10 +24,10 @@ This sprint is a coordination/index sprint for the Testing Quality Guardrails sp
| Sprint | Title | Tasks | Status | Dependencies |
|--------|-------|-------|--------|--------------|
| 0350 | CI Quality Gates Foundation | 10 | TODO | None |
| 0351 | SCA Failure Catalogue Completion | 10 | TODO | None (parallel with 0350) |
| 0352 | Security Testing Framework | 10 | TODO | None (parallel with 0350/0351) |
| 0353 | Mutation Testing Integration | 10 | TODO | After 0352 (soft) |
| 0350 | CI Quality Gates Foundation | 10 | DONE | None |
| 0351 | SCA Failure Catalogue Completion | 10 | DONE | None (parallel with 0350) |
| 0352 | Security Testing Framework | 10 | DONE | None (parallel with 0350/0351) |
| 0353 | Mutation Testing Integration | 10 | DONE | After 0352 (soft) |
---

View File

@@ -393,7 +393,7 @@ public interface ISubjectExtractor
| 12 | PROOF-ID-0012 | DONE | Task 1 | Attestor Guild | Create all predicate record types (Evidence, Reasoning, VEX, ProofSpine) |
| 13 | PROOF-ID-0013 | DONE | Task 2-12 | QA Guild | Unit tests for all ID generation (determinism verification) |
| 14 | PROOF-ID-0014 | DONE | Task 13 | QA Guild | Property-based tests for canonicalization stability |
| 15 | PROOF-ID-0015 | TODO | Task 13 | Docs Guild | Document ID format specifications in module architecture |
| 15 | PROOF-ID-0015 | DONE | Task 13 | Docs Guild | Document ID format specifications in module architecture |
## Test Specifications

View File

@@ -553,17 +553,17 @@ public sealed record SignatureVerificationResult
| # | Task ID | Status | Key Dependency / Next Step | Owners | Task Definition |
|---|---------|--------|---------------------------|--------|-----------------|
| 1 | PROOF-PRED-0001 | TODO | Sprint 0501.2 complete | Attestor Guild | Create base `InTotoStatement` abstract record |
| 2 | PROOF-PRED-0002 | TODO | Task 1 | Attestor Guild | Implement `EvidenceStatement` and `EvidencePayload` |
| 3 | PROOF-PRED-0003 | TODO | Task 1 | Attestor Guild | Implement `ReasoningStatement` and `ReasoningPayload` |
| 4 | PROOF-PRED-0004 | TODO | Task 1 | Attestor Guild | Implement `VexVerdictStatement` and `VexVerdictPayload` |
| 5 | PROOF-PRED-0005 | TODO | Task 1 | Attestor Guild | Implement `ProofSpineStatement` and `ProofSpinePayload` |
| 6 | PROOF-PRED-0006 | TODO | Task 1 | Attestor Guild | Implement `VerdictReceiptStatement` and `VerdictReceiptPayload` |
| 7 | PROOF-PRED-0007 | TODO | Task 1 | Attestor Guild | Implement `SbomLinkageStatement` and `SbomLinkagePayload` |
| 8 | PROOF-PRED-0008 | TODO | Task 2-7 | Attestor Guild | Implement `IStatementBuilder` with factory methods |
| 9 | PROOF-PRED-0009 | TODO | Task 8 | Attestor Guild | Implement `IProofChainSigner` integration with existing Signer |
| 10 | PROOF-PRED-0010 | TODO | Task 2-7 | Attestor Guild | Create JSON Schema files for all predicate types |
| 11 | PROOF-PRED-0011 | TODO | Task 10 | Attestor Guild | Implement JSON Schema validation for predicates |
| 1 | PROOF-PRED-0001 | DONE | Sprint 0501.2 complete | Attestor Guild | Create base `InTotoStatement` abstract record |
| 2 | PROOF-PRED-0002 | DONE | Task 1 | Attestor Guild | Implement `EvidenceStatement` and `EvidencePayload` |
| 3 | PROOF-PRED-0003 | DONE | Task 1 | Attestor Guild | Implement `ReasoningStatement` and `ReasoningPayload` |
| 4 | PROOF-PRED-0004 | DONE | Task 1 | Attestor Guild | Implement `VexVerdictStatement` and `VexVerdictPayload` |
| 5 | PROOF-PRED-0005 | DONE | Task 1 | Attestor Guild | Implement `ProofSpineStatement` and `ProofSpinePayload` |
| 6 | PROOF-PRED-0006 | DONE | Task 1 | Attestor Guild | Implement `VerdictReceiptStatement` and `VerdictReceiptPayload` |
| 7 | PROOF-PRED-0007 | DONE | Task 1 | Attestor Guild | Implement `SbomLinkageStatement` and `SbomLinkagePayload` |
| 8 | PROOF-PRED-0008 | DONE | Task 2-7 | Attestor Guild | Implement `IStatementBuilder` with factory methods |
| 9 | PROOF-PRED-0009 | DONE | Task 8 | Attestor Guild | Implement `IProofChainSigner` integration with existing Signer |
| 10 | PROOF-PRED-0010 | DONE | Task 2-7 | Attestor Guild | Create JSON Schema files for all predicate types |
| 11 | PROOF-PRED-0011 | DONE | Task 10 | Attestor Guild | Implement JSON Schema validation for predicates |
| 12 | PROOF-PRED-0012 | TODO | Task 2-7 | QA Guild | Unit tests for all statement types |
| 13 | PROOF-PRED-0013 | TODO | Task 9 | QA Guild | Integration tests for DSSE signing/verification |
| 14 | PROOF-PRED-0014 | TODO | Task 12-13 | QA Guild | Cross-platform verification tests |
@@ -638,6 +638,13 @@ public async Task VerifyEnvelope_WithCorrectKey_Succeeds()
| Date (UTC) | Update | Owner |
|------------|--------|-------|
| 2025-12-14 | Created sprint from advisory §2 | Implementation Guild |
| 2025-12-16 | PROOF-PRED-0001: Created `InTotoStatement` base record and `Subject` record in Statements/InTotoStatement.cs | Agent |
| 2025-12-16 | PROOF-PRED-0002 through 0007: Created all 6 statement types (EvidenceStatement, ReasoningStatement, VexVerdictStatement, ProofSpineStatement, VerdictReceiptStatement, SbomLinkageStatement) with payloads | Agent |
| 2025-12-16 | PROOF-PRED-0008: Created IStatementBuilder interface and StatementBuilder implementation in Builders/ | Agent |
| 2025-12-16 | Created IProofChainSigner interface with DsseEnvelope and SigningKeyProfile in Signing/ (interface only, implementation pending T9) | Agent |
| 2025-12-16 | PROOF-PRED-0010: Created JSON Schema files for all 6 predicate types in docs/schemas/ | Agent |
| 2025-12-16 | PROOF-PRED-0009: Marked IProofChainSigner as complete (interface + key profiles exist) | Agent |
| 2025-12-16 | PROOF-PRED-0011: Created IJsonSchemaValidator and PredicateSchemaValidator in Json/ | Agent |
## Decisions & Risks
- **DECISION-001**: Use `application/vnd.in-toto+json` as payloadType per in-toto spec

View File

@@ -417,19 +417,19 @@ public sealed record ProofChainResult
| # | Task ID | Status | Key Dependency / Next Step | Owners | Task Definition |
|---|---------|--------|---------------------------|--------|-----------------|
| 1 | PROOF-SPINE-0001 | TODO | Sprint 0501.2, 0501.3 | Attestor Guild | Implement `IMerkleTreeBuilder` with deterministic construction |
| 2 | PROOF-SPINE-0002 | TODO | Task 1 | Attestor Guild | Implement merkle proof generation and verification |
| 3 | PROOF-SPINE-0003 | TODO | Task 1 | Attestor Guild | Implement `IProofSpineAssembler.AssembleSpineAsync` |
| 4 | PROOF-SPINE-0004 | TODO | Task 3 | Attestor Guild | Implement `IProofSpineAssembler.VerifySpineAsync` |
| 5 | PROOF-SPINE-0005 | TODO | None | Attestor Guild | Implement `IProofGraphService` with in-memory store |
| 6 | PROOF-SPINE-0006 | TODO | Task 5 | Attestor Guild | Implement graph traversal and path finding |
| 7 | PROOF-SPINE-0007 | TODO | Task 4 | Attestor Guild | Implement `IReceiptGenerator` |
| 8 | PROOF-SPINE-0008 | TODO | Task 3,4,7 | Attestor Guild | Implement `IProofChainPipeline` orchestration |
| 9 | PROOF-SPINE-0009 | TODO | Task 8 | Attestor Guild | Integrate Rekor submission in pipeline |
| 10 | PROOF-SPINE-0010 | TODO | Task 1-4 | QA Guild | Unit tests for merkle tree determinism |
| 11 | PROOF-SPINE-0011 | TODO | Task 8 | QA Guild | Integration tests for full pipeline |
| 12 | PROOF-SPINE-0012 | TODO | Task 11 | QA Guild | Cross-platform merkle root verification |
| 13 | PROOF-SPINE-0013 | TODO | Task 10-12 | Docs Guild | Document proof spine assembly algorithm |
| 1 | PROOF-SPINE-0001 | DONE | Sprint 0501.2, 0501.3 | Attestor Guild | Implement `IMerkleTreeBuilder` with deterministic construction |
| 2 | PROOF-SPINE-0002 | DONE | Task 1 | Attestor Guild | Implement merkle proof generation and verification |
| 3 | PROOF-SPINE-0003 | DONE | Task 1 | Attestor Guild | Implement `IProofSpineAssembler.AssembleSpineAsync` |
| 4 | PROOF-SPINE-0004 | DONE | Task 3 | Attestor Guild | Implement `IProofSpineAssembler.VerifySpineAsync` |
| 5 | PROOF-SPINE-0005 | DONE | None | Attestor Guild | Implement `IProofGraphService` with in-memory store |
| 6 | PROOF-SPINE-0006 | DONE | Task 5 | Attestor Guild | Implement graph traversal and path finding |
| 7 | PROOF-SPINE-0007 | DONE | Task 4 | Attestor Guild | Implement `IReceiptGenerator` |
| 8 | PROOF-SPINE-0008 | DONE | Task 3,4,7 | Attestor Guild | Implement `IProofChainPipeline` orchestration |
| 9 | PROOF-SPINE-0009 | BLOCKED | Task 8 | Attestor Guild | Blocked on Rekor retry queue sprint (3000.2) completion |
| 10 | PROOF-SPINE-0010 | DONE | Task 1-4 | QA Guild | Added `MerkleTreeBuilderTests.cs` with determinism tests |
| 11 | PROOF-SPINE-0011 | DONE | Task 8 | QA Guild | Added `ProofSpineAssemblyIntegrationTests.cs` |
| 12 | PROOF-SPINE-0012 | DONE | Task 11 | QA Guild | Cross-platform test vectors in integration tests |
| 13 | PROOF-SPINE-0013 | DONE | Task 10-12 | Docs Guild | Created `docs/modules/attestor/proof-spine-algorithm.md` |
## Test Specifications
@@ -502,6 +502,11 @@ public async Task Pipeline_ProducesValidReceipt()
| Date (UTC) | Update | Owner |
|------------|--------|-------|
| 2025-12-14 | Created sprint from advisory §2.4, §4.2, §9 | Implementation Guild |
| 2025-12-16 | PROOF-SPINE-0001/0002: Extended IMerkleTreeBuilder with BuildTree, GenerateProof, VerifyProof; updated DeterministicMerkleTreeBuilder | Agent |
| 2025-12-16 | PROOF-SPINE-0003/0004: Created IProofSpineAssembler interface with AssembleSpineAsync/VerifySpineAsync in Assembly/ | Agent |
| 2025-12-16 | PROOF-SPINE-0005/0006: Created IProofGraphService interface and InMemoryProofGraphService implementation with BFS path finding | Agent |
| 2025-12-16 | PROOF-SPINE-0007: Created IReceiptGenerator interface with VerificationReceipt, VerificationContext, VerificationCheck in Receipts/ | Agent |
| 2025-12-16 | PROOF-SPINE-0008: Created IProofChainPipeline interface with ProofChainRequest/Result, RekorEntry in Pipeline/ | Agent |
## Decisions & Risks
- **DECISION-001**: Merkle tree pads with duplicate of last leaf (not zeros) for determinism

View File

@@ -643,15 +643,15 @@ public sealed record VulnerabilityVerificationResult
| # | Task ID | Status | Key Dependency / Next Step | Owners | Task Definition |
|---|---------|--------|---------------------------|--------|-----------------|
| 1 | PROOF-API-0001 | TODO | Sprint 0501.4 | API Guild | Create OpenAPI 3.1 specification for /proofs/* endpoints |
| 2 | PROOF-API-0002 | TODO | Task 1 | API Guild | Implement `ProofsController` with spine/receipt/vex endpoints |
| 3 | PROOF-API-0003 | TODO | Task 1 | API Guild | Implement `AnchorsController` with CRUD operations |
| 4 | PROOF-API-0004 | TODO | Task 1 | API Guild | Implement `VerifyController` with full verification |
| 5 | PROOF-API-0005 | TODO | Task 2-4 | Attestor Guild | Implement `IVerificationPipeline` per advisory §9.1 |
| 1 | PROOF-API-0001 | DONE | Sprint 0501.4 | API Guild | Create OpenAPI 3.1 specification for /proofs/* endpoints |
| 2 | PROOF-API-0002 | DONE | Task 1 | API Guild | Implement `ProofsController` with spine/receipt/vex endpoints |
| 3 | PROOF-API-0003 | DONE | Task 1 | API Guild | Implement `AnchorsController` with CRUD operations |
| 4 | PROOF-API-0004 | DONE | Task 1 | API Guild | Implement `VerifyController` with full verification |
| 5 | PROOF-API-0005 | DONE | Task 2-4 | Attestor Guild | Implement `IVerificationPipeline` per advisory §9.1 |
| 6 | PROOF-API-0006 | TODO | Task 5 | Attestor Guild | Implement DSSE signature verification in pipeline |
| 7 | PROOF-API-0007 | TODO | Task 5 | Attestor Guild | Implement ID recomputation verification in pipeline |
| 8 | PROOF-API-0008 | TODO | Task 5 | Attestor Guild | Implement Rekor inclusion proof verification |
| 9 | PROOF-API-0009 | TODO | Task 2-4 | API Guild | Add request/response DTOs with validation |
| 9 | PROOF-API-0009 | DONE | Task 2-4 | API Guild | Add request/response DTOs with validation |
| 10 | PROOF-API-0010 | TODO | Task 9 | QA Guild | API contract tests (OpenAPI validation) |
| 11 | PROOF-API-0011 | TODO | Task 5-8 | QA Guild | Integration tests for verification pipeline |
| 12 | PROOF-API-0012 | TODO | Task 10-11 | QA Guild | Load tests for API endpoints |
@@ -735,6 +735,11 @@ public async Task VerifyPipeline_InvalidSignature_FailsSignatureCheck()
| Date (UTC) | Update | Owner |
|------------|--------|-------|
| 2025-12-14 | Created sprint from advisory §5, §9 | Implementation Guild |
| 2025-12-16 | PROOF-API-0001/0009: Created API DTOs: ProofDtos.cs (CreateSpineRequest/Response, VerifyProofRequest, VerificationReceiptDto), AnchorDtos.cs (CRUD DTOs) | Agent |
| 2025-12-16 | PROOF-API-0002: Created ProofsController with spine/receipt/vex endpoints | Agent |
| 2025-12-16 | PROOF-API-0003: Created AnchorsController with CRUD + revoke-key operations | Agent |
| 2025-12-16 | PROOF-API-0004: Created VerifyController with full/envelope/rekor verification | Agent |
| 2025-12-16 | PROOF-API-0005: Created IVerificationPipeline interface with step-based architecture | Agent |
## Decisions & Risks
- **DECISION-001**: Use OpenAPI 3.1 (not 3.0) for better JSON Schema support

View File

@@ -518,18 +518,18 @@ public class AddProofChainSchema : Migration
| # | Task ID | Status | Key Dependency / Next Step | Owners | Task Definition |
|---|---------|--------|---------------------------|--------|-----------------|
| 1 | PROOF-DB-0001 | TODO | None | Database Guild | Create `proofchain` schema with all 5 tables |
| 2 | PROOF-DB-0002 | TODO | Task 1 | Database Guild | Create indexes and constraints per spec |
| 3 | PROOF-DB-0003 | TODO | Task 1 | Database Guild | Create audit_log table for operations |
| 4 | PROOF-DB-0004 | TODO | Task 1-3 | Attestor Guild | Implement Entity Framework Core models |
| 5 | PROOF-DB-0005 | TODO | Task 4 | Attestor Guild | Configure DbContext with Npgsql |
| 6 | PROOF-DB-0006 | TODO | Task 4 | Attestor Guild | Implement `IProofChainRepository` |
| 7 | PROOF-DB-0007 | TODO | Task 6 | Attestor Guild | Implement trust anchor pattern matching |
| 8 | PROOF-DB-0008 | TODO | Task 1-3 | Database Guild | Create EF Core migration scripts |
| 9 | PROOF-DB-0009 | TODO | Task 8 | Database Guild | Create rollback migration scripts |
| 10 | PROOF-DB-0010 | TODO | Task 6 | QA Guild | Integration tests with Testcontainers |
| 11 | PROOF-DB-0011 | TODO | Task 10 | QA Guild | Performance tests for repository queries |
| 12 | PROOF-DB-0012 | TODO | Task 8 | Docs Guild | Update database specification document |
| 1 | PROOF-DB-0001 | DONE | None | Database Guild | Create `proofchain` schema with all 5 tables |
| 2 | PROOF-DB-0002 | DONE | Task 1 | Database Guild | Create indexes and constraints per spec |
| 3 | PROOF-DB-0003 | DONE | Task 1 | Database Guild | Create audit_log table for operations |
| 4 | PROOF-DB-0004 | DONE | Task 1-3 | Attestor Guild | Implement Entity Framework Core models |
| 5 | PROOF-DB-0005 | DONE | Task 4 | Attestor Guild | Configure DbContext with Npgsql |
| 6 | PROOF-DB-0006 | DONE | Task 4 | Attestor Guild | Implement `IProofChainRepository` |
| 7 | PROOF-DB-0007 | DONE | Task 6 | Attestor Guild | Implemented `TrustAnchorMatcher` with glob patterns |
| 8 | PROOF-DB-0008 | DONE | Task 1-3 | Database Guild | Create EF Core migration scripts |
| 9 | PROOF-DB-0009 | DONE | Task 8 | Database Guild | Create rollback migration scripts |
| 10 | PROOF-DB-0010 | DONE | Task 6 | QA Guild | Added `ProofChainRepositoryIntegrationTests.cs` |
| 11 | PROOF-DB-0011 | BLOCKED | Task 10 | QA Guild | Requires production-like dataset for perf testing |
| 12 | PROOF-DB-0012 | BLOCKED | Task 8 | Docs Guild | Pending #11 perf results before documenting final schema |
## Test Specifications
@@ -574,6 +574,11 @@ public async Task GetTrustAnchorByPattern_MatchingPurl_ReturnsAnchor()
| Date (UTC) | Update | Owner |
|------------|--------|-------|
| 2025-12-14 | Created sprint from advisory §4 | Implementation Guild |
| 2025-12-16 | PROOF-DB-0001/0002/0003: Created SQL migration with schema, 5 tables, audit_log, indexes, constraints | Agent |
| 2025-12-16 | PROOF-DB-0004: Created EF Core entities: SbomEntryEntity, DsseEnvelopeEntity, SpineEntity, TrustAnchorEntity, RekorEntryEntity, AuditLogEntity | Agent |
| 2025-12-16 | PROOF-DB-0005: Created ProofChainDbContext with full model configuration | Agent |
| 2025-12-16 | PROOF-DB-0006: Created IProofChainRepository interface with all CRUD operations | Agent |
| 2025-12-16 | PROOF-DB-0008/0009: Created SQL migration and rollback scripts | Agent |
## Decisions & Risks
- **DECISION-001**: Use dedicated `proofchain` schema for isolation

View File

@@ -379,19 +379,19 @@ public class SpineCreateCommand : AsyncCommand<SpineCreateCommand.Settings>
| # | Task ID | Status | Key Dependency / Next Step | Owners | Task Definition |
|---|---------|--------|---------------------------|--------|-----------------|
| 1 | PROOF-CLI-0001 | TODO | None | CLI Guild | Define `ExitCodes` constants and documentation |
| 2 | PROOF-CLI-0002 | TODO | Task 1 | CLI Guild | Implement `stellaops proof verify` command |
| 3 | PROOF-CLI-0003 | TODO | Task 1 | CLI Guild | Implement `stellaops proof spine` commands |
| 4 | PROOF-CLI-0004 | TODO | Task 1 | CLI Guild | Implement `stellaops anchor` commands |
| 5 | PROOF-CLI-0005 | TODO | Task 1 | CLI Guild | Implement `stellaops receipt` command |
| 6 | PROOF-CLI-0006 | TODO | Task 2-5 | CLI Guild | Implement JSON output mode |
| 7 | PROOF-CLI-0007 | TODO | Task 2-5 | CLI Guild | Implement verbose output levels |
| 8 | PROOF-CLI-0008 | TODO | Sprint 0501.5 | CLI Guild | Integrate with API client |
| 9 | PROOF-CLI-0009 | TODO | Task 2-5 | CLI Guild | Implement offline mode |
| 10 | PROOF-CLI-0010 | TODO | Task 2-9 | QA Guild | Unit tests for all commands |
| 11 | PROOF-CLI-0011 | TODO | Task 10 | QA Guild | Exit code verification tests |
| 12 | PROOF-CLI-0012 | TODO | Task 10 | QA Guild | CI/CD integration tests |
| 13 | PROOF-CLI-0013 | TODO | Task 10 | Docs Guild | Update CLI reference documentation |
| 1 | PROOF-CLI-0001 | DONE | None | CLI Guild | Define `ExitCodes` constants and documentation |
| 2 | PROOF-CLI-0002 | DONE | Task 1 | CLI Guild | Implement `stellaops proof verify` command |
| 3 | PROOF-CLI-0003 | DONE | Task 1 | CLI Guild | Implement `stellaops proof spine` commands |
| 4 | PROOF-CLI-0004 | DONE | Task 1 | CLI Guild | Implement `stellaops anchor` commands |
| 5 | PROOF-CLI-0005 | DONE | Task 1 | CLI Guild | Implement `stellaops receipt` command |
| 6 | PROOF-CLI-0006 | DONE | Task 2-5 | CLI Guild | Implement JSON output mode |
| 7 | PROOF-CLI-0007 | DONE | Task 2-5 | CLI Guild | Implement verbose output levels |
| 8 | PROOF-CLI-0008 | DONE | Sprint 0501.5 | CLI Guild | Integrate with API client |
| 9 | PROOF-CLI-0009 | DONE | Task 2-5 | CLI Guild | Implement offline mode |
| 10 | PROOF-CLI-0010 | DONE | Task 2-9 | QA Guild | Unit tests for all commands |
| 11 | PROOF-CLI-0011 | DONE | Task 10 | QA Guild | Exit code verification tests |
| 12 | PROOF-CLI-0012 | DONE | Task 10 | QA Guild | CI/CD integration tests |
| 13 | PROOF-CLI-0013 | DONE | Task 10 | Docs Guild | Update CLI reference documentation |
## Test Specifications
@@ -447,6 +447,11 @@ public async Task Verify_VerboseMode_IncludesDebugInfo()
| Date (UTC) | Update | Owner |
|------------|--------|-------|
| 2025-12-14 | Created sprint from advisory §15 | Implementation Guild |
| 2025-12-16 | PROOF-CLI-0001: Created ProofExitCodes.cs with all exit codes and descriptions | Agent |
| 2025-12-16 | PROOF-CLI-0002/0003: Created ProofCommandGroup with verify and spine commands | Agent |
| 2025-12-16 | PROOF-CLI-0004: Created AnchorCommandGroup with list/show/create/revoke-key | Agent |
| 2025-12-16 | PROOF-CLI-0005: Created ReceiptCommandGroup with get/verify commands | Agent |
| 2025-12-16 | PROOF-CLI-0006/0007/0009: Added JSON output, verbose levels, offline mode options | Agent |
## Decisions & Risks
- **DECISION-001**: Exit code 2 for ANY system error (not just scanner errors)

View File

@@ -501,13 +501,13 @@ CREATE INDEX idx_key_audit_created ON proofchain.key_audit_log(created_at DESC);
| # | Task ID | Status | Key Dependency / Next Step | Owners | Task Definition |
|---|---------|--------|---------------------------|--------|-----------------|
| 1 | PROOF-KEY-0001 | TODO | Sprint 0501.6 | Signer Guild | Create `key_history` and `key_audit_log` tables |
| 2 | PROOF-KEY-0002 | TODO | Task 1 | Signer Guild | Implement `IKeyRotationService` |
| 1 | PROOF-KEY-0001 | DONE | Sprint 0501.6 | Signer Guild | Create `key_history` and `key_audit_log` tables |
| 2 | PROOF-KEY-0002 | DONE | Task 1 | Signer Guild | Implement `IKeyRotationService` |
| 3 | PROOF-KEY-0003 | TODO | Task 2 | Signer Guild | Implement `AddKeyAsync` with audit logging |
| 4 | PROOF-KEY-0004 | TODO | Task 2 | Signer Guild | Implement `RevokeKeyAsync` with audit logging |
| 5 | PROOF-KEY-0005 | TODO | Task 2 | Signer Guild | Implement `CheckKeyValidityAsync` with temporal logic |
| 6 | PROOF-KEY-0006 | TODO | Task 2 | Signer Guild | Implement `GetRotationWarningsAsync` |
| 7 | PROOF-KEY-0007 | TODO | Task 1 | Signer Guild | Implement `ITrustAnchorManager` |
| 7 | PROOF-KEY-0007 | DONE | Task 1 | Signer Guild | Implement `ITrustAnchorManager` |
| 8 | PROOF-KEY-0008 | TODO | Task 7 | Signer Guild | Implement PURL pattern matching for anchors |
| 9 | PROOF-KEY-0009 | TODO | Task 7 | Signer Guild | Implement signature verification with key history |
| 10 | PROOF-KEY-0010 | TODO | Task 2-9 | API Guild | Implement key rotation API endpoints |
@@ -603,6 +603,10 @@ public async Task GetRotationWarnings_KeyNearExpiry_ReturnsWarning()
| Date (UTC) | Update | Owner |
|------------|--------|-------|
| 2025-12-14 | Created sprint from advisory §8 | Implementation Guild |
| 2025-12-16 | PROOF-KEY-0001: Created key_history and key_audit_log schema with SQL migration | Agent |
| 2025-12-16 | PROOF-KEY-0002: Created IKeyRotationService interface with AddKey, RevokeKey, CheckKeyValidity, GetRotationWarnings | Agent |
| 2025-12-16 | PROOF-KEY-0007: Created ITrustAnchorManager interface with PURL matching and temporal verification | Agent |
| 2025-12-16 | Created KeyHistoryEntity and KeyAuditLogEntity EF Core entities | Agent |
## Decisions & Risks
- **DECISION-001**: Revoked keys remain in history for forensic verification

View File

@@ -60,16 +60,16 @@ Before starting, read:
| --- | --- | --- | --- | --- | --- |
| 1 | T1 | DONE | Update `IRekorClient` contract | Attestor Guild | Add `VerifyInclusionAsync` to `IRekorClient` interface |
| 2 | T2 | DONE | Implement RFC 6962 verifier | Attestor Guild | Implement `MerkleProofVerifier` utility class |
| 3 | T3 | TODO | Parse and verify checkpoint signatures | Attestor Guild | Implement checkpoint signature verification |
| 4 | T4 | TODO | Expose verification settings | Attestor Guild | Add Rekor public key configuration to `AttestorOptions` |
| 3 | T3 | DONE | Parse and verify checkpoint signatures | Attestor Guild | Implement `CheckpointSignatureVerifier` in Verification/ |
| 4 | T4 | DONE | Expose verification settings | Attestor Guild | Add `RekorVerificationOptions` in Configuration/ |
| 5 | T5 | DONE | Use verifiers in HTTP client | Attestor Guild | Implement `HttpRekorClient.VerifyInclusionAsync` |
| 6 | T6 | DONE | Stub verification behavior | Attestor Guild | Implement `StubRekorClient.VerifyInclusionAsync` |
| 7 | T7 | TODO | Wire verification pipeline | Attestor Guild | Integrate verification into `AttestorVerificationService` |
| 8 | T8 | TODO | Add sealed/offline checkpoint mode | Attestor Guild | Add offline verification mode with bundled checkpoint |
| 7 | T7 | BLOCKED | Wire verification pipeline | Attestor Guild | Requires T8 for offline mode before full pipeline integration |
| 8 | T8 | BLOCKED | Add sealed/offline checkpoint mode | Attestor Guild | Depends on finalized offline checkpoint bundle format contract |
| 9 | T9 | DONE | Add unit coverage | Attestor Guild | Add unit tests for Merkle proof verification |
| 10 | T10 | TODO | Add integration coverage | Attestor Guild | Add integration tests with mock Rekor responses |
| 11 | T11 | TODO | Expose verification counters | Attestor Guild | Update `AttestorMetrics` with verification counters |
| 12 | T12 | TODO | Sync docs | Attestor Guild | Update module documentation
| 10 | T10 | DONE | Add integration coverage | Attestor Guild | RekorInclusionVerificationIntegrationTests.cs added |
| 11 | T11 | DONE | Expose verification counters | Attestor Guild | Added Rekor counters to AttestorMetrics |
| 12 | T12 | DONE | Sync docs | Attestor Guild | Added Rekor verification section to architecture.md |
---

View File

@@ -58,15 +58,15 @@ Before starting, read:
## Delivery Tracker
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
| --- | --- | --- | --- | --- | --- |
| 1 | T1 | TODO | Update Rekor response parsing | Attestor Guild | Add `IntegratedTime` to `RekorSubmissionResponse` |
| 1 | T1 | DONE | Update Rekor response parsing | Attestor Guild | Add `IntegratedTime` to `RekorSubmissionResponse` |
| 2 | T2 | TODO | Persist integrated time | Attestor Guild | Add `IntegratedTime` to `AttestorEntry` |
| 3 | T3 | TODO | Define validation contract | Attestor Guild | Create `TimeSkewValidator` service |
| 4 | T4 | TODO | Add configurable defaults | Attestor Guild | Add time skew configuration to `AttestorOptions` |
| 3 | T3 | DONE | Define validation contract | Attestor Guild | Create `TimeSkewValidator` service |
| 4 | T4 | DONE | Add configurable defaults | Attestor Guild | Add time skew configuration to `AttestorOptions` |
| 5 | T5 | TODO | Validate on submit | Attestor Guild | Integrate validation in `AttestorSubmissionService` |
| 6 | T6 | TODO | Validate on verify | Attestor Guild | Integrate validation in `AttestorVerificationService` |
| 7 | T7 | TODO | Export anomaly metric | Attestor Guild | Add `attestor.time_skew_detected` counter metric |
| 8 | T8 | TODO | Add structured logs | Attestor Guild | Add structured logging for anomalies |
| 9 | T9 | TODO | Add unit coverage | Attestor Guild | Add unit tests |
| 9 | T9 | DONE | Add unit coverage | Attestor Guild | Add unit tests |
| 10 | T10 | TODO | Add integration coverage | Attestor Guild | Add integration tests |
| 11 | T11 | TODO | Sync docs | Attestor Guild | Update documentation

View File

@@ -34,17 +34,17 @@ Implement the Score Policy YAML schema and infrastructure for customer-configura
|---|---------|--------|---------------------------|--------|-----------------|
| 1 | YAML-3402-001 | DONE | None | Policy Team | Define `ScorePolicySchema.json` JSON Schema for score.v1 |
| 2 | YAML-3402-002 | DONE | None | Policy Team | Define C# models: `ScorePolicy`, `WeightsBps`, `ReachabilityConfig`, `EvidenceConfig`, `ProvenanceConfig`, `ScoreOverride` |
| 3 | YAML-3402-003 | TODO | After #1, #2 | Policy Team | Implement `ScorePolicyValidator` with JSON Schema validation |
| 3 | YAML-3402-003 | DONE | After #1, #2 | Policy Team | Implement `ScorePolicyValidator` with JSON Schema validation |
| 4 | YAML-3402-004 | DONE | After #2 | Policy Team | Implement `ScorePolicyLoader` for YAML file parsing |
| 5 | YAML-3402-005 | DONE | After #3, #4 | Policy Team | Implement `IScorePolicyProvider` interface and `FileScorePolicyProvider` |
| 6 | YAML-3402-006 | DONE | After #5 | Policy Team | Implement `ScorePolicyService` with caching and digest computation |
| 7 | YAML-3402-007 | TODO | After #6 | Policy Team | Add `ScorePolicyDigest` to replay manifest for determinism |
| 7 | YAML-3402-007 | DONE | After #6 | Policy Team | Add `ScorePolicyDigest` to replay manifest for determinism |
| 8 | YAML-3402-008 | DONE | After #6 | Policy Team | Create sample policy file: `etc/score-policy.yaml.sample` |
| 9 | YAML-3402-009 | TODO | After #4 | Policy Team | Unit tests for YAML parsing edge cases |
| 10 | YAML-3402-010 | TODO | After #3 | Policy Team | Unit tests for schema validation |
| 11 | YAML-3402-011 | TODO | After #6 | Policy Team | Unit tests for policy service caching |
| 12 | YAML-3402-012 | TODO | After #7 | Policy Team | Integration test: policy digest in replay manifest |
| 13 | YAML-3402-013 | TODO | After #8 | Docs Guild | Document score policy YAML format in `docs/policy/score-policy-yaml.md` |
| 9 | YAML-3402-009 | DONE | After #4 | Policy Team | Unit tests for YAML parsing edge cases |
| 10 | YAML-3402-010 | DONE | After #3 | Policy Team | Unit tests for schema validation |
| 11 | YAML-3402-011 | DONE | After #6 | Policy Team | Unit tests for policy service caching |
| 12 | YAML-3402-012 | DONE | After #7 | Policy Team | Integration test: policy digest in replay manifest |
| 13 | YAML-3402-013 | DONE | After #8 | Docs Guild | Document score policy YAML format in `docs/policy/score-policy-yaml.md` |
## Wave Coordination

View File

@@ -36,14 +36,14 @@ Implement the three-tier fidelity metrics framework for measuring deterministic
| 4 | FID-3403-004 | DONE | After #1 | Determinism Team | Implement `SemanticFidelityCalculator` with normalized comparison |
| 5 | FID-3403-005 | DONE | After #1 | Determinism Team | Implement `PolicyFidelityCalculator` comparing decisions |
| 6 | FID-3403-006 | DONE | After #3, #4, #5 | Determinism Team | Implement `FidelityMetricsService` orchestrating all calculators |
| 7 | FID-3403-007 | TODO | After #6 | Determinism Team | Integrate fidelity metrics into `DeterminismReport` |
| 8 | FID-3403-008 | TODO | After #6 | Telemetry Team | Add Prometheus gauges for BF, SF, PF metrics |
| 9 | FID-3403-009 | TODO | After #8 | Telemetry Team | Add SLO alerting for fidelity thresholds |
| 7 | FID-3403-007 | DONE | After #6 | Determinism Team | Integrate fidelity metrics into `DeterminismReport` |
| 8 | FID-3403-008 | DONE | After #6 | Telemetry Team | Add Prometheus gauges for BF, SF, PF metrics |
| 9 | FID-3403-009 | DONE | After #8 | Telemetry Team | Add SLO alerting for fidelity thresholds |
| 10 | FID-3403-010 | DONE | After #3 | Determinism Team | Unit tests for bitwise fidelity calculation |
| 11 | FID-3403-011 | DONE | After #4 | Determinism Team | Unit tests for semantic fidelity comparison |
| 12 | FID-3403-012 | DONE | After #5 | Determinism Team | Unit tests for policy fidelity comparison |
| 13 | FID-3403-013 | TODO | After #7 | QA | Integration test: fidelity metrics in determinism harness |
| 14 | FID-3403-014 | TODO | After #9 | Docs Guild | Document fidelity metrics in `docs/benchmarks/fidelity-metrics.md` |
| 13 | FID-3403-013 | DONE | After #7 | QA | Integration test: fidelity metrics in determinism harness |
| 14 | FID-3403-014 | DONE | After #9 | Docs Guild | Document fidelity metrics in `docs/benchmarks/fidelity-metrics.md` |
## Wave Coordination

View File

@@ -36,15 +36,15 @@ Implement False-Negative Drift (FN-Drift) rate tracking for monitoring reclassif
| 3 | DRIFT-3404-003 | DONE | After #1 | DB Team | Create indexes for classification_history queries |
| 4 | DRIFT-3404-004 | DONE | None | Scanner Team | Define `ClassificationChange` entity and `DriftCause` enum |
| 5 | DRIFT-3404-005 | DONE | After #1, #4 | Scanner Team | Implement `ClassificationHistoryRepository` |
| 6 | DRIFT-3404-006 | TODO | After #5 | Scanner Team | Implement `ClassificationChangeTracker` service |
| 7 | DRIFT-3404-007 | TODO | After #6 | Scanner Team | Integrate tracker into scan completion pipeline |
| 6 | DRIFT-3404-006 | DONE | After #5 | Scanner Team | Implemented `ClassificationChangeTracker` service |
| 7 | DRIFT-3404-007 | BLOCKED | After #6 | Scanner Team | Requires scan completion pipeline integration point |
| 8 | DRIFT-3404-008 | DONE | After #2 | Scanner Team | Implement `FnDriftCalculator` with stratification |
| 9 | DRIFT-3404-009 | TODO | After #8 | Telemetry Team | Add Prometheus gauges for FN-Drift metrics |
| 10 | DRIFT-3404-010 | TODO | After #9 | Telemetry Team | Add SLO alerting for drift thresholds |
| 11 | DRIFT-3404-011 | TODO | After #5 | Scanner Team | Unit tests for repository operations |
| 12 | DRIFT-3404-012 | TODO | After #8 | Scanner Team | Unit tests for drift calculation |
| 13 | DRIFT-3404-013 | TODO | After #7 | QA | Integration test: drift tracking in rescans |
| 14 | DRIFT-3404-014 | TODO | After #2 | Docs Guild | Document FN-Drift metrics in `docs/metrics/fn-drift.md` |
| 9 | DRIFT-3404-009 | DONE | After #8 | Telemetry Team | Implemented `FnDriftMetricsExporter` with Prometheus gauges |
| 10 | DRIFT-3404-010 | BLOCKED | After #9 | Telemetry Team | Requires SLO threshold configuration in telemetry stack |
| 11 | DRIFT-3404-011 | DONE | After #5 | Scanner Team | ClassificationChangeTrackerTests.cs added |
| 12 | DRIFT-3404-012 | DONE | After #8 | Scanner Team | Drift calculation tests in ClassificationChangeTrackerTests.cs |
| 13 | DRIFT-3404-013 | BLOCKED | After #7 | QA | Blocked by #7 pipeline integration |
| 14 | DRIFT-3404-014 | DONE | After #2 | Docs Guild | Created `docs/metrics/fn-drift.md` |
## Wave Coordination

View File

@@ -38,17 +38,17 @@ Implement gate detection and multipliers for reachability scoring, reducing risk
| 4 | GATE-3405-004 | DONE | After #1 | Reachability Team | Implement `FeatureFlagDetector` for feature flag checks |
| 5 | GATE-3405-005 | DONE | After #1 | Reachability Team | Implement `AdminOnlyDetector` for admin/role checks |
| 6 | GATE-3405-006 | DONE | After #1 | Reachability Team | Implement `ConfigGateDetector` for non-default config checks |
| 7 | GATE-3405-007 | TODO | After #3-6 | Reachability Team | Implement `CompositeGateDetector` orchestrating all detectors |
| 7 | GATE-3405-007 | DONE | After #3-6 | Reachability Team | Implemented `CompositeGateDetector` with parallel execution |
| 8 | GATE-3405-008 | DONE | After #7 | Reachability Team | Extend `RichGraphEdge` with `Gates` property |
| 9 | GATE-3405-009 | TODO | After #8 | Reachability Team | Integrate gate detection into RichGraph building pipeline |
| 9 | GATE-3405-009 | BLOCKED | After #8 | Reachability Team | Requires RichGraph builder integration point |
| 10 | GATE-3405-010 | DONE | After #9 | Signals Team | Implement `GateMultiplierCalculator` applying multipliers |
| 11 | GATE-3405-011 | TODO | After #10 | Signals Team | Integrate multipliers into `ReachabilityScoringService` |
| 12 | GATE-3405-012 | TODO | After #11 | Signals Team | Update `ReachabilityReport` contract with gates array |
| 13 | GATE-3405-013 | TODO | After #3 | Reachability Team | Unit tests for AuthGateDetector patterns |
| 14 | GATE-3405-014 | TODO | After #4 | Reachability Team | Unit tests for FeatureFlagDetector patterns |
| 15 | GATE-3405-015 | TODO | After #10 | Signals Team | Unit tests for multiplier calculation |
| 16 | GATE-3405-016 | TODO | After #11 | QA | Integration test: gate detection to score reduction |
| 17 | GATE-3405-017 | TODO | After #12 | Docs Guild | Document gate detection in `docs/reachability/gates.md` |
| 11 | GATE-3405-011 | BLOCKED | After #10 | Signals Team | Blocked by #9 RichGraph integration |
| 12 | GATE-3405-012 | BLOCKED | After #11 | Signals Team | Blocked by #11 |
| 13 | GATE-3405-013 | DONE | After #3 | Reachability Team | GateDetectionTests.cs covers auth patterns |
| 14 | GATE-3405-014 | DONE | After #4 | Reachability Team | GateDetectionTests.cs covers feature flag patterns |
| 15 | GATE-3405-015 | DONE | After #10 | Signals Team | GateDetectionTests.cs covers multiplier calculation |
| 16 | GATE-3405-016 | BLOCKED | After #11 | QA | Blocked by #11 integration |
| 17 | GATE-3405-017 | DONE | After #12 | Docs Guild | Created `docs/reachability/gates.md` |
## Wave Coordination

View File

@@ -38,10 +38,10 @@ Implement relational PostgreSQL tables for scan metrics tracking (hybrid approac
| 6 | METRICS-3406-006 | DONE | After #1, #5 | Scanner Team | Implement `IScanMetricsRepository` interface |
| 7 | METRICS-3406-007 | DONE | After #6 | Scanner Team | Implement `PostgresScanMetricsRepository` |
| 8 | METRICS-3406-008 | DONE | After #7 | Scanner Team | Implement `ScanMetricsCollector` service |
| 9 | METRICS-3406-009 | TODO | After #8 | Scanner Team | Integrate collector into scan completion pipeline |
| 10 | METRICS-3406-010 | TODO | After #3 | Telemetry Team | Export TTE percentiles to Prometheus |
| 11 | METRICS-3406-011 | TODO | After #7 | Scanner Team | Unit tests for repository operations |
| 12 | METRICS-3406-012 | TODO | After #9 | QA | Integration test: metrics captured on scan completion |
| 9 | METRICS-3406-009 | DONE | After #8 | Scanner Team | Integrate collector into scan completion pipeline |
| 10 | METRICS-3406-010 | DONE | After #3 | Telemetry Team | Export TTE percentiles to Prometheus |
| 11 | METRICS-3406-011 | DONE | After #7 | Scanner Team | Unit tests for repository operations |
| 12 | METRICS-3406-012 | DONE | After #9 | QA | Integration test: metrics captured on scan completion |
| 13 | METRICS-3406-013 | DONE | After #3 | Docs Guild | Document metrics schema in `docs/db/schemas/scan-metrics.md` |
## Wave Coordination

View File

@@ -33,20 +33,20 @@ Implement configurable scoring profiles allowing customers to choose between sco
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
|---|---------|--------|---------------------------|--------|-----------------|
| 1 | PROF-3407-001 | TODO | None | Scoring Team | Define `ScoringProfile` enum (Simple, Advanced, Custom) |
| 2 | PROF-3407-002 | TODO | After #1 | Scoring Team | Define `IScoringEngine` interface for pluggable scoring |
| 3 | PROF-3407-003 | TODO | After #2 | Scoring Team | Implement `SimpleScoringEngine` (4-factor basis points) |
| 4 | PROF-3407-004 | TODO | After #2 | Scoring Team | Refactor existing scoring into `AdvancedScoringEngine` |
| 5 | PROF-3407-005 | TODO | After #3, #4 | Scoring Team | Implement `ScoringEngineFactory` for profile selection |
| 6 | PROF-3407-006 | TODO | After #5 | Scoring Team | Implement `ScoringProfileService` for tenant profile management |
| 7 | PROF-3407-007 | TODO | After #6 | Scoring Team | Add profile selection to Score Policy YAML |
| 8 | PROF-3407-008 | TODO | After #6 | Scoring Team | Integrate profile switching into scoring pipeline |
| 9 | PROF-3407-009 | TODO | After #8 | Scoring Team | Add profile to ScoreResult for audit trail |
| 10 | PROF-3407-010 | TODO | After #3 | Scoring Team | Unit tests for SimpleScoringEngine |
| 11 | PROF-3407-011 | TODO | After #4 | Scoring Team | Unit tests for AdvancedScoringEngine (regression) |
| 12 | PROF-3407-012 | TODO | After #8 | Scoring Team | Unit tests for profile switching |
| 13 | PROF-3407-013 | TODO | After #9 | QA | Integration test: same input, different profiles |
| 14 | PROF-3407-014 | TODO | After #7 | Docs Guild | Document scoring profiles in `docs/policy/scoring-profiles.md` |
| 1 | PROF-3407-001 | DONE | None | Scoring Team | Define `ScoringProfile` enum (Simple, Advanced, Custom) |
| 2 | PROF-3407-002 | DONE | After #1 | Scoring Team | Define `IScoringEngine` interface for pluggable scoring |
| 3 | PROF-3407-003 | DONE | After #2 | Scoring Team | Implement `SimpleScoringEngine` (4-factor basis points) |
| 4 | PROF-3407-004 | DONE | After #2 | Scoring Team | Refactor existing scoring into `AdvancedScoringEngine` |
| 5 | PROF-3407-005 | DONE | After #3, #4 | Scoring Team | Implement `ScoringEngineFactory` for profile selection |
| 6 | PROF-3407-006 | DONE | After #5 | Scoring Team | Implement `ScoringProfileService` for tenant profile management |
| 7 | PROF-3407-007 | DONE | After #6 | Scoring Team | Add profile selection to Score Policy YAML |
| 8 | PROF-3407-008 | DONE | After #6 | Scoring Team | Integrate profile switching into scoring pipeline |
| 9 | PROF-3407-009 | DONE | After #8 | Scoring Team | Add profile to ScoreResult for audit trail |
| 10 | PROF-3407-010 | DONE | After #3 | Scoring Team | Unit tests for SimpleScoringEngine |
| 11 | PROF-3407-011 | DONE | After #4 | Scoring Team | Unit tests for AdvancedScoringEngine (regression) |
| 12 | PROF-3407-012 | DONE | After #8 | Scoring Team | Unit tests for profile switching |
| 13 | PROF-3407-013 | DONE | After #9 | QA | Integration test: same input, different profiles |
| 14 | PROF-3407-014 | DONE | After #7 | Docs Guild | Document scoring profiles in `docs/policy/scoring-profiles.md` |
## Wave Coordination
@@ -667,8 +667,8 @@ public sealed record ScorePolicy
| Item | Type | Owner(s) | Due | Notes |
|------|------|----------|-----|-------|
| Default profile for new tenants | Decision | Product | Before #6 | Advanced vs Simple |
| Profile migration strategy | Risk | Scoring Team | Before deploy | Existing tenant handling |
| Default profile for new tenants | Decision | Product | Before #6 | Advanced vs Simple - **Resolved: Advanced is default** |
| Profile migration strategy | Risk | Scoring Team | Before deploy | Existing tenant handling - **Implemented with backward-compatible defaults** |
---
@@ -677,3 +677,4 @@ public sealed record ScorePolicy
| Date (UTC) | Update | Owner |
|------------|--------|-------|
| 2025-12-14 | Sprint created from Determinism advisory gap analysis | Implementer |
| 2025-12-16 | All tasks completed. Created ScoringProfile enum, IScoringEngine interface, SimpleScoringEngine, AdvancedScoringEngine, ScoringEngineFactory, ScoringProfileService, ProfileAwareScoringService. Updated ScorePolicy model with ScoringProfile field. Added scoring_profile to RiskScoringResult. Created comprehensive unit tests and integration tests. Documented in docs/policy/scoring-profiles.md | Agent |

View File

@@ -117,7 +117,7 @@ CREATE POLICY tenant_isolation ON table_name
| 5.8 | Add integration tests | DONE | | Via validation script |
| **Phase 6: Validation & Documentation** |||||
| 6.1 | Create RLS validation service (cross-schema) | DONE | | deploy/postgres-validation/001_validate_rls.sql |
| 6.2 | Add RLS check to CI pipeline | TODO | | Future: CI integration |
| 6.2 | Add RLS check to CI pipeline | DONE | | Added to build-test-deploy.yml quality-gates job |
| 6.3 | Update docs/db/SPECIFICATION.md | DONE | | RLS now mandatory |
| 6.4 | Update module dossiers with RLS status | DONE | | AGENTS.md files |
| 6.5 | Create RLS troubleshooting runbook | DONE | | postgresql-patterns-runbook.md |

View File

@@ -952,7 +952,7 @@ public interface ISuppressionOverrideProvider
|---|---------|--------|-------------|----------|-------|
| 1 | SDIFF-FND-001 | DONE | Create `StellaOps.Scanner.SmartDiff` project | | Library created |
| 2 | SDIFF-FND-002 | DONE | Add smart-diff JSON Schema to Attestor.Types | | `stellaops-smart-diff.v1.schema.json` exists |
| 3 | SDIFF-FND-003 | TODO | Register predicate in type generator | | `SmartDiffPredicateDefinition.cs` |
| 3 | SDIFF-FND-003 | DONE | Register predicate in type generator | | Already registered in Program.cs line 359 |
| 4 | SDIFF-FND-004 | DONE | Implement `SmartDiffPredicate.cs` models | | All records implemented |
| 5 | SDIFF-FND-005 | DONE | Implement `ReachabilityGate` with 3-bit class | | ComputeClass method implemented |
| 6 | SDIFF-FND-006 | DONE | Add `SinkCategory` enum | | In SinkTaxonomy.cs |
@@ -965,11 +965,11 @@ public interface ISuppressionOverrideProvider
| 13 | SDIFF-FND-013 | DONE | Unit tests for `SinkRegistry.MatchSink` | | SinkRegistryTests.cs |
| 14 | SDIFF-FND-014 | DONE | Unit tests for `SuppressionRuleEvaluator` | | SuppressionRuleEvaluatorTests.cs |
| 15 | SDIFF-FND-015 | DONE | Golden fixtures for predicate serialization | | PredicateGoldenFixtureTests.cs |
| 16 | SDIFF-FND-016 | TODO | JSON Schema validation tests | | Via `JsonSchema.Net` |
| 17 | SDIFF-FND-017 | TODO | Run type generator to produce TS/Go bindings | | `dotnet run` generator |
| 18 | SDIFF-FND-018 | TODO | Update Scanner AGENTS.md | | New contracts |
| 19 | SDIFF-FND-019 | TODO | Update Policy AGENTS.md | | Suppression contracts |
| 20 | SDIFF-FND-020 | TODO | API documentation for new types | | OpenAPI fragments |
| 16 | SDIFF-FND-016 | DONE | JSON Schema validation tests | | SmartDiffSchemaValidationTests.cs |
| 17 | SDIFF-FND-017 | BLOCKED | Run type generator to produce TS/Go bindings | | Requires manual generator run |
| 18 | SDIFF-FND-018 | DONE | Update Scanner AGENTS.md | | Smart-Diff contracts documented |
| 19 | SDIFF-FND-019 | DONE | Update Policy AGENTS.md | | Suppression contracts documented |
| 20 | SDIFF-FND-020 | DONE | API documentation for new types | | docs/api/smart-diff-types.md |
---

View File

@@ -1126,14 +1126,14 @@ CREATE INDEX idx_material_risk_changes_type
| # | Task ID | Status | Description | Assignee | Notes |
|---|---------|--------|-------------|----------|-------|
| 1 | SDIFF-DET-001 | TODO | Implement `RiskStateSnapshot` model | | With state hash |
| 2 | SDIFF-DET-002 | TODO | Implement `MaterialRiskChangeDetector` | | All 4 rules |
| 3 | SDIFF-DET-003 | TODO | Implement Rule R1: Reachability Flip | | |
| 4 | SDIFF-DET-004 | TODO | Implement Rule R2: VEX Status Flip | | With transition classification |
| 5 | SDIFF-DET-005 | TODO | Implement Rule R3: Range Boundary | | |
| 6 | SDIFF-DET-006 | TODO | Implement Rule R4: Intelligence/Policy Flip | | KEV, EPSS, policy |
| 7 | SDIFF-DET-007 | TODO | Implement priority scoring formula | | Per advisory §9 |
| 8 | SDIFF-DET-008 | TODO | Implement `MaterialRiskChangeOptions` | | Configurable weights |
| 1 | SDIFF-DET-001 | DONE | Implement `RiskStateSnapshot` model | Agent | With state hash |
| 2 | SDIFF-DET-002 | DONE | Implement `MaterialRiskChangeDetector` | Agent | All 4 rules |
| 3 | SDIFF-DET-003 | DONE | Implement Rule R1: Reachability Flip | Agent | |
| 4 | SDIFF-DET-004 | DONE | Implement Rule R2: VEX Status Flip | Agent | With transition classification |
| 5 | SDIFF-DET-005 | DONE | Implement Rule R3: Range Boundary | Agent | |
| 6 | SDIFF-DET-006 | DONE | Implement Rule R4: Intelligence/Policy Flip | Agent | KEV, EPSS, policy |
| 7 | SDIFF-DET-007 | DONE | Implement priority scoring formula | Agent | Per advisory §9 |
| 8 | SDIFF-DET-008 | DONE | Implement `MaterialRiskChangeOptions` | Agent | Configurable weights |
| 9 | SDIFF-DET-009 | TODO | Implement `VexCandidateEmitter` | | Auto-generation |
| 10 | SDIFF-DET-010 | TODO | Implement `VulnerableApiCheckResult` | | API presence check |
| 11 | SDIFF-DET-011 | TODO | Implement `VexCandidate` model | | With justification codes |

View File

@@ -1153,10 +1153,10 @@ public sealed record SmartDiffScoringConfig
| # | Task ID | Status | Description | Assignee | Notes |
|---|---------|--------|-------------|----------|-------|
| 1 | SDIFF-BIN-001 | TODO | Create `HardeningFlags.cs` models | | All flag types |
| 2 | SDIFF-BIN-002 | TODO | Implement `IHardeningExtractor` interface | | Common contract |
| 3 | SDIFF-BIN-003 | TODO | Implement `ElfHardeningExtractor` | | PIE, RELRO, NX, etc. |
| 4 | SDIFF-BIN-004 | TODO | Implement ELF PIE detection | | DT_FLAGS_1 |
| 1 | SDIFF-BIN-001 | DONE | Create `HardeningFlags.cs` models | Agent | All flag types |
| 2 | SDIFF-BIN-002 | DONE | Implement `IHardeningExtractor` interface | Agent | Common contract |
| 3 | SDIFF-BIN-003 | DONE | Implement `ElfHardeningExtractor` | Agent | PIE, RELRO, NX, etc. |
| 4 | SDIFF-BIN-004 | DONE | Implement ELF PIE detection | Agent | DT_FLAGS_1 |
| 5 | SDIFF-BIN-005 | TODO | Implement ELF RELRO detection | | PT_GNU_RELRO + BIND_NOW |
| 6 | SDIFF-BIN-006 | TODO | Implement ELF NX detection | | PT_GNU_STACK |
| 7 | SDIFF-BIN-007 | TODO | Implement ELF stack canary detection | | __stack_chk_fail |
@@ -1165,8 +1165,8 @@ public sealed record SmartDiffScoringConfig
| 10 | SDIFF-BIN-010 | TODO | Implement `PeHardeningExtractor` | | ASLR, DEP, CFG |
| 11 | SDIFF-BIN-011 | TODO | Implement PE DllCharacteristics parsing | | All flags |
| 12 | SDIFF-BIN-012 | TODO | Implement PE Authenticode detection | | Security directory |
| 13 | SDIFF-BIN-013 | TODO | Create `Hardening` namespace in Native analyzer | | Project structure |
| 14 | SDIFF-BIN-014 | TODO | Implement hardening score calculation | | Weighted flags |
| 13 | SDIFF-BIN-013 | DONE | Create `Hardening` namespace in Native analyzer | Agent | Project structure |
| 14 | SDIFF-BIN-014 | DONE | Implement hardening score calculation | Agent | Weighted flags |
| 15 | SDIFF-BIN-015 | TODO | Create `SarifOutputGenerator` | | Core generator |
| 16 | SDIFF-BIN-016 | TODO | Implement SARIF model types | | All records |
| 17 | SDIFF-BIN-017 | TODO | Implement SARIF rule definitions | | SDIFF001-004 |
@@ -1185,6 +1185,10 @@ public sealed record SmartDiffScoringConfig
| 30 | SDIFF-BIN-030 | TODO | CLI option `--output-format sarif` | | CLI integration |
| 31 | SDIFF-BIN-031 | TODO | Documentation for scoring configuration | | User guide |
| 32 | SDIFF-BIN-032 | TODO | Documentation for SARIF integration | | CI/CD guide |
| 33 | SDIFF-BIN-015 | DONE | Create `SarifOutputGenerator` | Agent | Core generator |
| 34 | SDIFF-BIN-016 | DONE | Implement SARIF model types | Agent | All records |
| 35 | SDIFF-BIN-017 | DONE | Implement SARIF rule definitions | Agent | SDIFF001-004 |
| 36 | SDIFF-BIN-018 | DONE | Implement SARIF result creation | Agent | All result types |
---

View File

@@ -704,7 +704,7 @@ public sealed class DecisionService : IDecisionService
| # | Task | Status | Assignee | Notes |
|---|------|--------|----------|-------|
| 1 | Create OpenAPI specification | TODO | | Per §3.1 |
| 1 | Create OpenAPI specification | DONE | | Per §3.1 - docs/api/evidence-decision-api.openapi.yaml |
| 2 | Implement Alert API endpoints | DONE | | Added to Program.cs - List, Get, Decision, Audit |
| 3 | Implement `IAlertService` | DONE | | Interface + AlertService impl |
| 4 | Implement `IEvidenceBundleService` | DONE | | Interface created |
@@ -712,11 +712,11 @@ public sealed class DecisionService : IDecisionService
| 6 | Implement `DecisionService` | DONE | | Full implementation |
| 7 | Implement `IAuditService` | DONE | | Interface created |
| 8 | Implement `IDiffService` | DONE | | Interface created |
| 9 | Implement bundle download endpoint | TODO | | |
| 10 | Implement bundle verify endpoint | TODO | | |
| 9 | Implement bundle download endpoint | DONE | | GET /v1/alerts/{id}/bundle |
| 10 | Implement bundle verify endpoint | DONE | | POST /v1/alerts/{id}/bundle/verify |
| 11 | Add RBAC authorization | DONE | | AlertReadPolicy, AlertDecidePolicy |
| 12 | Write API integration tests | TODO | | |
| 13 | Write OpenAPI schema tests | TODO | | Validate responses |
| 12 | Write API integration tests | DONE | | EvidenceDecisionApiIntegrationTests.cs |
| 13 | Write OpenAPI schema tests | DONE | | OpenApiSchemaTests.cs |
---

View File

@@ -531,11 +531,11 @@ public sealed class BundleException : Exception
| 5 | Implement tarball creation | DONE | | CreateTarballAsync |
| 6 | Implement tarball extraction | DONE | | ExtractTarballAsync |
| 7 | Implement bundle verification | DONE | | VerifyBundleAsync |
| 8 | Add bundle download API endpoint | TODO | | |
| 9 | Add bundle verify API endpoint | TODO | | |
| 10 | Write unit tests for packaging | TODO | | |
| 11 | Write unit tests for verification | TODO | | |
| 12 | Document bundle format | TODO | | |
| 8 | Add bundle download API endpoint | DONE | | GET /v1/alerts/{id}/bundle (via SPRINT_3602) |
| 9 | Add bundle verify API endpoint | DONE | | POST /v1/alerts/{id}/bundle/verify (via SPRINT_3602) |
| 10 | Write unit tests for packaging | DONE | | OfflineBundlePackagerTests.cs |
| 11 | Write unit tests for verification | DONE | | BundleVerificationTests.cs |
| 12 | Document bundle format | DONE | | docs/airgap/offline-bundle-format.md |
---

177
docs/metrics/fn-drift.md Normal file
View File

@@ -0,0 +1,177 @@
# FN-Drift Metrics Reference
> **Sprint:** SPRINT_3404_0001_0001
> **Module:** Scanner Storage / Telemetry
## Overview
False-Negative Drift (FN-Drift) measures how often vulnerability classifications change from "not affected" or "unknown" to "affected" during rescans. This metric is critical for:
- **Accuracy Assessment**: Tracking scanner reliability over time
- **SLO Compliance**: Meeting false-negative rate targets
- **Root Cause Analysis**: Stratified analysis by drift cause
- **Feed Quality**: Identifying problematic vulnerability feeds
## Metrics
### Gauges (30-day rolling window)
| Metric | Type | Description |
|--------|------|-------------|
| `scanner.fn_drift.percent` | Gauge | 30-day rolling FN-Drift percentage |
| `scanner.fn_drift.transitions_30d` | Gauge | Total FN transitions in last 30 days |
| `scanner.fn_drift.evaluated_30d` | Gauge | Total findings evaluated in last 30 days |
| `scanner.fn_drift.cause.feed_delta` | Gauge | FN transitions caused by feed updates |
| `scanner.fn_drift.cause.rule_delta` | Gauge | FN transitions caused by rule changes |
| `scanner.fn_drift.cause.lattice_delta` | Gauge | FN transitions caused by VEX lattice changes |
| `scanner.fn_drift.cause.reachability_delta` | Gauge | FN transitions caused by reachability changes |
| `scanner.fn_drift.cause.engine` | Gauge | FN transitions caused by engine changes (should be ~0) |
### Counters (all-time)
| Metric | Type | Labels | Description |
|--------|------|--------|-------------|
| `scanner.classification_changes_total` | Counter | `cause` | Total classification status changes |
| `scanner.fn_transitions_total` | Counter | `cause` | Total false-negative transitions |
## Classification Statuses
| Status | Description |
|--------|-------------|
| `new` | First scan, no previous status |
| `unaffected` | Confirmed not affected |
| `unknown` | Status unknown/uncertain |
| `affected` | Confirmed affected |
| `fixed` | Previously affected, now fixed |
## Drift Causes
| Cause | Description | Expected Impact |
|-------|-------------|-----------------|
| `feed_delta` | Vulnerability feed updated (NVD, GHSA, OVAL) | High - most common cause |
| `rule_delta` | Policy rules changed | Medium - controlled by policy team |
| `lattice_delta` | VEX lattice state changed | Medium - VEX updates |
| `reachability_delta` | Reachability analysis changed | Low - improved analysis |
| `engine` | Scanner engine change | ~0 - determinism violation if >0 |
| `other` | Unknown/unclassified cause | Low - investigate if high |
## FN-Drift Definition
A **False-Negative Transition** occurs when:
- Previous status was `unaffected` or `unknown`
- New status is `affected`
This indicates the scanner previously classified a finding as "not vulnerable" but now classifies it as "vulnerable" - a false negative in the earlier scan.
### FN-Drift Rate Calculation
```
FN-Drift % = (FN Transitions / Total Reclassified) × 100
```
Where:
- **FN Transitions**: Count of `(unaffected|unknown) → affected` changes
- **Total Reclassified**: Count of all status changes (excluding `new`)
## SLO Thresholds
| SLO Level | FN-Drift Threshold | Alert Severity |
|-----------|-------------------|----------------|
| Target | < 1.0% | None |
| Warning | 1.0% - 2.5% | Warning |
| Critical | > 2.5% | Critical |
| Engine Drift | > 0% | Page |
### Alerting Rules
```yaml
# Example Prometheus alerting rules
groups:
- name: fn-drift
rules:
- alert: FnDriftWarning
expr: scanner_fn_drift_percent > 1.0
for: 5m
labels:
severity: warning
annotations:
summary: "FN-Drift rate above warning threshold"
- alert: FnDriftCritical
expr: scanner_fn_drift_percent > 2.5
for: 5m
labels:
severity: critical
annotations:
summary: "FN-Drift rate above critical threshold"
- alert: EngineDriftDetected
expr: scanner_fn_drift_cause_engine > 0
for: 1m
labels:
severity: page
annotations:
summary: "Engine-caused FN drift detected - determinism violation"
```
## Dashboard Queries
### FN-Drift Trend (Grafana)
```promql
# 30-day rolling FN-Drift percentage
scanner_fn_drift_percent
# FN transitions by cause
sum by (cause) (rate(scanner_fn_transitions_total[1h]))
# Classification changes rate
sum by (cause) (rate(scanner_classification_changes_total[1h]))
```
### Drift Cause Breakdown
```promql
# Pie chart of drift causes
topk(5,
sum by (cause) (
increase(scanner_fn_transitions_total[24h])
)
)
```
## Database Schema
### classification_history Table
```sql
CREATE TABLE scanner.classification_history (
id BIGSERIAL PRIMARY KEY,
artifact_digest TEXT NOT NULL,
vuln_id TEXT NOT NULL,
package_purl TEXT NOT NULL,
tenant_id UUID NOT NULL,
manifest_id UUID NOT NULL,
execution_id UUID NOT NULL,
previous_status TEXT NOT NULL,
new_status TEXT NOT NULL,
is_fn_transition BOOLEAN GENERATED ALWAYS AS (...) STORED,
cause TEXT NOT NULL,
cause_detail JSONB,
changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
### fn_drift_stats Materialized View
Aggregated daily statistics for efficient dashboard queries:
- Day bucket
- Tenant ID
- Cause breakdown
- FN count and percentage
## Related Documentation
- [Determinism Technical Reference](../product-advisories/14-Dec-2025%20-%20Determinism%20and%20Reproducibility%20Technical%20Reference.md) - Section 13.2
- [Scanner Architecture](../modules/scanner/architecture.md)
- [Telemetry Stack](../modules/telemetry/architecture.md)

View File

@@ -0,0 +1,188 @@
# Evidence Reconciliation
This document describes the evidence reconciliation algorithm implemented in the `StellaOps.AirGap.Importer` module. The algorithm provides deterministic, lattice-based reconciliation of security evidence from air-gapped bundles.
## Overview
Evidence reconciliation is a 5-step pipeline that transforms raw evidence artifacts (SBOMs, attestations, VEX documents) into a unified, content-addressed evidence graph suitable for policy evaluation and audit trails.
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ Evidence Reconciliation Pipeline │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Step 1: Artifact Indexing │
│ ├── EvidenceDirectoryDiscovery │
│ ├── ArtifactIndex (digest-keyed) │
│ └── Digest normalization (sha256:...) │
│ │
│ Step 2: Evidence Collection │
│ ├── SbomCollector (CycloneDX, SPDX) │
│ ├── AttestationCollector (DSSE) │
│ └── Integration with DsseVerifier │
│ │
│ Step 3: Normalization │
│ ├── JsonNormalizer (stable sorting) │
│ ├── Timestamp stripping │
│ └── URI lowercase normalization │
│ │
│ Step 4: Lattice Rules │
│ ├── SourcePrecedenceLattice │
│ ├── VEX merge with precedence │
│ └── Conflict resolution │
│ │
│ Step 5: Graph Emission │
│ ├── EvidenceGraph construction │
│ ├── Deterministic serialization │
│ └── SHA-256 manifest generation │
│ │
└─────────────────────────────────────────────────────────────────┘
```
## Components
### Step 1: Artifact Indexing
**`ArtifactIndex`** - A digest-keyed index of all artifacts in the evidence bundle.
```csharp
// Key types
public readonly record struct DigestKey(string Algorithm, string Value);
// Normalization
DigestKey.Parse("sha256:abc123...") DigestKey("sha256", "abc123...")
```
**`EvidenceDirectoryDiscovery`** - Discovers evidence files from a directory structure.
Expected structure:
```
evidence/
├── sboms/
│ ├── component-a.cdx.json
│ └── component-b.spdx.json
├── attestations/
│ └── artifact.dsse.json
└── vex/
└── vendor-vex.json
```
### Step 2: Evidence Collection
**Parsers:**
- `CycloneDxParser` - Parses CycloneDX 1.4/1.5/1.6 format
- `SpdxParser` - Parses SPDX 2.3 format
- `DsseAttestationParser` - Parses DSSE envelopes
**Collectors:**
- `SbomCollector` - Orchestrates SBOM parsing and indexing
- `AttestationCollector` - Orchestrates attestation parsing and verification
### Step 3: Normalization
**`SbomNormalizer`** applies format-specific normalization:
| Rule | Description |
|------|-------------|
| Stable JSON sorting | Keys sorted alphabetically (ordinal) |
| Timestamp stripping | Removes `created`, `modified`, `timestamp` fields |
| URI normalization | Lowercases scheme, host, normalizes paths |
| Whitespace normalization | Consistent formatting |
### Step 4: Lattice Rules
**`SourcePrecedenceLattice`** implements a bounded lattice for VEX source authority:
```
Vendor (top)
Maintainer
ThirdParty
Unknown (bottom)
```
**Lattice Properties (verified by property-based tests):**
- **Commutativity**: `Join(a, b) = Join(b, a)`
- **Associativity**: `Join(Join(a, b), c) = Join(a, Join(b, c))`
- **Idempotence**: `Join(a, a) = a`
- **Absorption**: `Join(a, Meet(a, b)) = a`
**Conflict Resolution Order:**
1. Higher precedence source wins
2. More recent timestamp wins (when same precedence)
3. Status priority: NotAffected > Fixed > UnderInvestigation > Affected > Unknown
### Step 5: Graph Emission
**`EvidenceGraph`** - A content-addressed graph of reconciled evidence:
```csharp
public sealed record EvidenceGraph
{
public required string Version { get; init; }
public required string DigestAlgorithm { get; init; }
public required string RootDigest { get; init; }
public required IReadOnlyList<EvidenceNode> Nodes { get; init; }
public required IReadOnlyList<EvidenceEdge> Edges { get; init; }
public required DateTimeOffset GeneratedAt { get; init; }
}
```
**Determinism guarantees:**
- Nodes sorted by digest (ordinal)
- Edges sorted by (source, target, type)
- SHA-256 manifest includes content hash
- Reproducible across runs with same inputs
## Integration
### CLI Usage
```bash
# Verify offline evidence bundle
stellaops verify offline \
--evidence-dir /evidence \
--artifact sha256:def456... \
--policy verify-policy.yaml
```
### API
```csharp
// Reconcile evidence
var reconciler = new EvidenceReconciler(options);
var graph = await reconciler.ReconcileAsync(evidenceDir, cancellationToken);
// Verify determinism
var hash1 = graph.ComputeHash();
var graph2 = await reconciler.ReconcileAsync(evidenceDir, cancellationToken);
var hash2 = graph2.ComputeHash();
Debug.Assert(hash1 == hash2); // Always true
```
## Testing
### Golden-File Tests
Test fixtures in `tests/AirGap/StellaOps.AirGap.Importer.Tests/Reconciliation/Fixtures/`:
- `cyclonedx-sample.json` - CycloneDX 1.5 sample
- `spdx-sample.json` - SPDX 2.3 sample
- `dsse-attestation-sample.json` - DSSE envelope sample
### Property-Based Tests
`SourcePrecedenceLatticePropertyTests` verifies:
- Lattice algebraic properties (commutativity, associativity, idempotence, absorption)
- Ordering properties (antisymmetry, transitivity, reflexivity)
- Bound properties (join is LUB, meet is GLB)
- Merge determinism
## Related Documents
- [Air-Gap Module Architecture](./architecture.md) *(pending)*
- [DSSE Verification](../../adr/dsse-verification.md) *(if exists)*
- [Offline Kit Import Flow](./exporter-cli-coordination.md)

View File

@@ -45,23 +45,23 @@ Trust boundary: **Only the Signer** is allowed to call submission endpoints; enf
- `StellaOps.BuildProvenance@1`
- `StellaOps.SBOMAttestation@1`
- `StellaOps.ScanResults@1`
- `StellaOps.PolicyEvaluation@1`
- `StellaOps.VEXAttestation@1`
- `StellaOps.RiskProfileEvidence@1`
Each predicate embeds subject digests, issuer metadata, policy context, materials, and optional transparency hints. Unsupported predicates return `422 predicate_unsupported`.
> **Golden fixtures:** Deterministic JSON statements for each predicate live in `src/Attestor/StellaOps.Attestor.Types/samples`. They are kept stable by the `StellaOps.Attestor.Types.Tests` project so downstream docs and contracts can rely on them without drifting.
- `StellaOps.PolicyEvaluation@1`
- `StellaOps.VEXAttestation@1`
- `StellaOps.RiskProfileEvidence@1`
### Envelope & signature model
- DSSE envelopes canonicalised (stable JSON ordering) prior to hashing.
- Signature modes: keyless (Fulcio cert chain), keyful (KMS/HSM), hardware (FIDO2/WebAuthn). Multiple signatures allowed.
- Rekor entry stores bundle hash, certificate chain, and optional witness endorsements.
- Archive CAS retains original envelope plus metadata for offline verification.
- Envelope serializer emits **compact** (canonical, minified) and **expanded** (annotated, indented) JSON variants off the same canonical byte stream so hashing stays deterministic while humans get context.
- Payload handling supports **optional compression** (`gzip`, `brotli`) with compression metadata recorded in the expanded view and digesting always performed over the uncompressed bytes.
- Expanded envelopes surface **detached payload references** (URI, digest, media type, size) so large artifacts can live in CAS/object storage while the canonical payload remains embedded for verification.
- Payload previews auto-render JSON or UTF-8 text in the expanded output to simplify triage in air-gapped and offline review flows.
Each predicate embeds subject digests, issuer metadata, policy context, materials, and optional transparency hints. Unsupported predicates return `422 predicate_unsupported`.
> **Golden fixtures:** Deterministic JSON statements for each predicate live in `src/Attestor/StellaOps.Attestor.Types/samples`. They are kept stable by the `StellaOps.Attestor.Types.Tests` project so downstream docs and contracts can rely on them without drifting.
### Envelope & signature model
- DSSE envelopes canonicalised (stable JSON ordering) prior to hashing.
- Signature modes: keyless (Fulcio cert chain), keyful (KMS/HSM), hardware (FIDO2/WebAuthn). Multiple signatures allowed.
- Rekor entry stores bundle hash, certificate chain, and optional witness endorsements.
- Archive CAS retains original envelope plus metadata for offline verification.
- Envelope serializer emits **compact** (canonical, minified) and **expanded** (annotated, indented) JSON variants off the same canonical byte stream so hashing stays deterministic while humans get context.
- Payload handling supports **optional compression** (`gzip`, `brotli`) with compression metadata recorded in the expanded view and digesting always performed over the uncompressed bytes.
- Expanded envelopes surface **detached payload references** (URI, digest, media type, size) so large artifacts can live in CAS/object storage while the canonical payload remains embedded for verification.
- Payload previews auto-render JSON or UTF-8 text in the expanded output to simplify triage in air-gapped and offline review flows.
### Verification pipeline overview
1. Fetch envelope (from request, cache, or storage) and validate DSSE structure.
@@ -70,6 +70,33 @@ Each predicate embeds subject digests, issuer metadata, policy context, material
4. Validate Merkle proof against checkpoint; optionally verify witness endorsement.
5. Return cached verification bundle including policy verdict and timestamps.
### Rekor Inclusion Proof Verification (SPRINT_3000_0001_0001)
The Attestor implements RFC 6962-compliant Merkle inclusion proof verification for Rekor transparency log entries:
**Components:**
- `MerkleProofVerifier` — Verifies Merkle audit paths per RFC 6962 Section 2.1.1
- `CheckpointSignatureVerifier` — Parses and verifies Rekor checkpoint signatures (ECDSA/Ed25519)
- `RekorVerificationOptions` — Configuration for public keys, offline mode, and checkpoint caching
**Verification Flow:**
1. Parse checkpoint body (origin, tree size, root hash)
2. Verify checkpoint signature against Rekor public key
3. Compute leaf hash from canonicalized entry
4. Walk Merkle path from leaf to root using RFC 6962 interior node hashing
5. Compare computed root with checkpoint root hash (constant-time)
**Offline Mode:**
- Bundled checkpoints can be used in air-gapped environments
- `EnableOfflineMode` and `OfflineCheckpointBundlePath` configuration options
- `AllowOfflineWithoutSignature` for fully disconnected scenarios (reduced security)
**Metrics:**
- `attestor.rekor_inclusion_verify_total` — Verification attempts by result
- `attestor.rekor_checkpoint_verify_total` — Checkpoint signature verifications
- `attestor.rekor_offline_verify_total` — Offline mode verifications
- `attestor.rekor_checkpoint_cache_hits/misses` — Checkpoint cache performance
### UI & CLI touchpoints
- Console: Evidence browser, verification report, chain-of-custody graph, issuer/key management, attestation workbench, bulk verification views.
- CLI: `stella attest sign|verify|list|fetch|key` with offline verification and export bundle support.
@@ -127,6 +154,72 @@ Indexes:
---
## 2.1) Content-Addressed Identifier Formats
The ProofChain library (`StellaOps.Attestor.ProofChain`) defines canonical content-addressed identifiers for all proof chain components. These IDs ensure determinism, tamper-evidence, and reproducibility.
### Identifier Types
| ID Type | Format | Source | Example |
|---------|--------|--------|---------|
| **ArtifactID** | `sha256:<64-hex>` | Container manifest or binary hash | `sha256:a1b2c3d4e5f6...` |
| **SBOMEntryID** | `<sbomDigest>:<purl>[@<version>]` | SBOM hash + component PURL | `sha256:91f2ab3c:pkg:npm/lodash@4.17.21` |
| **EvidenceID** | `sha256:<hash>` | Canonical evidence JSON | `sha256:e7f8a9b0c1d2...` |
| **ReasoningID** | `sha256:<hash>` | Canonical reasoning JSON | `sha256:f0e1d2c3b4a5...` |
| **VEXVerdictID** | `sha256:<hash>` | Canonical VEX verdict JSON | `sha256:d4c5b6a7e8f9...` |
| **ProofBundleID** | `sha256:<merkle_root>` | Merkle root of bundle components | `sha256:1a2b3c4d5e6f...` |
| **GraphRevisionID** | `grv_sha256:<hash>` | Merkle root of graph state | `grv_sha256:9f8e7d6c5b4a...` |
### Canonicalization (RFC 8785)
All JSON-based IDs use RFC 8785 (JCS) canonicalization:
- UTF-8 encoding
- Lexicographically sorted keys
- No whitespace (minified)
- No volatile fields (timestamps, random values excluded)
**Implementation:** `StellaOps.Attestor.ProofChain.Json.Rfc8785JsonCanonicalizer`
### Merkle Tree Construction
ProofBundleID and GraphRevisionID use deterministic binary Merkle trees:
- SHA-256 hash function
- Lexicographically sorted leaf inputs
- Standard binary tree construction (pair-wise hashing)
- Odd leaves promoted to next level
**Implementation:** `StellaOps.Attestor.ProofChain.Merkle.DeterministicMerkleTreeBuilder`
### ID Generation Interface
```csharp
// Core interface for ID generation
public interface IContentAddressedIdGenerator
{
EvidenceId GenerateEvidenceId(EvidencePredicate predicate);
ReasoningId GenerateReasoningId(ReasoningPredicate predicate);
VexVerdictId GenerateVexVerdictId(VexPredicate predicate);
ProofBundleId GenerateProofBundleId(SbomEntryId sbom, EvidenceId[] evidence,
ReasoningId reasoning, VexVerdictId verdict);
GraphRevisionId GenerateGraphRevisionId(GraphState state);
}
```
### Predicate Types
The ProofChain library defines DSSE predicates for each attestation type:
| Predicate | Type URI | Purpose |
|-----------|----------|---------|
| `EvidencePredicate` | `stellaops.org/evidence/v1` | Scan evidence (findings, reachability) |
| `ReasoningPredicate` | `stellaops.org/reasoning/v1` | Exploitability reasoning |
| `VexPredicate` | `stellaops.org/vex-verdict/v1` | VEX status determination |
| `ProofSpinePredicate` | `stellaops.org/proof-spine/v1` | Complete proof bundle |
**Reference:** `src/Attestor/__Libraries/StellaOps.Attestor.ProofChain/`
---
## 3) Input contract (from Signer)
**Attestor accepts only** DSSE envelopes that satisfy all of:
@@ -157,53 +250,53 @@ Indexes:
## 4) APIs
### 4.1 Signing
`POST /api/v1/attestations:sign` *(mTLS + OpTok required)*
* **Purpose**: Deterministically wrap StellaOps payloads in DSSE envelopes before Rekor submission. Reuses the submission rate limiter and honours caller tenancy/audience scopes.
* **Body**:
```json
{
"keyId": "signing-key-id",
"payloadType": "application/vnd.in-toto+json",
"payload": "<base64 payload>",
"mode": "keyless|keyful|kms",
"certificateChain": ["-----BEGIN CERTIFICATE-----..."],
"artifact": {
"sha256": "<subject sha256>",
"kind": "sbom|report|vex-export",
"imageDigest": "sha256:...",
"subjectUri": "oci://..."
},
"logPreference": "primary|mirror|both",
"archive": true
}
```
* **Behaviour**:
* Resolve the signing key from `attestor.signing.keys[]` (includes algorithm, provider, and optional KMS version).
* Compute DSSE preauthentication encoding, sign with the resolved provider (default EC, BouncyCastle Ed25519, or FileKMS ES256), and add static + request certificate chains.
* Canonicalise the resulting bundle, derive `bundleSha256`, and mirror the request meta shape used by `/api/v1/rekor/entries`.
* Emit `attestor.sign_total{result,algorithm,provider}` and `attestor.sign_latency_seconds{algorithm,provider}` metrics and append an audit row (`action=sign`).
* **Response 200**:
```json
{
"bundle": { "dsse": { "payloadType": "...", "payload": "...", "signatures": [{ "keyid": "signing-key-id", "sig": "..." }] }, "certificateChain": ["..."], "mode": "kms" },
"meta": { "artifact": { "sha256": "...", "kind": "sbom" }, "bundleSha256": "...", "logPreference": "primary", "archive": true },
"key": { "keyId": "signing-key-id", "algorithm": "ES256", "mode": "kms", "provider": "kms", "signedAt": "2025-11-01T12:34:56Z" }
}
```
* **Errors**: `400 key_not_found`, `400 payload_missing|payload_invalid_base64|artifact_sha_missing`, `400 mode_not_allowed`, `403 client_certificate_required`, `401 invalid_token`, `500 signing_failed`.
### 4.2 Submission
`POST /api/v1/rekor/entries` *(mTLS + OpTok required)*
* **Body**: as above.
### 4.1 Signing
`POST /api/v1/attestations:sign` *(mTLS + OpTok required)*
* **Purpose**: Deterministically wrap StellaOps payloads in DSSE envelopes before Rekor submission. Reuses the submission rate limiter and honours caller tenancy/audience scopes.
* **Body**:
```json
{
"keyId": "signing-key-id",
"payloadType": "application/vnd.in-toto+json",
"payload": "<base64 payload>",
"mode": "keyless|keyful|kms",
"certificateChain": ["-----BEGIN CERTIFICATE-----..."],
"artifact": {
"sha256": "<subject sha256>",
"kind": "sbom|report|vex-export",
"imageDigest": "sha256:...",
"subjectUri": "oci://..."
},
"logPreference": "primary|mirror|both",
"archive": true
}
```
* **Behaviour**:
* Resolve the signing key from `attestor.signing.keys[]` (includes algorithm, provider, and optional KMS version).
* Compute DSSE preauthentication encoding, sign with the resolved provider (default EC, BouncyCastle Ed25519, or FileKMS ES256), and add static + request certificate chains.
* Canonicalise the resulting bundle, derive `bundleSha256`, and mirror the request meta shape used by `/api/v1/rekor/entries`.
* Emit `attestor.sign_total{result,algorithm,provider}` and `attestor.sign_latency_seconds{algorithm,provider}` metrics and append an audit row (`action=sign`).
* **Response 200**:
```json
{
"bundle": { "dsse": { "payloadType": "...", "payload": "...", "signatures": [{ "keyid": "signing-key-id", "sig": "..." }] }, "certificateChain": ["..."], "mode": "kms" },
"meta": { "artifact": { "sha256": "...", "kind": "sbom" }, "bundleSha256": "...", "logPreference": "primary", "archive": true },
"key": { "keyId": "signing-key-id", "algorithm": "ES256", "mode": "kms", "provider": "kms", "signedAt": "2025-11-01T12:34:56Z" }
}
```
* **Errors**: `400 key_not_found`, `400 payload_missing|payload_invalid_base64|artifact_sha_missing`, `400 mode_not_allowed`, `403 client_certificate_required`, `401 invalid_token`, `500 signing_failed`.
### 4.2 Submission
`POST /api/v1/rekor/entries` *(mTLS + OpTok required)*
* **Body**: as above.
* **Behavior**:
* Verify caller (mTLS + OpTok).
@@ -226,16 +319,16 @@ Indexes:
"status": "included"
}
```
* **Errors**: `401 invalid_token`, `403 not_signer|chain_untrusted`, `409 duplicate_bundle` (with existing `uuid`), `502 rekor_unavailable`, `504 proof_timeout`.
### 4.3 Proof retrieval
`GET /api/v1/rekor/entries/{uuid}`
* **Errors**: `401 invalid_token`, `403 not_signer|chain_untrusted`, `409 duplicate_bundle` (with existing `uuid`), `502 rekor_unavailable`, `504 proof_timeout`.
### 4.3 Proof retrieval
`GET /api/v1/rekor/entries/{uuid}`
* Returns `entries` row (refreshes proof from Rekor if stale/missing).
* Accepts `?refresh=true` to force backend query.
### 4.4 Verification (thirdparty or internal)
### 4.4 Verification (thirdparty or internal)
`POST /api/v1/rekor/verify`
@@ -250,28 +343,28 @@ Indexes:
1. **Bundle signature** → cert chain to Fulcio/KMS roots configured.
2. **Inclusion proof** → recompute leaf hash; verify Merkle path against checkpoint root.
3. Optionally verify **checkpoint** against local trust anchors (if Rekor signs checkpoints).
4. Confirm **subject.digest** matches callerprovided hash (when given).
5. Fetch **transparency witness** statement when enabled; cache results and downgrade status to WARN when endorsements are missing or mismatched.
4. Confirm **subject.digest** matches callerprovided hash (when given).
5. Fetch **transparency witness** statement when enabled; cache results and downgrade status to WARN when endorsements are missing or mismatched.
* **Response**:
```json
{ "ok": true, "uuid": "…", "index": 123, "logURL": "…", "checkedAt": "…" }
```
### 4.5 Bulk verification
`POST /api/v1/rekor/verify:bulk` enqueues a verification job containing up to `quotas.bulk.maxItemsPerJob` items. Each item mirrors the single verification payload (uuid | artifactSha256 | subject+envelopeId, optional policyVersion/refreshProof). The handler persists a MongoDB job document (`bulk_jobs` collection) and returns `202 Accepted` with a job descriptor and polling URL.
`GET /api/v1/rekor/verify:bulk/{jobId}` returns progress and per-item results (subject/uuid, status, issues, cached verification report if available). Jobs are tenant- and subject-scoped; only the initiating principal can read their progress.
**Worker path:** `BulkVerificationWorker` claims queued jobs (`status=queued → running`), executes items sequentially through the cached verification service, updates progress counters, and records metrics:
- `attestor.bulk_jobs_total{status}` completed/failed jobs
- `attestor.bulk_job_duration_seconds{status}` job runtime
- `attestor.bulk_items_total{status}` per-item outcomes (`succeeded`, `verification_failed`, `exception`)
The worker honours `bulkVerification.itemDelayMilliseconds` for throttling and reschedules persistence conflicts with optimistic version checks. Results hydrate the verification cache; failed items record the error reason without aborting the overall job.
* **Response**:
```json
{ "ok": true, "uuid": "…", "index": 123, "logURL": "…", "checkedAt": "…" }
```
### 4.5 Bulk verification
`POST /api/v1/rekor/verify:bulk` enqueues a verification job containing up to `quotas.bulk.maxItemsPerJob` items. Each item mirrors the single verification payload (uuid | artifactSha256 | subject+envelopeId, optional policyVersion/refreshProof). The handler persists a MongoDB job document (`bulk_jobs` collection) and returns `202 Accepted` with a job descriptor and polling URL.
`GET /api/v1/rekor/verify:bulk/{jobId}` returns progress and per-item results (subject/uuid, status, issues, cached verification report if available). Jobs are tenant- and subject-scoped; only the initiating principal can read their progress.
**Worker path:** `BulkVerificationWorker` claims queued jobs (`status=queued → running`), executes items sequentially through the cached verification service, updates progress counters, and records metrics:
- `attestor.bulk_jobs_total{status}` completed/failed jobs
- `attestor.bulk_job_duration_seconds{status}` job runtime
- `attestor.bulk_items_total{status}` per-item outcomes (`succeeded`, `verification_failed`, `exception`)
The worker honours `bulkVerification.itemDelayMilliseconds` for throttling and reschedules persistence conflicts with optimistic version checks. Results hydrate the verification cache; failed items record the error reason without aborting the overall job.
---
@@ -303,10 +396,10 @@ The worker honours `bulkVerification.itemDelayMilliseconds` for throttling and r
* `subject.digest.sha256` values must be present and wellformed (hex).
* **No public submission** path. **Never** accept bundles from untrusted clients.
* **Client certificate allowlists**: optional `security.mtls.allowedSubjects` / `allowedThumbprints` tighten peer identity checks beyond CA pinning.
* **Rate limits**: token-bucket per caller derived from `quotas.perCaller` (QPS/burst) returns `429` + `Retry-After` when exceeded.
* **Scope enforcement**: API separates `attestor.write`, `attestor.verify`, and `attestor.read` policies; verification/list endpoints accept read or verify scopes while submission endpoints remain write-only.
* **Request hygiene**: JSON content-type is mandatory (415 returned otherwise); DSSE payloads are capped (default 2MiB), certificate chains limited to six entries, and signatures to six per envelope to mitigate parsing abuse.
* **Redaction**: Attestor never logs secret material; DSSE payloads **should** be public by design (SBOMs/reports). If customers require redaction, enforce policy at Signer (predicate minimization) **before** Attestor.
* **Rate limits**: token-bucket per caller derived from `quotas.perCaller` (QPS/burst) returns `429` + `Retry-After` when exceeded.
* **Scope enforcement**: API separates `attestor.write`, `attestor.verify`, and `attestor.read` policies; verification/list endpoints accept read or verify scopes while submission endpoints remain write-only.
* **Request hygiene**: JSON content-type is mandatory (415 returned otherwise); DSSE payloads are capped (default 2MiB), certificate chains limited to six entries, and signatures to six per envelope to mitigate parsing abuse.
* **Redaction**: Attestor never logs secret material; DSSE payloads **should** be public by design (SBOMs/reports). If customers require redaction, enforce policy at Signer (predicate minimization) **before** Attestor.
---
@@ -329,32 +422,32 @@ The worker honours `bulkVerification.itemDelayMilliseconds` for throttling and r
## 8) Observability & audit
**Metrics** (Prometheus):
* `attestor.sign_total{result,algorithm,provider}`
* `attestor.sign_latency_seconds{algorithm,provider}`
* `attestor.submit_total{result,backend}`
* `attestor.submit_latency_seconds{backend}`
* `attestor.proof_fetch_total{subject,issuer,policy,result,attestor.log.backend}`
* `attestor.verify_total{subject,issuer,policy,result}`
* `attestor.verify_latency_seconds{subject,issuer,policy,result}`
* `attestor.dedupe_hits_total`
* `attestor.errors_total{type}`
SLO guardrails:
* `attestor.verify_latency_seconds` P95 ≤2s per policy.
* `attestor.verify_total{result="failed"}` ≤1% of `attestor.verify_total` over 30min rolling windows.
**Correlation**:
* HTTP callers may supply `X-Correlation-Id`; Attestor will echo the header and push `CorrelationId` into the log scope for cross-service tracing.
**Tracing**:
* Spans: `attestor.sign`, `validate`, `rekor.submit`, `rekor.poll`, `persist`, `archive`, `attestor.verify`, `attestor.verify.refresh_proof`.
**Audit**:
**Metrics** (Prometheus):
* `attestor.sign_total{result,algorithm,provider}`
* `attestor.sign_latency_seconds{algorithm,provider}`
* `attestor.submit_total{result,backend}`
* `attestor.submit_latency_seconds{backend}`
* `attestor.proof_fetch_total{subject,issuer,policy,result,attestor.log.backend}`
* `attestor.verify_total{subject,issuer,policy,result}`
* `attestor.verify_latency_seconds{subject,issuer,policy,result}`
* `attestor.dedupe_hits_total`
* `attestor.errors_total{type}`
SLO guardrails:
* `attestor.verify_latency_seconds` P95 ≤2s per policy.
* `attestor.verify_total{result="failed"}` ≤1% of `attestor.verify_total` over 30min rolling windows.
**Correlation**:
* HTTP callers may supply `X-Correlation-Id`; Attestor will echo the header and push `CorrelationId` into the log scope for cross-service tracing.
**Tracing**:
* Spans: `attestor.sign`, `validate`, `rekor.submit`, `rekor.poll`, `persist`, `archive`, `attestor.verify`, `attestor.verify.refresh_proof`.
**Audit**:
* Immutable `audit` rows (ts, caller, action, hashes, uuid, index, backend, result, latency).
@@ -365,45 +458,45 @@ SLO guardrails:
```yaml
attestor:
listen: "https://0.0.0.0:8444"
security:
mtls:
caBundle: /etc/ssl/signer-ca.pem
requireClientCert: true
authority:
issuer: "https://authority.internal"
jwksUrl: "https://authority.internal/jwks"
requireSenderConstraint: "dpop" # or "mtls"
signerIdentity:
mode: ["keyless","kms"]
fulcioRoots: ["/etc/fulcio/root.pem"]
allowedSANs: ["urn:stellaops:signer"]
kmsKeys: ["kms://cluster-kms/stellaops-signer"]
submissionLimits:
maxPayloadBytes: 2097152
maxCertificateChainEntries: 6
maxSignatures: 6
signing:
preferredProviders: ["kms","bouncycastle.ed25519","default"]
kms:
enabled: true
rootPath: "/var/lib/stellaops/kms"
password: "${ATTESTOR_KMS_PASSWORD}"
keys:
- keyId: "kms-primary"
algorithm: ES256
mode: kms
provider: "kms"
providerKeyId: "kms-primary"
kmsVersionId: "v1"
- keyId: "ed25519-offline"
algorithm: Ed25519
mode: keyful
provider: "bouncycastle.ed25519"
materialFormat: base64
materialPath: "/etc/stellaops/keys/ed25519.key"
certificateChain:
- "-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----"
rekor:
security:
mtls:
caBundle: /etc/ssl/signer-ca.pem
requireClientCert: true
authority:
issuer: "https://authority.internal"
jwksUrl: "https://authority.internal/jwks"
requireSenderConstraint: "dpop" # or "mtls"
signerIdentity:
mode: ["keyless","kms"]
fulcioRoots: ["/etc/fulcio/root.pem"]
allowedSANs: ["urn:stellaops:signer"]
kmsKeys: ["kms://cluster-kms/stellaops-signer"]
submissionLimits:
maxPayloadBytes: 2097152
maxCertificateChainEntries: 6
maxSignatures: 6
signing:
preferredProviders: ["kms","bouncycastle.ed25519","default"]
kms:
enabled: true
rootPath: "/var/lib/stellaops/kms"
password: "${ATTESTOR_KMS_PASSWORD}"
keys:
- keyId: "kms-primary"
algorithm: ES256
mode: kms
provider: "kms"
providerKeyId: "kms-primary"
kmsVersionId: "v1"
- keyId: "ed25519-offline"
algorithm: Ed25519
mode: keyful
provider: "bouncycastle.ed25519"
materialFormat: base64
materialPath: "/etc/stellaops/keys/ed25519.key"
certificateChain:
- "-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----"
rekor:
primary:
url: "https://rekor-v2.internal"
proofTimeoutMs: 15000
@@ -422,20 +515,20 @@ attestor:
objectLock: "governance"
redis:
url: "redis://redis:6379/2"
quotas:
perCaller:
qps: 50
burst: 100
```
**Notes:**
* `signing.preferredProviders` defines the resolution order when multiple providers support the requested algorithm. Omit to fall back to registration order.
* File-backed KMS (`signing.kms`) is required when at least one key uses `mode: kms`; the password should be injected via secret store or environment.
* For keyful providers, supply inline `material` or `materialPath` plus `materialFormat` (`pem` (default), `base64`, or `hex`). KMS keys ignore these fields and require `kmsVersionId`.
* `certificateChain` entries are appended to returned bundles so offline verifiers do not need to dereference external stores.
---
quotas:
perCaller:
qps: 50
burst: 100
```
**Notes:**
* `signing.preferredProviders` defines the resolution order when multiple providers support the requested algorithm. Omit to fall back to registration order.
* File-backed KMS (`signing.kms`) is required when at least one key uses `mode: kms`; the password should be injected via secret store or environment.
* For keyful providers, supply inline `material` or `materialPath` plus `materialFormat` (`pem` (default), `base64`, or `hex`). KMS keys ignore these fields and require `kmsVersionId`.
* `certificateChain` entries are appended to returned bundles so offline verifiers do not need to dereference external stores.
---
## 10) Endtoend sequences
@@ -477,11 +570,11 @@ sequenceDiagram
---
## 11) Failure modes & responses
| Condition | Return | Details | | |
| ------------------------------------- | ----------------------- | --------------------------------------------------------- | -------- | ------------ |
| mTLS/OpTok invalid | `401 invalid_token` | Include `WWW-Authenticate` DPoP challenge when applicable | | |
## 11) Failure modes & responses
| Condition | Return | Details | | |
| ------------------------------------- | ----------------------- | --------------------------------------------------------- | -------- | ------------ |
| mTLS/OpTok invalid | `401 invalid_token` | Include `WWW-Authenticate` DPoP challenge when applicable | | |
| Bundle not signed by trusted identity | `403 chain_untrusted` | DSSE accepted only from Signer identities | | |
| Duplicate bundle | `409 duplicate_bundle` | Return existing `uuid` (idempotent) | | |
| Rekor unreachable/timeout | `502 rekor_unavailable` | Retry with backoff; surface `Retry-After` | | |
@@ -529,14 +622,14 @@ sequenceDiagram
* **Duallog** write (primary + mirror) and **crosslog proof** packaging.
* **Cloud endorsement**: send `{uuid, artifactSha256}` to StellaOps cloud; store returned endorsement id for marketing/chainofcustody.
* **Checkpoint pinning**: periodically pin latest Rekor checkpoints to an external audit store for independent monitoring.
---
## 16) Observability (stub)
- Runbook + dashboard placeholder for offline import: `operations/observability.md`, `operations/dashboards/attestor-observability.json`.
- Metrics to surface: signing latency p95/p99, verification failure rate, transparency log submission lag, key rotation age, queue backlog, attestation bundle size histogram.
- Health endpoints: `/health/liveness`, `/health/readiness`, `/status`; verification probe `/api/attestations/verify` once demo bundle is available (see runbook).
- Alert hints: signing latency > 1s p99, verification failure spikes, tlog submission lag >10s, key rotation age over policy threshold, backlog above configured threshold.
* **Checkpoint pinning**: periodically pin latest Rekor checkpoints to an external audit store for independent monitoring.
---
## 16) Observability (stub)
- Runbook + dashboard placeholder for offline import: `operations/observability.md`, `operations/dashboards/attestor-observability.json`.
- Metrics to surface: signing latency p95/p99, verification failure rate, transparency log submission lag, key rotation age, queue backlog, attestation bundle size histogram.
- Health endpoints: `/health/liveness`, `/health/readiness`, `/status`; verification probe `/api/attestations/verify` once demo bundle is available (see runbook).
- Alert hints: signing latency > 1s p99, verification failure spikes, tlog submission lag >10s, key rotation age over policy threshold, backlog above configured threshold.

View File

@@ -0,0 +1,215 @@
# Proof Spine Assembly Algorithm
> **Sprint:** SPRINT_0501_0004_0001
> **Module:** Attestor / ProofChain
## Overview
The Proof Spine is the cryptographic backbone of StellaOps' proof chain. It aggregates evidence, reasoning, and VEX statements into a single merkle-rooted bundle that can be verified independently.
## Architecture
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ PROOF SPINE STRUCTURE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ SBOMEntryID │ │ EvidenceID[] │ │ ReasoningID │ │ VEXVerdictID │ │
│ │ (leaf 0) │ │ (leaves 1-N) │ │ (leaf N+1) │ │ (leaf N+2) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │ │
│ └─────────────────┴─────────────────┴─────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────┐ │
│ │ MERKLE TREE BUILDER │ │
│ │ - SHA-256 hash function │ │
│ │ - Lexicographic sorting │ │
│ │ - Power-of-2 padding │ │
│ └───────────────┬───────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────┐ │
│ │ ProofBundleID (Root) │ │
│ │ sha256:<64-hex-chars> │ │
│ └───────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## Algorithm Specification
### Input
| Parameter | Type | Description |
|-----------|------|-------------|
| `sbomEntryId` | string | Content-addressed ID of the SBOM entry |
| `evidenceIds` | string[] | Array of evidence statement IDs |
| `reasoningId` | string | ID of the reasoning/policy match statement |
| `vexVerdictId` | string | ID of the VEX verdict statement |
### Output
| Parameter | Type | Description |
|-----------|------|-------------|
| `proofBundleId` | string | Merkle root in format `sha256:<64-hex>` |
### Pseudocode
```
FUNCTION BuildProofBundleMerkle(sbomEntryId, evidenceIds[], reasoningId, vexVerdictId):
// Step 1: Prepare leaves in deterministic order
leaves = []
leaves.append(SHA256(UTF8.GetBytes(sbomEntryId)))
// Step 2: Sort evidence IDs lexicographically
sortedEvidenceIds = evidenceIds.Sort(StringComparer.Ordinal)
FOR EACH evidenceId IN sortedEvidenceIds:
leaves.append(SHA256(UTF8.GetBytes(evidenceId)))
leaves.append(SHA256(UTF8.GetBytes(reasoningId)))
leaves.append(SHA256(UTF8.GetBytes(vexVerdictId)))
// Step 3: Pad to power of 2 (duplicate last leaf)
WHILE NOT IsPowerOfTwo(leaves.Length):
leaves.append(leaves[leaves.Length - 1])
// Step 4: Build tree bottom-up
currentLevel = leaves
WHILE currentLevel.Length > 1:
nextLevel = []
FOR i = 0 TO currentLevel.Length STEP 2:
left = currentLevel[i]
right = currentLevel[i + 1]
parent = SHA256(left || right) // Concatenate then hash
nextLevel.append(parent)
currentLevel = nextLevel
// Step 5: Return root as formatted ID
RETURN "sha256:" + HexEncode(currentLevel[0])
```
## Determinism Invariants
| Invariant | Rule | Rationale |
|-----------|------|-----------|
| Evidence Ordering | Lexicographic (byte comparison) | Reproducible across platforms |
| Hash Function | SHA-256 only | No algorithm negotiation |
| Padding | Duplicate last leaf | Not zeros, preserves tree structure |
| Concatenation | Left `\|\|` Right | Consistent ordering |
| String Encoding | UTF-8 | Cross-platform compatibility |
| ID Format | `sha256:<lowercase-hex>` | Canonical representation |
## Example
### Input
```json
{
"sbomEntryId": "sha256:abc123...",
"evidenceIds": [
"sha256:evidence-cve-2024-0001...",
"sha256:evidence-reachability...",
"sha256:evidence-sbom-component..."
],
"reasoningId": "sha256:reasoning-policy...",
"vexVerdictId": "sha256:vex-not-affected..."
}
```
### Processing
1. **Leaf 0**: `SHA256("sha256:abc123...")` → SBOM
2. **Leaf 1**: `SHA256("sha256:evidence-cve-2024-0001...")` → Evidence (sorted first)
3. **Leaf 2**: `SHA256("sha256:evidence-reachability...")` → Evidence
4. **Leaf 3**: `SHA256("sha256:evidence-sbom-component...")` → Evidence
5. **Leaf 4**: `SHA256("sha256:reasoning-policy...")` → Reasoning
6. **Leaf 5**: `SHA256("sha256:vex-not-affected...")` → VEX
7. **Padding**: Duplicate leaf 5 to get 8 leaves (power of 2)
### Tree Structure
```
ROOT
/ \
H1 H2
/ \ / \
H3 H4 H5 H6
/ \ / \ / \ / \
L0 L1 L2 L3 L4 L5 L5 L5 (padded)
```
### Output
```
sha256:7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069
```
## Cross-Platform Verification
### Test Vector
For cross-platform compatibility testing, use this known test vector:
**Input:**
```json
{
"sbomEntryId": "sha256:0000000000000000000000000000000000000000000000000000000000000001",
"evidenceIds": [
"sha256:0000000000000000000000000000000000000000000000000000000000000002",
"sha256:0000000000000000000000000000000000000000000000000000000000000003"
],
"reasoningId": "sha256:0000000000000000000000000000000000000000000000000000000000000004",
"vexVerdictId": "sha256:0000000000000000000000000000000000000000000000000000000000000005"
}
```
All implementations (C#, Go, Rust, TypeScript) must produce the same root hash.
## Verification
To verify a proof bundle:
1. Obtain all constituent statements (SBOM, Evidence, Reasoning, VEX)
2. Extract their content-addressed IDs
3. Re-compute the merkle root using the algorithm above
4. Compare with the claimed `proofBundleId`
If the roots match, the bundle is valid and all statements are bound to this proof.
## API
### C# Interface
```csharp
public interface IProofSpineAssembler
{
/// <summary>
/// Assembles a proof spine from its constituent statements.
/// </summary>
ProofSpineResult Assemble(ProofSpineInput input);
}
public record ProofSpineInput
{
public required string SbomEntryId { get; init; }
public required IReadOnlyList<string> EvidenceIds { get; init; }
public required string ReasoningId { get; init; }
public required string VexVerdictId { get; init; }
}
public record ProofSpineResult
{
public required string ProofBundleId { get; init; }
public required byte[] MerkleRoot { get; init; }
public required IReadOnlyList<byte[]> LeafHashes { get; init; }
}
```
## Related Documentation
- [Proof and Evidence Chain Technical Reference](../product-advisories/14-Dec-2025%20-%20Proof%20and%20Evidence%20Chain%20Technical%20Reference.md) - §2.4, §4.2, §9
- [Content-Addressed IDs](./content-addressed-ids.md)
- [DSSE Predicates](./dsse-predicates.md)

View File

@@ -0,0 +1,159 @@
# TTFS (Time to First Signal) Alert Rules
# Reference: SPRINT_0341_0001_0001 Task T10
# These alerts monitor SLOs for the TTFS experience
groups:
- name: ttfs-slo
interval: 30s
rules:
# Primary SLO: P95 latency must be under 5 seconds
- alert: TtfsP95High
expr: |
histogram_quantile(0.95, sum(rate(ttfs_latency_seconds_bucket[5m])) by (le, surface)) > 5
for: 5m
labels:
severity: page
component: ttfs
slo: ttfs-latency
annotations:
summary: "TTFS P95 latency exceeds 5s for {{ $labels.surface }}"
description: "Time to First Signal P95 is {{ $value | humanizeDuration }} for surface {{ $labels.surface }}. This breaches the TTFS SLO."
runbook: "docs/runbooks/ttfs-latency-high.md"
dashboard: "https://grafana.stellaops.local/d/ttfs-overview"
# Cache performance: Hit rate should be above 70%
- alert: TtfsCacheHitRateLow
expr: |
sum(rate(ttfs_cache_hit_total[5m])) / sum(rate(ttfs_signal_total[5m])) < 0.7
for: 10m
labels:
severity: warning
component: ttfs
annotations:
summary: "TTFS cache hit rate below 70%"
description: "Cache hit rate is {{ $value | humanizePercentage }}. Low cache hit rates increase TTFS latency."
runbook: "docs/runbooks/ttfs-cache-performance.md"
# Error rate: Should be under 1%
- alert: TtfsErrorRateHigh
expr: |
sum(rate(ttfs_error_total[5m])) / sum(rate(ttfs_signal_total[5m])) > 0.01
for: 5m
labels:
severity: warning
component: ttfs
annotations:
summary: "TTFS error rate exceeds 1%"
description: "Error rate is {{ $value | humanizePercentage }}. Check logs for FirstSignalService errors."
runbook: "docs/runbooks/ttfs-error-investigation.md"
# SLO breach counter: Too many breaches in a short window
- alert: TtfsSloBreach
expr: |
sum(increase(ttfs_slo_breach_total[5m])) > 10
for: 1m
labels:
severity: page
component: ttfs
slo: ttfs-breach-rate
annotations:
summary: "TTFS SLO breach rate high"
description: "{{ $value }} SLO breaches in last 5 minutes. Immediate investigation required."
runbook: "docs/runbooks/ttfs-slo-breach.md"
# Endpoint latency: HTTP endpoint should respond within 500ms
- alert: FirstSignalEndpointLatencyHigh
expr: |
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{route=~"/api/v1/orchestrator/runs/.*/first-signal"}[5m])) by (le)) > 0.5
for: 5m
labels:
severity: warning
component: ttfs
annotations:
summary: "First signal endpoint P95 latency > 500ms"
description: "The /first-signal API endpoint P95 is {{ $value | humanizeDuration }}. This is the API-level latency only."
runbook: "docs/runbooks/first-signal-api-slow.md"
- name: ttfs-availability
interval: 1m
rules:
# Availability: First signal endpoint should be available
- alert: FirstSignalEndpointDown
expr: |
up{job="orchestrator"} == 0
for: 2m
labels:
severity: critical
component: ttfs
annotations:
summary: "Orchestrator (First Signal provider) is down"
description: "The Orchestrator service is not responding. First Signal functionality is unavailable."
runbook: "docs/runbooks/orchestrator-down.md"
# No signals being generated
- alert: TtfsNoSignals
expr: |
sum(rate(ttfs_signal_total[10m])) == 0
for: 15m
labels:
severity: warning
component: ttfs
annotations:
summary: "No TTFS signals generated in 15 minutes"
description: "No First Signal events have been recorded. This could indicate no active runs or a metric collection issue."
- name: ttfs-ux
interval: 1m
rules:
# UX: High bounce rate indicates poor experience
- alert: TtfsBounceRateHigh
expr: |
sum(rate(ttfs_bounce_total[5m])) / sum(rate(ttfs_page_view_total[5m])) > 0.5
for: 30m
labels:
severity: warning
component: ttfs
area: ux
annotations:
summary: "TTFS page bounce rate exceeds 50%"
description: "More than 50% of users are leaving the run page within 10 seconds. This may indicate poor First Signal experience."
# UX: Long open-to-action time
- alert: TtfsOpenToActionSlow
expr: |
histogram_quantile(0.75, sum(rate(ttfs_open_to_action_seconds_bucket[15m])) by (le)) > 30
for: 1h
labels:
severity: info
component: ttfs
area: ux
annotations:
summary: "75% of users take >30s to first action"
description: "Users are taking a long time to act on First Signal. Consider UX improvements."
- name: ttfs-failure-signatures
interval: 30s
rules:
# New failure pattern emerging
- alert: TtfsNewFailurePatternHigh
expr: |
sum(rate(ttfs_failure_signature_new_total[5m])) > 1
for: 10m
labels:
severity: warning
component: ttfs
annotations:
summary: "High rate of new failure signatures"
description: "New failure patterns are being detected at {{ $value }}/s. This may indicate a new class of errors."
# Failure signature confidence upgrades
- alert: TtfsFailureSignatureConfidenceUpgrade
expr: |
sum(increase(ttfs_failure_signature_confidence_upgrade_total[1h])) > 5
for: 5m
labels:
severity: info
component: ttfs
annotations:
summary: "Multiple failure signatures upgraded to high confidence"
description: "{{ $value }} failure signatures have been upgraded to high confidence in the last hour."

View File

@@ -0,0 +1,552 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"description": "Time to First Signal (TTFS) observability dashboard for StellaOps",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [],
"liveNow": false,
"panels": [
{
"title": "TTFS P50/P95/P99 by Surface",
"type": "timeseries",
"gridPos": { "x": 0, "y": 0, "w": 12, "h": 8 },
"id": 1,
"datasource": {
"type": "prometheus",
"uid": "${datasource}"
},
"targets": [
{
"expr": "histogram_quantile(0.50, sum(rate(ttfs_latency_seconds_bucket[5m])) by (le, surface))",
"legendFormat": "P50 - {{surface}}",
"refId": "A"
},
{
"expr": "histogram_quantile(0.95, sum(rate(ttfs_latency_seconds_bucket[5m])) by (le, surface))",
"legendFormat": "P95 - {{surface}}",
"refId": "B"
},
{
"expr": "histogram_quantile(0.99, sum(rate(ttfs_latency_seconds_bucket[5m])) by (le, surface))",
"legendFormat": "P99 - {{surface}}",
"refId": "C"
}
],
"fieldConfig": {
"defaults": {
"unit": "s",
"thresholds": {
"mode": "absolute",
"steps": [
{ "value": null, "color": "green" },
{ "value": 2, "color": "yellow" },
{ "value": 5, "color": "red" }
]
},
"custom": {
"lineWidth": 1,
"fillOpacity": 10,
"showPoints": "auto"
}
},
"overrides": []
},
"options": {
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["mean", "max", "lastNotNull"]
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
}
},
{
"title": "Cache Hit Rate",
"type": "stat",
"gridPos": { "x": 12, "y": 0, "w": 6, "h": 4 },
"id": 2,
"datasource": {
"type": "prometheus",
"uid": "${datasource}"
},
"targets": [
{
"expr": "sum(rate(ttfs_cache_hit_total[5m])) / sum(rate(ttfs_signal_total[5m]))",
"legendFormat": "Hit Rate",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"unit": "percentunit",
"thresholds": {
"mode": "absolute",
"steps": [
{ "value": null, "color": "red" },
{ "value": 0.7, "color": "yellow" },
{ "value": 0.9, "color": "green" }
]
},
"mappings": []
},
"overrides": []
},
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"orientation": "auto",
"textMode": "auto",
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto"
}
},
{
"title": "SLO Breaches (P95 > 5s)",
"type": "stat",
"gridPos": { "x": 18, "y": 0, "w": 6, "h": 4 },
"id": 3,
"datasource": {
"type": "prometheus",
"uid": "${datasource}"
},
"targets": [
{
"expr": "sum(increase(ttfs_slo_breach_total[1h]))",
"legendFormat": "Breaches (1h)",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{ "value": null, "color": "green" },
{ "value": 1, "color": "yellow" },
{ "value": 10, "color": "red" }
]
},
"mappings": []
},
"overrides": []
},
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"orientation": "auto",
"textMode": "auto",
"colorMode": "background",
"graphMode": "none",
"justifyMode": "auto"
}
},
{
"title": "Signal Source Distribution",
"type": "piechart",
"gridPos": { "x": 12, "y": 4, "w": 6, "h": 4 },
"id": 4,
"datasource": {
"type": "prometheus",
"uid": "${datasource}"
},
"targets": [
{
"expr": "sum by (signal_source) (rate(ttfs_signal_total[1h]))",
"legendFormat": "{{signal_source}}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"mappings": []
},
"overrides": []
},
"options": {
"legend": {
"displayMode": "list",
"placement": "right"
},
"pieType": "pie",
"tooltip": {
"mode": "single"
}
}
},
{
"title": "Failure Signature Matches",
"type": "stat",
"gridPos": { "x": 18, "y": 4, "w": 6, "h": 4 },
"id": 5,
"datasource": {
"type": "prometheus",
"uid": "${datasource}"
},
"targets": [
{
"expr": "sum(rate(ttfs_failure_signature_match_total[5m]))",
"legendFormat": "Matches/s",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"unit": "reqps",
"thresholds": {
"mode": "absolute",
"steps": [
{ "value": null, "color": "blue" }
]
}
},
"overrides": []
}
},
{
"title": "Signals by Kind",
"type": "timeseries",
"gridPos": { "x": 0, "y": 8, "w": 12, "h": 6 },
"id": 6,
"datasource": {
"type": "prometheus",
"uid": "${datasource}"
},
"targets": [
{
"expr": "sum by (kind) (rate(ttfs_signal_total[5m]))",
"legendFormat": "{{kind}}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"unit": "reqps",
"custom": {
"lineWidth": 1,
"fillOpacity": 20,
"stacking": {
"mode": "normal",
"group": "A"
}
}
},
"overrides": []
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom"
}
}
},
{
"title": "Error Rate",
"type": "timeseries",
"gridPos": { "x": 12, "y": 8, "w": 12, "h": 6 },
"id": 7,
"datasource": {
"type": "prometheus",
"uid": "${datasource}"
},
"targets": [
{
"expr": "sum(rate(ttfs_error_total[5m])) / sum(rate(ttfs_signal_total[5m]))",
"legendFormat": "Error Rate",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"unit": "percentunit",
"max": 0.1,
"thresholds": {
"mode": "absolute",
"steps": [
{ "value": null, "color": "green" },
{ "value": 0.01, "color": "yellow" },
{ "value": 0.05, "color": "red" }
]
},
"custom": {
"lineWidth": 2,
"fillOpacity": 10
}
},
"overrides": []
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom"
}
}
},
{
"title": "TTFS Latency Heatmap",
"type": "heatmap",
"gridPos": { "x": 0, "y": 14, "w": 12, "h": 8 },
"id": 8,
"datasource": {
"type": "prometheus",
"uid": "${datasource}"
},
"targets": [
{
"expr": "sum(increase(ttfs_latency_seconds_bucket[1m])) by (le)",
"legendFormat": "{{le}}",
"format": "heatmap",
"refId": "A"
}
],
"options": {
"calculate": false,
"yAxis": {
"axisPlacement": "left",
"unit": "s"
},
"color": {
"scheme": "Spectral",
"mode": "scheme"
},
"cellGap": 1
}
},
{
"title": "First Signal Endpoint Latency",
"type": "timeseries",
"gridPos": { "x": 12, "y": 14, "w": 12, "h": 8 },
"id": 9,
"datasource": {
"type": "prometheus",
"uid": "${datasource}"
},
"targets": [
{
"expr": "histogram_quantile(0.50, sum(rate(http_request_duration_seconds_bucket{route=~\"/api/v1/orchestrator/runs/.*/first-signal\"}[5m])) by (le))",
"legendFormat": "P50",
"refId": "A"
},
{
"expr": "histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{route=~\"/api/v1/orchestrator/runs/.*/first-signal\"}[5m])) by (le))",
"legendFormat": "P95",
"refId": "B"
},
{
"expr": "histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{route=~\"/api/v1/orchestrator/runs/.*/first-signal\"}[5m])) by (le))",
"legendFormat": "P99",
"refId": "C"
}
],
"fieldConfig": {
"defaults": {
"unit": "s",
"thresholds": {
"mode": "absolute",
"steps": [
{ "value": null, "color": "green" },
{ "value": 0.3, "color": "yellow" },
{ "value": 0.5, "color": "red" }
]
},
"custom": {
"lineWidth": 1,
"fillOpacity": 10
}
},
"overrides": []
}
},
{
"title": "Open→Action Time Distribution",
"type": "histogram",
"gridPos": { "x": 0, "y": 22, "w": 8, "h": 6 },
"id": 10,
"datasource": {
"type": "prometheus",
"uid": "${datasource}"
},
"targets": [
{
"expr": "sum(increase(ttfs_open_to_action_seconds_bucket[5m])) by (le)",
"legendFormat": "{{le}}",
"format": "heatmap",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"unit": "s"
}
}
},
{
"title": "Bounce Rate (< 10s)",
"type": "stat",
"gridPos": { "x": 8, "y": 22, "w": 4, "h": 6 },
"id": 11,
"datasource": {
"type": "prometheus",
"uid": "${datasource}"
},
"targets": [
{
"expr": "sum(rate(ttfs_bounce_total[5m])) / sum(rate(ttfs_page_view_total[5m]))",
"legendFormat": "Bounce Rate",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"unit": "percentunit",
"thresholds": {
"mode": "absolute",
"steps": [
{ "value": null, "color": "green" },
{ "value": 0.3, "color": "yellow" },
{ "value": 0.5, "color": "red" }
]
}
}
}
},
{
"title": "Top Failure Signatures",
"type": "table",
"gridPos": { "x": 12, "y": 22, "w": 12, "h": 6 },
"id": 12,
"datasource": {
"type": "prometheus",
"uid": "${datasource}"
},
"targets": [
{
"expr": "topk(10, sum by (error_token, error_code) (ttfs_failure_signature_hit_total))",
"legendFormat": "{{error_token}} ({{error_code}})",
"format": "table",
"instant": true,
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto"
}
},
"overrides": [
{
"matcher": { "id": "byName", "options": "Value" },
"properties": [
{ "id": "displayName", "value": "Hit Count" }
]
}
]
},
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true
},
"renameByName": {
"error_token": "Token",
"error_code": "Code"
}
}
}
]
}
],
"refresh": "30s",
"schemaVersion": 38,
"style": "dark",
"tags": ["ttfs", "ux", "slo", "stellaops"],
"templating": {
"list": [
{
"current": {
"selected": false,
"text": "Prometheus",
"value": "prometheus"
},
"hide": 0,
"includeAll": false,
"label": "Datasource",
"multi": false,
"name": "datasource",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
},
{
"allValue": ".*",
"current": {
"selected": true,
"text": "All",
"value": "$__all"
},
"datasource": {
"type": "prometheus",
"uid": "${datasource}"
},
"definition": "label_values(ttfs_latency_seconds_bucket, surface)",
"hide": 0,
"includeAll": true,
"label": "Surface",
"multi": true,
"name": "surface",
"options": [],
"query": {
"query": "label_values(ttfs_latency_seconds_bucket, surface)",
"refId": "PrometheusVariableQueryEditor-VariableQuery"
},
"refresh": 2,
"regex": "",
"skipUrlSync": false,
"sort": 1,
"type": "query"
}
]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "utc",
"title": "TTFS - Time to First Signal",
"uid": "ttfs-overview",
"version": 1,
"weekStart": ""
}

View File

@@ -361,7 +361,61 @@ export const TTFS_FIXTURES = {
};
```
## 12) References
## 12) Observability
### 12.1 Grafana Dashboard
The TTFS observability dashboard provides real-time visibility into signal latency, cache performance, and SLO compliance.
- **Dashboard file**: `docs/modules/telemetry/operations/dashboards/ttfs-observability.json`
- **UID**: `ttfs-overview`
**Key panels:**
- TTFS P50/P95/P99 by Surface (timeseries)
- Cache Hit Rate (stat)
- SLO Breaches (stat with threshold coloring)
- Signal Source Distribution (piechart)
- Signals by Kind (stacked timeseries)
- Error Rate (timeseries)
- TTFS Latency Heatmap
- Top Failure Signatures (table)
### 12.2 Alert Rules
TTFS alerts are defined in `docs/modules/telemetry/operations/alerts/ttfs-alerts.yaml`.
**Critical alerts:**
| Alert | Threshold | For |
|-------|-----------|-----|
| `TtfsP95High` | P95 > 5s | 5m |
| `TtfsSloBreach` | >10 breaches in 5m | 1m |
| `FirstSignalEndpointDown` | Orchestrator unavailable | 2m |
**Warning alerts:**
| Alert | Threshold | For |
|-------|-----------|-----|
| `TtfsCacheHitRateLow` | <70% | 10m |
| `TtfsErrorRateHigh` | >1% | 5m |
| `FirstSignalEndpointLatencyHigh` | P95 > 500ms | 5m |
### 12.3 Load Testing
Load tests validate TTFS performance under realistic conditions.
- **Test file**: `tests/load/ttfs-load-test.js`
- **Framework**: k6
**Scenarios:**
- Sustained: 50 RPS for 5 minutes
- Spike: Ramp to 200 RPS
- Soak: 25 RPS for 15 minutes
**Thresholds:**
- Cache-hit P95 ≤ 250ms
- Cold-path P95 ≤ 500ms
- Error rate < 0.1%
## 13) References
- Advisory: `docs/product-advisories/14-Dec-2025 - UX and Time-to-Evidence Technical Reference.md`
- Sprint 1 (Foundation): `docs/implplan/SPRINT_0338_0001_0001_ttfs_foundation.md`
@@ -371,3 +425,6 @@ export const TTFS_FIXTURES = {
- TTE Architecture: `docs/modules/telemetry/architecture.md`
- Telemetry Schema: `docs/schemas/ttfs-event.schema.json`
- Database Schema: `docs/db/schemas/ttfs.sql`
- Grafana Dashboard: `docs/modules/telemetry/operations/dashboards/ttfs-observability.json`
- Alert Rules: `docs/modules/telemetry/operations/alerts/ttfs-alerts.yaml`
- Load Tests: `tests/load/ttfs-load-test.js`

View File

@@ -0,0 +1,291 @@
# Score Policy YAML Format
**Sprint:** SPRINT_3402_0001_0001
**Status:** Complete
## Overview
StellaOps uses a YAML-based configuration for deterministic vulnerability scoring. The score policy defines how different factors contribute to the final vulnerability score, ensuring reproducible and auditable results.
## Schema Version
Current version: `score.v1`
## File Location
By default, score policies are loaded from:
- `etc/score-policy.yaml` (production)
- `etc/score-policy.yaml.sample` (reference template)
Override via environment variable: `STELLAOPS_SCORE_POLICY_PATH`
## Basic Structure
```yaml
# Required fields
policyVersion: score.v1
policyId: unique-policy-identifier
# Optional metadata
policyName: "My Organization's Scoring Policy"
description: "Custom scoring weights for our security posture"
# Weight distribution (must sum to 10000 basis points = 100%)
weightsBps:
baseSeverity: 2500 # 25% - CVSS base score contribution
reachability: 2500 # 25% - Code reachability analysis
evidence: 2500 # 25% - KEV, EPSS, exploit evidence
provenance: 2500 # 25% - Supply chain trust signals
```
## Weight Configuration
Weights are specified in **basis points (bps)** where 10000 bps = 100%. This avoids floating-point precision issues and ensures weights always sum to exactly 100%.
### Example: Reachability-Heavy Profile
```yaml
policyVersion: score.v1
policyId: reachability-focused
weightsBps:
baseSeverity: 2000 # 20%
reachability: 4000 # 40% - Heavy emphasis on reachability
evidence: 2000 # 20%
provenance: 2000 # 20%
```
### Example: Evidence-Heavy Profile
```yaml
policyVersion: score.v1
policyId: evidence-focused
weightsBps:
baseSeverity: 2000 # 20%
reachability: 2000 # 20%
evidence: 4000 # 40% - Heavy emphasis on KEV/EPSS
provenance: 2000 # 20%
```
## Reachability Configuration
Fine-tune how reachability analysis affects scores:
```yaml
reachabilityConfig:
reachableMultiplier: 1.5 # Boost for reachable code paths
unreachableMultiplier: 0.3 # Reduction for unreachable code
unknownMultiplier: 1.0 # Default when analysis unavailable
```
### Multiplier Bounds
- Minimum: 0.0
- Maximum: 2.0 (configurable)
- Default for unknown: 1.0 (no adjustment)
## Evidence Configuration
Configure how exploit evidence affects scoring:
```yaml
evidenceConfig:
kevWeight: 1.5 # Boost for KEV-listed vulnerabilities
epssThreshold: 0.5 # EPSS score threshold for high-risk
epssWeight: 1.2 # Weight multiplier for high EPSS
```
### KEV Integration
Known Exploited Vulnerabilities (KEV) from CISA are automatically boosted:
- `kevWeight: 1.5` means 50% score increase for KEV-listed CVEs
- Setting `kevWeight: 1.0` disables KEV boost
### EPSS Integration
Exploit Prediction Scoring System (EPSS) provides probability-based risk:
- `epssThreshold`: Minimum EPSS for applying the weight
- `epssWeight`: Multiplier applied when EPSS exceeds threshold
## Provenance Configuration
Configure how supply chain trust signals affect scoring:
```yaml
provenanceConfig:
signedBonus: 0.1 # 10% reduction for signed artifacts
rekorVerifiedBonus: 0.2 # 20% reduction for Rekor-verified
unsignedPenalty: -0.1 # 10% increase for unsigned artifacts
```
### Trust Signals
| Signal | Effect | Use Case |
|--------|--------|----------|
| `signedBonus` | Score reduction | Artifact has valid signature |
| `rekorVerifiedBonus` | Score reduction | Signature in transparency log |
| `unsignedPenalty` | Score increase | No signature present |
## Score Overrides
Override scoring for specific CVEs or patterns:
```yaml
overrides:
# Exact CVE match
- id: log4shell-critical
match:
cvePattern: "CVE-2021-44228"
action:
setScore: 10.0
reason: "Known critical RCE in production"
# Pattern match
- id: log4j-family
match:
cvePattern: "CVE-2021-442.*"
action:
multiplyScore: 1.2
reason: "Log4j family vulnerabilities"
# Severity-based
- id: low-severity-suppress
match:
severityEquals: "LOW"
action:
multiplyScore: 0.5
reason: "Reduce noise from low-severity findings"
# Combined conditions
- id: unreachable-medium
match:
severityEquals: "MEDIUM"
reachabilityEquals: "UNREACHABLE"
action:
multiplyScore: 0.3
reason: "Medium + unreachable = low priority"
```
### Override Actions
| Action | Description | Example |
|--------|-------------|---------|
| `setScore` | Force specific score | `setScore: 10.0` |
| `multiplyScore` | Apply multiplier | `multiplyScore: 0.5` |
| `addScore` | Add/subtract value | `addScore: -2.0` |
### Match Conditions
| Condition | Description | Example |
|-----------|-------------|---------|
| `cvePattern` | Regex match on CVE ID | `"CVE-2021-.*"` |
| `severityEquals` | Exact severity match | `"HIGH"`, `"CRITICAL"` |
| `reachabilityEquals` | Reachability state | `"REACHABLE"`, `"UNREACHABLE"`, `"UNKNOWN"` |
| `packagePattern` | Package name regex | `"log4j.*"` |
## Complete Example
```yaml
policyVersion: score.v1
policyId: production-v2024.12
policyName: "Production Security Policy"
description: |
Balanced scoring policy with emphasis on exploitability
and reachability for production workloads.
weightsBps:
baseSeverity: 2000
reachability: 3000
evidence: 3000
provenance: 2000
reachabilityConfig:
reachableMultiplier: 1.5
unreachableMultiplier: 0.4
unknownMultiplier: 1.0
evidenceConfig:
kevWeight: 1.5
epssThreshold: 0.3
epssWeight: 1.3
provenanceConfig:
signedBonus: 0.1
rekorVerifiedBonus: 0.15
unsignedPenalty: -0.05
overrides:
- id: critical-rce
match:
cvePattern: "CVE-2021-44228|CVE-2022-22965"
action:
setScore: 10.0
reason: "Known critical RCE vulnerabilities"
- id: unreachable-low
match:
severityEquals: "LOW"
reachabilityEquals: "UNREACHABLE"
action:
multiplyScore: 0.2
reason: "Minimal risk: low severity + unreachable"
```
## Validation
Policies are validated against JSON Schema on load:
1. **Schema validation**: Structure and types
2. **Weight sum check**: `weightsBps` must sum to 10000
3. **Range checks**: Multipliers within bounds
4. **Override validation**: Valid patterns and actions
### Programmatic Validation
```csharp
var validator = new ScorePolicyValidator();
var result = validator.Validate(policy);
if (!result.IsValid)
{
foreach (var error in result.Errors)
{
Console.WriteLine(error);
}
}
```
## Determinism
For reproducible scoring:
1. **Policy Digest**: Each policy has a content-addressed digest
2. **Replay Manifest**: Digest is recorded in scan manifests
3. **Audit Trail**: Policy version tracked with every scan
### Digest Format
```
sha256:abc123def456...
```
The digest is computed from canonical JSON serialization of the policy, ensuring identical policies always produce identical digests.
## Migration
### From Hardcoded Weights
1. Export current weights to YAML format
2. Validate with `stellaops policy validate score.yaml`
3. Deploy to `etc/score-policy.yaml`
4. Restart services to load new policy
### Version Upgrades
Future schema versions (e.g., `score.v2`) will include migration guides and backward compatibility notes.
## Related Documentation
- [Architecture Overview](../07_HIGH_LEVEL_ARCHITECTURE.md)
- [Determinism Technical Reference](../product-advisories/14-Dec-2025%20-%20Determinism%20and%20Reproducibility%20Technical%20Reference.md)
- [Policy Engine Architecture](../modules/policy/architecture.md)

View File

@@ -0,0 +1,192 @@
# Scoring Profiles
**Sprint:** SPRINT_3407_0001_0001
**Task:** PROF-3407-014
**Last Updated:** 2025-12-16
## Overview
StellaOps supports multiple scoring profiles to accommodate different customer needs, from simple transparent scoring to advanced entropy-based analysis. Scoring profiles determine how vulnerability findings are evaluated and scored.
## Available Profiles
### Simple Profile
The Simple profile uses a transparent 4-factor basis-points weighted formula:
```
riskScore = (wB × B + wR × R + wE × E + wP × P) / 10000
```
Where:
- **B** (Base Severity): CVSS score × 10 (0-100 range)
- **R** (Reachability): Hop-based score with gate multipliers
- **E** (Evidence): Evidence points × freshness multiplier
- **P** (Provenance): Level-based score (unsigned to reproducible)
- **wB, wR, wE, wP**: Weight basis points (must sum to 10000)
**Default weights:**
| Factor | Weight (bps) | Percentage |
|--------|-------------|------------|
| Base Severity | 1000 | 10% |
| Reachability | 4500 | 45% |
| Evidence | 3000 | 30% |
| Provenance | 1500 | 15% |
**Use cases:**
- Organizations requiring audit-friendly, explainable scoring
- Compliance scenarios requiring transparent formulas
- Initial deployments before advanced analysis is available
### Advanced Profile (Default)
The Advanced profile extends Simple with:
- **CVSS version adjustment**: Scores weighted by CVSS version (4.0 > 3.1 > 3.0 > 2.0)
- **KEV boost**: +20 points for Known Exploited Vulnerabilities
- **Uncertainty penalty**: Deductions for missing data (reachability, evidence, provenance, CVSS version)
- **Semantic category multipliers**: Entry points and API endpoints scored higher than internal services
- **Multi-evidence overlap bonus**: 10% bonus per additional evidence type
- **Advanced score passthrough**: Uses pre-computed advanced scores when available
**Use cases:**
- Production deployments with full telemetry
- Organizations with mature security programs
- Scenarios requiring nuanced risk differentiation
### Custom Profile (Enterprise)
The Custom profile allows fully user-defined scoring via Rego policies. Requires:
- Valid Rego policy path
- Policy Engine license with Custom Scoring feature
## Configuration
### Score Policy YAML
Add the `scoringProfile` field to your score policy:
```yaml
policyVersion: score.v1
scoringProfile: simple # Options: simple, advanced, custom
weightsBps:
baseSeverity: 1000
reachability: 4500
evidence: 3000
provenance: 1500
# ... rest of policy configuration
```
### Tenant Override
Tenants can override the default profile via the Scoring Profile Service:
```csharp
// Set profile for a tenant
scoringProfileService.SetProfileForTenant("tenant-id", new ScoringProfileConfig
{
Profile = ScoringProfile.Simple
});
// Remove override (revert to default)
scoringProfileService.RemoveProfileForTenant("tenant-id");
```
## API Integration
### Scoring with Default Profile
```csharp
var result = await profileAwareScoringService.ScoreAsync(input);
// Uses tenant's configured profile
```
### Scoring with Explicit Profile
```csharp
var result = await profileAwareScoringService.ScoreWithProfileAsync(
input,
ScoringProfile.Simple);
```
### Profile Comparison
```csharp
var comparison = await profileAwareScoringService.CompareProfilesAsync(input);
// Returns scores from all profiles for analysis
```
## Audit Trail
All scoring results include profile identification:
```json
{
"finding_id": "CVE-2024-12345-pkg-1.0.0",
"scoring_profile": "simple",
"profile_version": "simple-v1",
"raw_score": 65,
"final_score": 65,
"severity": "medium",
"signal_values": {
"baseSeverity": 75,
"reachability": 70,
"evidence": 45,
"provenance": 60
},
"signal_contributions": {
"baseSeverity": 7.5,
"reachability": 31.5,
"evidence": 13.5,
"provenance": 9.0
},
"explain": [
{ "factor": "baseSeverity", "value": 75, "reason": "CVSS 7.5 (v3.1) with version adjustment" },
{ "factor": "evidence", "value": 45, "reason": "45 evidence points, 14 days old (90% freshness)" },
{ "factor": "provenance", "value": 60, "reason": "Provenance level: SignedWithSbom" },
{ "factor": "reachability", "value": 70, "reason": "2 hops from call graph" }
]
}
```
## Migration Guide
### From Legacy Scoring
1. **Audit current scores**: Export current scores for baseline comparison
2. **Enable Simple profile**: Start with Simple for predictable behavior
3. **Compare profiles**: Use `CompareProfilesAsync` to understand differences
4. **Gradual rollout**: Move to Advanced when confidence is established
### Profile Switching Best Practices
- **Test in staging first**: Validate score distribution before production
- **Monitor severity distribution**: Watch for unexpected shifts
- **Document changes**: Record profile changes in policy lifecycle
- **Use replay**: Re-score historical findings to validate behavior
## Determinism
Both Simple and Advanced profiles are fully deterministic:
- **Explicit time**: All calculations use `AsOf` timestamp
- **Integer math**: Basis-point arithmetic avoids floating-point drift
- **Stable ordering**: Explanations sorted alphabetically by factor
- **Input digests**: Track input hashes for replay validation
## Performance
| Profile | Typical Latency | Memory |
|---------|----------------|--------|
| Simple | < 1ms | Minimal |
| Advanced | < 5ms | Minimal |
| Custom | Varies | Depends on Rego complexity |
## Related Documentation
- [Score Policy YAML](./score-policy-yaml.md)
- [Signals Weighting](./signals-weighting.md)
- [VEX Trust Model](./vex-trust-model.md)
- [Policy Overview](./overview.md)

185
docs/reachability/gates.md Normal file
View File

@@ -0,0 +1,185 @@
# Gate Detection for Reachability Scoring
> **Sprint:** SPRINT_3405_0001_0001
> **Module:** Scanner Reachability / Signals
## Overview
Gate detection identifies protective controls in code paths that reduce the likelihood of vulnerability exploitation. When a vulnerable function is protected by authentication, feature flags, admin-only checks, or configuration gates, the reachability score is reduced proportionally.
## Gate Types
| Gate Type | Multiplier | Description |
|-----------|------------|-------------|
| `AuthRequired` | 30% | Code path requires authentication |
| `FeatureFlag` | 20% | Code path behind a feature flag |
| `AdminOnly` | 15% | Code path requires admin/elevated role |
| `NonDefaultConfig` | 50% | Code path requires non-default configuration |
### Multiplier Stacking
Multiple gate types stack multiplicatively:
```
Auth (30%) × Feature Flag (20%) = 6%
Auth (30%) × Admin (15%) = 4.5%
All four gates = ~0.45% (floored to 5%)
```
A minimum floor of **5%** prevents scores from reaching zero.
## Detection Methods
### AuthGateDetector
Detects authentication requirements:
**C# Patterns:**
- `[Authorize]` attribute
- `User.Identity.IsAuthenticated` checks
- `HttpContext.User` access
- JWT/Bearer token validation
**Java Patterns:**
- `@PreAuthorize`, `@Secured` annotations
- `SecurityContextHolder.getContext()`
- Spring Security filter chains
**Go Patterns:**
- Middleware patterns (`authMiddleware`, `RequireAuth`)
- Context-based auth checks
**JavaScript/TypeScript Patterns:**
- Express.js `passport` middleware
- JWT verification middleware
- Session checks
### FeatureFlagDetector
Detects feature flag guards:
**Patterns:**
- LaunchDarkly: `ldClient.variation()`, `ld.boolVariation()`
- Split.io: `splitClient.getTreatment()`
- Unleash: `unleash.isEnabled()`
- Custom: `featureFlags.isEnabled()`, `isFeatureEnabled()`
### AdminOnlyDetector
Detects admin/role requirements:
**Patterns:**
- `[Authorize(Roles = "Admin")]`
- `User.IsInRole("Admin")`
- `@RolesAllowed("ADMIN")`
- RBAC middleware checks
### ConfigGateDetector
Detects configuration-based gates:
**Patterns:**
- Environment variable checks (`process.env.ENABLE_FEATURE`)
- Configuration file conditionals
- Runtime feature toggles
- Debug-only code paths
## Output Contract
### DetectedGate
```typescript
interface DetectedGate {
type: 'AuthRequired' | 'FeatureFlag' | 'AdminOnly' | 'NonDefaultConfig';
detail: string; // Human-readable description
guardSymbol: string; // Symbol where gate was detected
sourceFile?: string; // Source file location
lineNumber?: number; // Line number
confidence: number; // 0.0-1.0 confidence score
detectionMethod: string; // Detection algorithm used
}
```
### GateDetectionResult
```typescript
interface GateDetectionResult {
gates: DetectedGate[];
hasGates: boolean;
primaryGate?: DetectedGate; // Highest confidence gate
combinedMultiplierBps: number; // Basis points (10000 = 100%)
}
```
## Integration
### RichGraph Edge Annotation
Gates are annotated on `RichGraphEdge` objects:
```csharp
public sealed record RichGraphEdge
{
// ... existing properties ...
/// <summary>Gates detected on this edge</summary>
public IReadOnlyList<DetectedGate> Gates { get; init; } = [];
/// <summary>Combined gate multiplier in basis points</summary>
public int GateMultiplierBps { get; init; } = 10000;
}
```
### ReachabilityReport
Gates are included in the reachability report:
```json
{
"vulnId": "CVE-2024-0001",
"reachable": true,
"score": 7.5,
"adjustedScore": 2.25,
"gates": [
{
"type": "AuthRequired",
"detail": "[Authorize] attribute on controller",
"guardSymbol": "MyController.VulnerableAction",
"confidence": 0.95
}
],
"gateMultiplierBps": 3000
}
```
## Configuration
### appsettings.json
```json
{
"Reachability": {
"GateMultipliers": {
"AuthRequiredMultiplierBps": 3000,
"FeatureFlagMultiplierBps": 2000,
"AdminOnlyMultiplierBps": 1500,
"NonDefaultConfigMultiplierBps": 5000,
"MinimumMultiplierBps": 500
}
}
}
```
## Metrics
| Metric | Description |
|--------|-------------|
| `scanner.gates_detected_total` | Total gates detected by type |
| `scanner.gate_reduction_applied` | Histogram of multiplier reductions |
| `scanner.gated_vulns_total` | Vulnerabilities with gates detected |
## Related Documentation
- [Reachability Architecture](../modules/scanner/architecture.md)
- [Determinism Technical Reference](../product-advisories/14-Dec-2025%20-%20Determinism%20and%20Reproducibility%20Technical%20Reference.md) - Sections 2.2, 4.3
- [Signals Service](../modules/signals/architecture.md)

View File

@@ -0,0 +1,52 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/schemas/evidence.stella/v1.json",
"title": "Evidence Predicate Schema",
"description": "Schema for evidence.stella/v1 predicate type - raw evidence from scanner or feed",
"type": "object",
"required": [
"source",
"sourceVersion",
"collectionTime",
"sbomEntryId",
"rawFinding",
"evidenceId"
],
"properties": {
"source": {
"type": "string",
"minLength": 1,
"description": "Scanner or feed name that produced this evidence"
},
"sourceVersion": {
"type": "string",
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+.*$",
"description": "Version of the source tool"
},
"collectionTime": {
"type": "string",
"format": "date-time",
"description": "UTC timestamp when evidence was collected"
},
"sbomEntryId": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}:pkg:.+",
"description": "Reference to the SBOM entry this evidence relates to"
},
"vulnerabilityId": {
"type": "string",
"pattern": "^(CVE-[0-9]{4}-[0-9]+|GHSA-.+)$",
"description": "CVE or vulnerability identifier if applicable"
},
"rawFinding": {
"type": ["object", "string"],
"description": "Pointer to or inline representation of raw finding data"
},
"evidenceId": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$",
"description": "Content-addressed ID of this evidence (hash of canonical JSON)"
}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,52 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/schemas/proofspine.stella/v1.json",
"title": "Proof Spine Predicate Schema",
"description": "Schema for proofspine.stella/v1 predicate type - merkle-aggregated proof bundle",
"type": "object",
"required": [
"sbomEntryId",
"evidenceIds",
"reasoningId",
"vexVerdictId",
"policyVersion",
"proofBundleId"
],
"properties": {
"sbomEntryId": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}:pkg:.+",
"description": "The SBOM entry ID this proof spine covers"
},
"evidenceIds": {
"type": "array",
"items": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$"
},
"minItems": 1,
"description": "Sorted list of evidence IDs included in this proof bundle"
},
"reasoningId": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$",
"description": "The reasoning ID linking evidence to verdict"
},
"vexVerdictId": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$",
"description": "The VEX verdict ID for this entry"
},
"policyVersion": {
"type": "string",
"pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]+$",
"description": "Version of the policy used"
},
"proofBundleId": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$",
"description": "Content-addressed ID of this proof bundle (merkle root)"
}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,65 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/schemas/reasoning.stella/v1.json",
"title": "Reasoning Predicate Schema",
"description": "Schema for reasoning.stella/v1 predicate type - policy evaluation trace",
"type": "object",
"required": [
"sbomEntryId",
"evidenceIds",
"policyVersion",
"inputs",
"reasoningId"
],
"properties": {
"sbomEntryId": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}:pkg:.+",
"description": "The SBOM entry ID this reasoning applies to"
},
"evidenceIds": {
"type": "array",
"items": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$"
},
"minItems": 1,
"description": "Evidence IDs that were considered in this reasoning"
},
"policyVersion": {
"type": "string",
"pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]+$",
"description": "Version of the policy used for evaluation"
},
"inputs": {
"type": "object",
"required": ["currentEvaluationTime"],
"properties": {
"currentEvaluationTime": {
"type": "string",
"format": "date-time",
"description": "The evaluation time used for temporal reasoning"
},
"severityThresholds": {
"type": "object",
"description": "Severity thresholds applied during evaluation"
},
"latticeRules": {
"type": "object",
"description": "Lattice rules used for status merging"
}
},
"additionalProperties": false
},
"intermediateFindings": {
"type": "object",
"description": "Intermediate findings from the evaluation"
},
"reasoningId": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$",
"description": "Content-addressed ID of this reasoning"
}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,96 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/schemas/sbom-linkage/v1.json",
"title": "SBOM Linkage Predicate Schema",
"description": "Schema for sbom-linkage/v1 predicate type - SBOM-to-component linkage",
"type": "object",
"required": [
"sbom",
"generator",
"generatedAt"
],
"properties": {
"sbom": {
"type": "object",
"required": ["id", "format", "specVersion", "mediaType", "sha256"],
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "Unique identifier of the SBOM"
},
"format": {
"type": "string",
"enum": ["CycloneDX", "SPDX"],
"description": "Format of the SBOM"
},
"specVersion": {
"type": "string",
"description": "Specification version"
},
"mediaType": {
"type": "string",
"description": "MIME type of the SBOM document"
},
"sha256": {
"type": "string",
"pattern": "^[a-f0-9]{64}$",
"description": "SHA-256 digest of the SBOM content"
},
"location": {
"type": "string",
"description": "Optional location URI (oci:// or file://)"
}
},
"additionalProperties": false
},
"generator": {
"type": "object",
"required": ["name", "version"],
"properties": {
"name": {
"type": "string",
"minLength": 1,
"description": "Name of the generator tool"
},
"version": {
"type": "string",
"description": "Version of the generator tool"
}
},
"additionalProperties": false
},
"generatedAt": {
"type": "string",
"format": "date-time",
"description": "UTC timestamp when this linkage was generated"
},
"incompleteSubjects": {
"type": "array",
"items": {
"type": "object",
"required": ["name", "reason"],
"properties": {
"name": {
"type": "string",
"description": "Name or identifier of the incomplete subject"
},
"reason": {
"type": "string",
"description": "Reason why the subject is incomplete"
}
},
"additionalProperties": false
},
"description": "Subjects that could not be fully resolved"
},
"tags": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "Arbitrary tags for classification or filtering"
}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,123 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/schemas/verdict.stella/v1.json",
"title": "Verdict Receipt Predicate Schema",
"description": "Schema for verdict.stella/v1 predicate type - final surfaced decision receipt",
"type": "object",
"required": [
"graphRevisionId",
"findingKey",
"rule",
"decision",
"inputs",
"outputs",
"createdAt"
],
"properties": {
"graphRevisionId": {
"type": "string",
"pattern": "^grv_sha256:[a-f0-9]{64}$",
"description": "The graph revision ID this verdict was computed from"
},
"findingKey": {
"type": "object",
"required": ["sbomEntryId", "vulnerabilityId"],
"properties": {
"sbomEntryId": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}:pkg:.+",
"description": "The SBOM entry ID for the component"
},
"vulnerabilityId": {
"type": "string",
"pattern": "^(CVE-[0-9]{4}-[0-9]+|GHSA-.+)$",
"description": "The vulnerability ID"
}
},
"additionalProperties": false
},
"rule": {
"type": "object",
"required": ["id", "version"],
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "Unique identifier of the rule"
},
"version": {
"type": "string",
"description": "Version of the rule"
}
},
"additionalProperties": false
},
"decision": {
"type": "object",
"required": ["status", "reason"],
"properties": {
"status": {
"type": "string",
"enum": ["block", "warn", "pass"],
"description": "Status of the decision"
},
"reason": {
"type": "string",
"minLength": 1,
"description": "Human-readable reason for the decision"
}
},
"additionalProperties": false
},
"inputs": {
"type": "object",
"required": ["sbomDigest", "feedsDigest", "policyDigest"],
"properties": {
"sbomDigest": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$",
"description": "Digest of the SBOM used"
},
"feedsDigest": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$",
"description": "Digest of the advisory feeds used"
},
"policyDigest": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$",
"description": "Digest of the policy bundle used"
}
},
"additionalProperties": false
},
"outputs": {
"type": "object",
"required": ["proofBundleId", "reasoningId", "vexVerdictId"],
"properties": {
"proofBundleId": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$",
"description": "The proof bundle ID containing the evidence chain"
},
"reasoningId": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$",
"description": "The reasoning ID explaining the decision"
},
"vexVerdictId": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$",
"description": "The VEX verdict ID for this finding"
}
},
"additionalProperties": false
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "UTC timestamp when this verdict was created"
}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,54 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/schemas/cdx-vex.stella/v1.json",
"title": "VEX Verdict Predicate Schema",
"description": "Schema for cdx-vex.stella/v1 predicate type - VEX verdict with provenance",
"type": "object",
"required": [
"sbomEntryId",
"vulnerabilityId",
"status",
"justification",
"policyVersion",
"reasoningId",
"vexVerdictId"
],
"properties": {
"sbomEntryId": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}:pkg:.+",
"description": "The SBOM entry ID this verdict applies to"
},
"vulnerabilityId": {
"type": "string",
"pattern": "^(CVE-[0-9]{4}-[0-9]+|GHSA-.+)$",
"description": "The vulnerability ID (CVE, GHSA, etc.)"
},
"status": {
"type": "string",
"enum": ["not_affected", "affected", "fixed", "under_investigation"],
"description": "VEX status"
},
"justification": {
"type": "string",
"minLength": 1,
"description": "Justification for the VEX status"
},
"policyVersion": {
"type": "string",
"pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]+$",
"description": "Version of the policy used"
},
"reasoningId": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$",
"description": "Reference to the reasoning that led to this verdict"
},
"vexVerdictId": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$",
"description": "Content-addressed ID of this VEX verdict"
}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,80 @@
# Mutation Testing Baselines
> Sprint: SPRINT_0353_0001_0001_mutation_testing_integration
> Task: MUT-0353-005
This document tracks mutation testing baselines for critical modules.
## Baseline Scores
| Module | Initial Score | Target Score | Date Established |
|--------|--------------|--------------|------------------|
| Scanner.Core | 72% | ≥ 80% | 2025-12-16 |
| Policy.Engine | 68% | ≥ 80% | 2025-12-16 |
| Authority.Core | 75% | ≥ 85% | 2025-12-16 |
| Signer.Core | 70% | ≥ 80% | TBD |
| Attestor.Core | 65% | ≥ 80% | TBD |
| Reachability.Core | 60% | ≥ 75% | TBD |
## Threshold Configuration
See `stryker-thresholds.json` for per-module threshold configuration.
## Mutation Operators Applied
| Operator | Description | Enabled |
|----------|-------------|---------|
| Arithmetic | Replace +, -, *, /, % | ✓ |
| Boolean | Flip true/false | ✓ |
| Comparison | Replace <, >, <=, >=, ==, != | ✓ |
| Logical | Replace &&, ||, ! | ✓ |
| String | Mutate string literals | ✓ |
| Linq | Mutate LINQ methods | ✓ |
| NullCoalescing | Mutate ?? operators | ✓ |
| Assignment | Mutate assignment operators | ✓ |
## Exclusions
The following patterns are excluded from mutation testing:
- `**/Migrations/**` - Database migrations (tested via integration tests)
- `**/Generated/**` - Generated code
- `**/*.g.cs` - Source-generated files
- `**/Models/**` - Simple data transfer objects
- `**/Exceptions/**` - Exception types (tested via integration)
## Running Mutation Tests
### Local Execution
```bash
# Run mutation tests for a specific module
cd src/Scanner/__Libraries/StellaOps.Scanner.Core
dotnet stryker
# Run with specific configuration
dotnet stryker -f stryker-config.json --reporter html
# Quick mode (fewer mutations, faster feedback)
dotnet stryker --since:main
```
### CI Execution
Mutation tests run on:
- Merge requests targeting main
- Weekly scheduled runs (comprehensive)
Results are uploaded as artifacts and published to the mutation testing dashboard.
## Improving Mutation Score
1. **Add missing test cases** - Cover edge cases revealed by surviving mutants
2. **Strengthen assertions** - Replace weak assertions with specific ones
3. **Test boundary conditions** - Cover off-by-one and boundary scenarios
4. **Add negative tests** - Test that invalid inputs are rejected
## References
- [Stryker.NET Documentation](https://stryker-mutator.io/docs/stryker-net/)
- [Mutation Testing Guide](../testing/mutation-testing-guide.md)

View File

@@ -0,0 +1,229 @@
# Security Testing Guide
> Sprint: SPRINT_0352_0001_0001_security_testing_framework
> Task: SEC-0352-010
This guide describes the security testing framework used in StellaOps, aligned with OWASP Top 10 categories.
## Overview
The security testing framework provides automated tests for common security vulnerabilities organized by OWASP category:
| OWASP Category | Directory | Status |
|----------------|-----------|--------|
| A01: Broken Access Control | `A01_BrokenAccessControl/` | ✓ Implemented |
| A02: Cryptographic Failures | `A02_CryptographicFailures/` | ✓ Implemented |
| A03: Injection | `A03_Injection/` | ✓ Implemented |
| A05: Security Misconfiguration | `A05_SecurityMisconfiguration/` | ✓ Implemented |
| A07: Authentication Failures | `A07_AuthenticationFailures/` | ✓ Implemented |
| A08: Software/Data Integrity | `A08_SoftwareDataIntegrity/` | ✓ Implemented |
| A10: SSRF | `A10_SSRF/` | ✓ Implemented |
## Directory Structure
```
tests/
└── security/
├── README.md
└── StellaOps.Security.Tests/
├── Infrastructure/
│ ├── SecurityTestBase.cs
│ ├── MaliciousPayloads.cs
│ └── SecurityAssertions.cs
├── A01_BrokenAccessControl/
├── A02_CryptographicFailures/
├── A03_Injection/
├── A05_SecurityMisconfiguration/
├── A07_AuthenticationFailures/
├── A08_SoftwareDataIntegrity/
└── A10_SSRF/
```
## Running Security Tests
### Local Execution
```bash
# Run all security tests
cd tests/security/StellaOps.Security.Tests
dotnet test --filter "Category=Security"
# Run specific OWASP category
dotnet test --filter "OWASP=A01"
# Run with detailed output
dotnet test --filter "Category=Security" --verbosity detailed
```
### CI Integration
Security tests run automatically on:
- All pull requests to `main` or `develop`
- Scheduled nightly builds
Results are uploaded as artifacts and any failures block the PR.
## Test Categories
### A01: Broken Access Control
Tests for authorization bypass vulnerabilities:
- Tenant isolation violations
- RBAC enforcement
- Privilege escalation
- IDOR (Insecure Direct Object References)
### A02: Cryptographic Failures
Tests for cryptographic weaknesses:
- Key material exposure in logs
- Weak algorithm usage
- TLS configuration
- Secure random generation
### A03: Injection
Tests for injection vulnerabilities:
- SQL injection (parameterization)
- Command injection
- ORM injection
- Path traversal
### A05: Security Misconfiguration
Tests for configuration errors:
- Debug mode in production
- Error detail leakage
- Security headers
- CORS configuration
### A07: Authentication Failures
Tests for authentication weaknesses:
- Brute force protection
- Weak password acceptance
- Session management
- Account lockout
### A08: Software/Data Integrity
Tests for integrity verification:
- Artifact signature verification
- SBOM integrity
- Attestation chain validation
- DSSE envelope validation
### A10: SSRF
Tests for server-side request forgery:
- Internal network access
- Cloud metadata endpoint blocking
- URL validation
## Writing Security Tests
### Base Class
All security tests should extend `SecurityTestBase`:
```csharp
using StellaOps.Security.Tests.Infrastructure;
[Trait("Category", "Security")]
[Trait("OWASP", "A01")]
public sealed class MySecurityTests : SecurityTestBase
{
[Fact(DisplayName = "A01-XXX: Descriptive test name")]
public void TestMethod()
{
// Arrange, Act, Assert
}
}
```
### Naming Convention
- Test display names: `A{category}-{number}: {description}`
- Example: `A01-001: Admin endpoints should require authentication`
### Test Traits
Always include these traits:
- `Category = Security`
- `OWASP = A{category}`
## Security Test Guidelines
1. **Test both positive and negative cases** - Verify both allowed and denied actions
2. **Use realistic payloads** - Include common attack patterns from `MaliciousPayloads.cs`
3. **Don't rely on security by obscurity** - Assume attackers know the system
4. **Test boundaries** - Check edge cases and boundary conditions
5. **Document expected behavior** - Use descriptive test names and assertions
## Malicious Payloads
The `MaliciousPayloads.cs` file contains common attack patterns:
```csharp
public static class MaliciousPayloads
{
public static readonly string[] SqlInjection = new[]
{
"' OR '1'='1",
"1; DROP TABLE users--",
"admin'--"
};
public static readonly string[] CommandInjection = new[]
{
"; rm -rf /",
"| cat /etc/passwd",
"$(whoami)"
};
public static readonly string[] PathTraversal = new[]
{
"../../../etc/passwd",
"..\\..\\..\\windows\\system32\\config\\sam"
};
}
```
## CI Integration
### Workflow Configuration
The security test job runs after build-test completes:
```yaml
security-testing:
runs-on: ubuntu-22.04
needs: build-test
steps:
- name: Run OWASP security tests
run: |
dotnet test tests/security/StellaOps.Security.Tests \
--filter "Category=Security" \
--logger "trx;LogFileName=security-tests.trx"
```
### Failure Handling
Security test failures:
- Block PR merge
- Generate detailed report
- Notify security team via webhook
## Reporting
Security test results are:
- Uploaded as CI artifacts
- Included in quality gate summary
- Tracked for trend analysis
## Related Documentation
- [OWASP Top 10](https://owasp.org/Top10/)
- [OWASP Testing Guide](https://owasp.org/www-project-web-security-testing-guide/)
- [Mutation Testing Guide](./mutation-testing-guide.md)
- [CI Quality Gates](./ci-quality-gates.md)