archive audit attempts

This commit is contained in:
master
2026-02-19 22:00:31 +02:00
parent c2f13fe588
commit b5829dce5c
19638 changed files with 6366 additions and 7 deletions

View File

@@ -0,0 +1,241 @@
# Contract: Artifact Canonical Record (v1)
## Status
- Status: DRAFT (2026-02-19)
- Owners: Attestor Guild, EvidenceLocker Guild
- Consumers: Evidence Thread API, Audit Pack Export, Console UI
- Sprint: SPRINT_20260219_009 (CID-03)
## Purpose
Define a unified document that aggregates all evidence references for a single artifact identified by `canonical_id`. Today this information is distributed across Attestor (`dsse_envelopes`), Scanner (SBOM refs), VexLens (consensus records), and OCI registries (referrers). The Artifact Canonical Record provides a single query target for:
1. The Evidence Thread API (`GET /api/v1/evidence/thread/{id}`)
2. Audit pack generation (export all evidence for an artifact)
3. Console UI proof chain visualization
## Record Schema
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Artifact Canonical Record v1",
"type": "object",
"required": ["canonical_id", "format", "sbom_ref", "created_at"],
"properties": {
"canonical_id": {
"type": "string",
"description": "sha256:<hex> computed per canonical-sbom-id-v1.md",
"pattern": "^sha256:[a-f0-9]{64}$"
},
"format": {
"type": "string",
"description": "Canonicalization format identifier",
"const": "cyclonedx-jcs:1"
},
"sbom_ref": {
"type": "string",
"description": "Content-addressable reference to the SBOM (CAS URI or OCI ref)",
"examples": [
"cas://sbom/inventory/abc123.json",
"oci://registry/repo@sha256:abc123"
]
},
"attestations": {
"type": "array",
"description": "All DSSE attestations referencing this artifact",
"items": {
"type": "object",
"required": ["predicate_type", "dsse_digest", "signed_at"],
"properties": {
"predicate_type": {
"type": "string",
"description": "Predicate type URI from the predicate registry"
},
"dsse_digest": {
"type": "string",
"description": "SHA-256 of the DSSE envelope body",
"pattern": "^sha256:[a-f0-9]{64}$"
},
"signer_keyid": {
"type": "string",
"description": "Key ID of the signer"
},
"rekor_entry_id": {
"type": "string",
"description": "Rekor transparency log entry UUID (null if offline)"
},
"rekor_tile": {
"type": "string",
"description": "Rekor tile URL for inclusion proof verification"
},
"signed_at": {
"type": "string",
"format": "date-time"
}
}
}
},
"referrers": {
"type": "array",
"description": "OCI referrers (symbol bundles, attestation manifests)",
"items": {
"type": "object",
"required": ["media_type", "descriptor_digest"],
"properties": {
"media_type": {
"type": "string",
"description": "OCI media type",
"examples": [
"application/vnd.stella.symbols+tar",
"application/vnd.in-toto+json"
]
},
"descriptor_digest": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$"
},
"registry": {
"type": "string",
"description": "Registry hostname"
}
}
}
},
"vex_refs": {
"type": "array",
"description": "VEX consensus records targeting this artifact",
"items": {
"type": "object",
"required": ["vulnerability_id", "consensus_status"],
"properties": {
"vulnerability_id": {
"type": "string",
"description": "CVE or advisory ID"
},
"consensus_status": {
"type": "string",
"enum": ["affected", "not_affected", "under_investigation", "fixed"]
},
"confidence_score": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"consensus_digest": {
"type": "string",
"description": "SHA-256 of the VexLens consensus record"
},
"dsse_digest": {
"type": "string",
"description": "SHA-256 of the VEX attestation DSSE (if signed)"
},
"rekor_tile": {
"type": "string",
"description": "Rekor tile URL (if anchored)"
}
}
}
},
"created_at": {
"type": "string",
"format": "date-time"
},
"updated_at": {
"type": "string",
"format": "date-time"
}
}
}
```
## Content Addressing
The record itself is content-addressed:
```
record_id := sha256(JCS(record_json))
```
This enables deduplication and integrity verification of the record.
## Storage Design
### Option A: Materialized View (recommended for v1)
Create a PostgreSQL materialized view joining existing tables:
```sql
CREATE MATERIALIZED VIEW proofchain.artifact_canonical_records AS
SELECT
se.bom_digest AS canonical_id,
'cyclonedx-jcs:1' AS format,
se.artifact_digest,
se.purl,
se.created_at,
-- Attestations aggregated as JSONB array
COALESCE(
jsonb_agg(DISTINCT jsonb_build_object(
'predicate_type', de.predicate_type,
'dsse_digest', de.body_hash,
'signer_keyid', de.signer_keyid,
'rekor_entry_id', re.uuid,
'rekor_tile', re.log_id,
'signed_at', de.signed_at
)) FILTER (WHERE de.env_id IS NOT NULL),
'[]'::jsonb
) AS attestations
FROM proofchain.sbom_entries se
LEFT JOIN proofchain.dsse_envelopes de ON de.entry_id = se.entry_id
LEFT JOIN proofchain.rekor_entries re ON re.env_id = de.env_id
GROUP BY se.entry_id, se.bom_digest, se.artifact_digest, se.purl, se.created_at;
CREATE UNIQUE INDEX idx_acr_canonical_id ON proofchain.artifact_canonical_records(canonical_id);
```
**Refresh strategy:** `REFRESH MATERIALIZED VIEW CONCURRENTLY` triggered by:
- New DSSE envelope submission
- New Rekor entry confirmation
- Periodic schedule (every 5 minutes)
### Option B: Dedicated Table (for v2 if write patterns emerge)
If the record needs to be written to directly (e.g., for referrers and VEX refs that come from different services), migrate to a dedicated table with trigger-based population from upstream sources.
## API Integration
### Evidence Thread Endpoint
`GET /api/v1/evidence/thread/{canonical_id}`
Returns the Artifact Canonical Record for the given `canonical_id`. The response includes all attestations, referrers, and VEX refs.
Query parameters:
- `include_attestations` (boolean, default true)
- `include_vex` (boolean, default true)
- `include_referrers` (boolean, default true)
### Evidence Thread by PURL
`GET /api/v1/evidence/thread?purl={purl}`
Resolves PURL to `canonical_id` via `sbom_entries` table, then returns the record.
## Offline Mode
When Rekor is unavailable (air-gapped deployment):
- `rekor_entry_id` and `rekor_tile` fields are `null`
- A `transparency_status` field indicates `"offline"` with a `reason` sub-field
- Verification uses bundled checkpoints instead of live Rekor queries
## Determinism
The record's content is deterministic given frozen inputs:
- Attestation arrays sorted by `signed_at` ascending, then `predicate_type` ascending
- VEX refs sorted by `vulnerability_id` ascending
- Referrers sorted by `media_type` ascending, then `descriptor_digest` ascending
## References
- `docs/contracts/canonical-sbom-id-v1.md` - Canonical ID computation
- `docs/modules/attestor/architecture.md` - DSSE envelope storage
- `docs/modules/evidence-locker/attestation-contract.md` - Evidence Bundle v1
- Endpoint contract: S00-T05-EVID-01

View File

@@ -0,0 +1,132 @@
# Beacon Attestation Predicate Contract
**Predicate Type:** `stella.ops/beaconAttestation@v1`
**Status:** Active
**Sprint:** SPRINT_20260219_014
Provides low-volume, signed proof that a specific artifact actually executed in a real environment. Beacon events are captured via eBPF uprobe (Linux), ETW DynamicTraceProvider (Windows), or dyld interpose (macOS) on a designated "beacon function" — without modifying the artifact.
## Overview
- **Execution proof:** Cryptographic attestation that an artifact ran in a specific environment.
- **Config-driven instrumentation:** Beacon functions are designated by configuration, not by modifying container entrypoints. Preserves image integrity.
- **Batched attestation:** Events collected over a configurable window (default: 5 minutes) produce a single attestation.
- **Nonce-protected:** Per-event nonces prevent replay attacks. Sequence numbers detect gaps.
- **Offline-first:** No external service dependencies.
## Schema
```json
{
"predicateType": "stella.ops/beaconAttestation@v1",
"subject": [{"name": "artifact", "digest": {"sha256": "<canonical_id>"}}],
"predicate": {
"artifact_id": "sha256:<image_or_binary_digest>",
"environment_id": "<env_identifier>",
"beacon_source": "ebpf_uprobe|etw_dynamic|dyld_interpose",
"beacon_function": "<symbol_name_or_address_range>",
"window_start": "2026-02-19T12:00:00Z",
"window_end": "2026-02-19T12:05:00Z",
"beacon_count": 47,
"first_sequence": 1,
"last_sequence": 50,
"sequence_gaps": 3,
"verification_rate": 0.94,
"timestamp": "2026-02-19T12:05:01Z"
}
}
```
## Field Definitions
### Root Fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `predicateType` | string | yes | Always `stella.ops/beaconAttestation@v1` |
| `subject` | array | yes | Single-element array with artifact canonical ID |
### Predicate Fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `artifact_id` | string | yes | `sha256:<digest>` of the container image or binary |
| `environment_id` | string | yes | Identifier of the environment where beacon was observed |
| `beacon_source` | string | yes | Instrumentation source: `ebpf_uprobe`, `etw_dynamic`, or `dyld_interpose` |
| `beacon_function` | string | yes | Symbol name or address range of the designated beacon function |
| `window_start` | ISO 8601 | yes | Start of the batching window |
| `window_end` | ISO 8601 | yes | End of the batching window |
| `beacon_count` | int | yes | Number of verified beacon events in the window |
| `first_sequence` | long | yes | First sequence number in the window |
| `last_sequence` | long | yes | Last sequence number in the window |
| `sequence_gaps` | int | yes | Number of detected sequence gaps |
| `verification_rate` | double | yes | `verified_count / expected_count` (0.01.0) |
| `timestamp` | ISO 8601 | yes | When the attestation predicate was generated |
## Beacon Function Designation
Beacon functions are designated by **environment-level configuration**, not by modifying the artifact:
```yaml
Signals:
Beacon:
Enabled: true
Functions:
- ArtifactPattern: "myapp:*"
SymbolName: "healthCheck"
Source: "ebpf_uprobe"
- ArtifactPattern: "api-gateway:*"
AddressRange: "0x1000-0x1100"
Source: "etw_dynamic"
```
The eBPF/ETW probe attaches to the named symbol or address range externally. **No entrypoint injection or image modification is performed.** This preserves the attestation chain that Stella Ops signs and verifies.
## Nonce and Sequence Semantics
- **Nonce:** Random unique value per beacon event. The deduplication cache rejects events with duplicate `(artifact_id, environment_id, nonce)` tuples. Nonce TTL is configurable (default: 1 hour).
- **Sequence:** Monotonically increasing counter per `(artifact_id, environment_id)`. Gaps in the sequence indicate missed beacon events (probe failure, artifact restart, or instrumentation issue).
- **Verification rate:** `verified_beacons / (last_sequence - first_sequence + 1)` over the batch window.
## DSSE Signing
Beacon predicates are wrapped in DSSE envelopes and signed using the environment's configured crypto profile. Supported: EdDSA, ECDSA, RSA, GOST R 34.10, SM2, eIDAS QSealC, PQC (ML-DSA).
## Batching
Beacon events are lightweight and high-frequency. Rather than signing per-event, the pipeline collects events over a configurable window (default: 5 minutes) and produces a single attestation summarizing the window.
Early flush occurs if the batch reaches `MaxBatchSize` (default: 1000) before the window expires.
## Configuration
Section: `Signals:Beacon`
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `Enabled` | bool | `true` | Whether the beacon pipeline is active |
| `BatchWindowSeconds` | int | `300` | Batching window (5 minutes) |
| `MaxBatchSize` | int | `1000` | Max events per batch (triggers early flush) |
| `NonceTtlSeconds` | int | `3600` | Nonce deduplication cache TTL (1 hour) |
| `VerificationRateLookbackHours` | int | `24` | Lookback window for rate computation |
## API Endpoints
| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/signals/beacons` | Ingest beacon events |
| `GET` | `/signals/beacons/rate/{artifactId}/{environmentId}` | Query beacon verification rate |
## Audit Pack Export
Beacon attestation predicates are included in Evidence Bundle exports as `predicates/beacon-attestation.json`. The bundle's `checksums.sha256` manifest includes the beacon attestation entry for offline verification.
## Related Documents
- `docs/contracts/execution-evidence-v1.md` — Full execution evidence predicate (heavyweight complement)
- `docs/contracts/witness-v1.md` — Runtime witness predicate
- `docs/modules/policy/gates/beacon-rate-gate.md` — Policy gate consuming beacon verification rate
---
*Last updated: 2026-02-19.*

View File

@@ -0,0 +1,156 @@
# Contract: Canonical SBOM Identifier (v1)
## Status
- Status: DRAFT (2026-02-19)
- Owners: Scanner Guild, Attestor Guild
- Consumers: Scanner, Attestor, VexLens, EvidenceLocker, Graph, Policy
## Purpose
Define a single, deterministic, cross-module identifier for any CycloneDX SBOM document. All modules that reference SBOMs must use this identifier for cross-module joins, evidence threading, and verification.
## Definition
```
canonical_id := "sha256:" + hex(SHA-256(JCS(sbom_json)))
```
Where:
- `sbom_json` is the raw CycloneDX JSON document (v1.4 through v1.7)
- `JCS` is JSON Canonicalization Scheme per RFC 8785
- `SHA-256` is the hash function
- `hex` is lowercase hexadecimal encoding
- The result is prefixed with `sha256:` for algorithm agility
## Canonicalization Rules (RFC 8785)
### Object Key Ordering
All JSON object keys are sorted lexicographically using Unicode code point ordering (equivalent to `StringComparer.Ordinal` in .NET).
### Number Serialization
- Integers: no leading zeros, no trailing zeros after decimal point
- Floating point: use shortest representation that round-trips exactly
- No scientific notation for integers
### String Serialization
- Use `\uXXXX` escaping for control characters (U+0000 through U+001F)
- Use minimal escaping (no unnecessary escape sequences)
- UTF-8 encoding
### Whitespace
- No whitespace between tokens (no indentation, no trailing newlines)
### Null Handling
- Null values are serialized as `null` (not omitted)
- Missing optional fields are omitted entirely
### Array Element Order
- Array elements maintain their original order (arrays are NOT sorted)
- This is critical: CycloneDX component arrays preserve document order
## Implementation Reference
### .NET Implementation
Use `StellaOps.AuditPack.Services.CanonicalJson.Canonicalize(ReadOnlySpan<byte> json)`:
```csharp
using StellaOps.AuditPack.Services;
using System.Security.Cryptography;
byte[] sbomJsonBytes = ...; // raw CycloneDX JSON
byte[] canonicalBytes = CanonicalJson.Canonicalize(sbomJsonBytes);
byte[] hash = SHA256.HashData(canonicalBytes);
string canonicalId = "sha256:" + Convert.ToHexString(hash).ToLowerInvariant();
```
### CLI Implementation
```bash
# Using jcs_canonicalize (or equivalent tool)
jcs_canonicalize ./bom.json | sha256sum | awk '{print "sha256:" $1}'
```
## Relationship to Existing Identifiers
### `ContentHash` (CycloneDxArtifact.ContentHash)
- **Current:** `sha256(raw_json_bytes)` -- hash of serialized JSON, NOT canonical
- **Relationship:** `ContentHash` is a serialization-specific hash that depends on whitespace, key ordering, and JSON serializer settings. `canonical_id` is a content-specific hash that is stable across serializers.
- **Coexistence:** Both identifiers are retained. `ContentHash` is used for integrity verification of a specific serialized form. `canonical_id` is used for cross-module reference and evidence threading.
### `stella.contentHash` (SBOM metadata property)
- **Current:** Same as `ContentHash` above, emitted as a metadata property
- **New:** `stella:canonical_id` is emitted as a separate metadata property alongside `stella.contentHash`
### `CompositionRecipeSha256`
- **Purpose:** Hash of the composition recipe (layer ordering), not the SBOM content itself
- **Relationship:** Independent. Composition recipe describes HOW the SBOM was built; `canonical_id` describes WHAT was built.
## DSSE Subject Binding
When a DSSE attestation is created for an SBOM (predicate type `StellaOps.SBOMAttestation@1`), the subject MUST include `canonical_id`:
```json
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "sbom",
"digest": {
"sha256": "<canonical_id_hex_without_prefix>"
}
}
],
"predicateType": "StellaOps.SBOMAttestation@1",
"predicate": { ... }
}
```
Note: The `subject.digest.sha256` value is the hex hash WITHOUT the `sha256:` prefix, following in-toto convention.
## Stability Guarantee
Given identical CycloneDX content (same components, same metadata, same vulnerabilities), `canonical_id` MUST produce the same value:
- Across different machines
- Across different .NET runtime versions
- Across serialization/deserialization round-trips
- Regardless of original JSON formatting (whitespace, key order)
This is the fundamental invariant that enables cross-module evidence joins.
## Test Vectors
### Vector 1: Minimal CycloneDX 1.7
Input:
```json
{"bomFormat":"CycloneDX","specVersion":"1.7","version":1,"components":[]}
```
Expected canonical form: `{"bomFormat":"CycloneDX","components":[],"specVersion":"1.7","version":1}`
Expected canonical_id: compute SHA-256 of the canonical form bytes.
### Vector 2: Key ordering
Input (keys out of order):
```json
{"specVersion":"1.7","bomFormat":"CycloneDX","version":1}
```
Expected canonical form: `{"bomFormat":"CycloneDX","specVersion":"1.7","version":1}`
Must produce same `canonical_id` as any other key ordering of the same content.
### Vector 3: Whitespace normalization
Input (pretty-printed):
```json
{
"bomFormat": "CycloneDX",
"specVersion": "1.7",
"version": 1
}
```
Must produce same `canonical_id` as the minified form.
## Migration Notes
- New attestations MUST include `canonical_id` in the DSSE subject
- Existing attestations are NOT backfilled (they retain their original subject digests)
- Verification of historical attestations uses their original subject binding
- The `stella:canonical_id` metadata property is added to new SBOMs only
## References
- RFC 8785: JSON Canonicalization Scheme (JCS)
- CycloneDX v1.7 specification
- DSSE v1.0 specification
- in-toto Statement v1 specification

View File

@@ -0,0 +1,172 @@
# Execution Evidence Predicate Contract
**Predicate Type:** `stella.ops/executionEvidence@v1`
**Status:** Active
**Sprint:** SPRINT_20260219_013
Provides a cryptographically verifiable, deterministic attestation that a specific artifact was observed executing in a real environment. Converts "it executed" from an implicit signal into a signed DSSE predicate suitable for policy gates and audit packs.
## Overview
- **Auditability:** Offline-verifiable proof that an artifact ran in a given environment.
- **Determinism:** Same trace input blob produces byte-identical predicates (address-canonicalized, sorted).
- **Privacy-safe:** Coarse trace summary (syscall families, hot symbols, counts) — no raw syscall logs.
- **Offline-first:** No external service dependencies; uses local signing keys.
## Schema
```json
{
"predicateType": "stella.ops/executionEvidence@v1",
"subject": [{"name": "artifact", "digest": {"sha256": "<canonical_id>"}}],
"predicate": {
"artifact_id": "sha256:<image_or_binary_digest>",
"environment_id": "<env_identifier>",
"trace_source": "ebpf|etw|dyld",
"observation_window": {
"start": "2026-02-19T12:00:00Z",
"end": "2026-02-19T12:00:10Z",
"duration_ms": 10000
},
"trace_summary": {
"syscall_families_observed": ["network", "filesystem", "process"],
"hot_symbols": ["main", "handleRequest", "db.Query"],
"hot_symbol_count": 42,
"unique_call_paths": 17,
"address_canonicalized": true
},
"trace_digest": "sha256:<hash_of_canonical_trace_blob>",
"determinism": {
"replay_seed": "<if applicable>",
"inputs_digest": "sha256:<hash_of_frozen_inputs>",
"expected_output_digest": "sha256:<if deterministic replay enabled>"
},
"timestamp": "2026-02-19T12:00:10Z"
}
}
```
## Field Definitions
### Root Fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `predicateType` | string | yes | Always `stella.ops/executionEvidence@v1` |
| `subject` | array | yes | Single-element array with artifact canonical ID |
### Predicate Fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `artifact_id` | string | yes | `sha256:<digest>` of the container image or binary |
| `environment_id` | string | yes | Identifier of the environment where execution was observed |
| `trace_source` | string | yes | Instrumentation source: `ebpf`, `etw`, or `dyld` |
| `observation_window` | object | yes | Time window of the trace capture |
| `trace_summary` | object | yes | Coarse summary of observed behavior |
| `trace_digest` | string | yes | `sha256:<hex>` digest of the canonical trace blob |
| `determinism` | object | yes | Replay and determinism metadata |
| `timestamp` | ISO 8601 | yes | When the predicate was generated |
### Observation Window
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start` | ISO 8601 | yes | Trace capture start time |
| `end` | ISO 8601 | yes | Trace capture end time |
| `duration_ms` | long | yes | Window duration in milliseconds |
### Trace Summary
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `syscall_families_observed` | string[] | yes | Coarse syscall families: `network`, `filesystem`, `process` |
| `hot_symbols` | string[] | yes | Top-K symbols by hit count (capped by `MaxHotSymbols`) |
| `hot_symbol_count` | int | yes | Total number of distinct hot symbols before top-K |
| `unique_call_paths` | int | yes | Number of unique call paths observed |
| `address_canonicalized` | bool | yes | Whether ASLR noise was stripped |
### Determinism Metadata
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `replay_seed` | string | no | Seed for deterministic replay (if applicable) |
| `inputs_digest` | string | yes | `sha256:<hex>` of frozen input set |
| `expected_output_digest` | string | no | Expected output digest (if deterministic replay enabled) |
## Privacy Canonicalization Rules
The following transformations are applied to raw trace data before predicate generation:
| Data | Canonicalization | Rationale |
|------|-----------------|-----------|
| Memory addresses (loader base) | Stripped to zero-based offset | Removes ASLR noise |
| Socket addresses | Port stripped, IP retained as family indicator | Privacy — port numbers leak service topology |
| Process names | Retained (coarse family classification only) | Needed for `process` syscall family |
| Raw syscall sequences | Collapsed to family set (`network`, `filesystem`, `process`) | Privacy-safe, deterministic |
| Symbol names | Retained (hot symbols are public API surface) | Required for trace utility |
| File paths | Not included in predicate | Privacy — paths leak deployment layout |
## Digest Computation
### Trace Digest
1. Sort canonical trace events by `(SymbolId, HitCount)` ascending.
2. Serialize to deterministic JSON (sorted keys, no whitespace).
3. Compute `SHA256` over the UTF-8 bytes.
4. Encode as lowercase hex with `sha256:` prefix.
### Predicate Digest
1. Serialize the complete predicate to deterministic JSON.
2. Compute `SHA256` over the UTF-8 bytes.
3. Encode as lowercase hex (no prefix).
## DSSE Signing
Predicates are wrapped in a DSSE envelope and signed using the environment's configured crypto profile. Supported profiles: EdDSA, ECDSA, RSA, GOST R 34.10, SM2, eIDAS QSealC, PQC (ML-DSA).
```json
{
"payloadType": "application/vnd.in-toto+json",
"payload": "<base64(predicate_json)>",
"signatures": [
{
"keyid": "<key_identifier>",
"sig": "<base64(signature)>"
}
]
}
```
## Rate Limiting
One predicate per `(artifact_id, environment_id)` per configurable window (default: 60 minutes). Duplicate submissions within the window return a rate-limited result without generating a new predicate.
## Configuration
Section: `Signals:ExecutionEvidence`
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `Enabled` | bool | `true` | Whether the pipeline is active |
| `RateLimitWindowMinutes` | int | `60` | Rate limit window per artifact/environment pair |
| `MaxHotSymbols` | int | `50` | Maximum hot symbols in trace summary |
| `MinEventsThreshold` | int | `5` | Minimum events required to produce a predicate |
## API Endpoints
| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/signals/execution-evidence` | Submit runtime trace for evidence generation |
| `GET` | `/signals/execution-evidence/{artifactId}/{environmentId}` | Query latest evidence for an artifact/environment pair |
## Related Documents
- `docs/contracts/witness-v1.md` — Runtime witness predicate (complementary)
- `docs/contracts/beacon-attestation-v1.md` — Beacon attestation predicate (lightweight complement)
- `docs/modules/policy/gates/execution-evidence-gate.md` — Policy gate consuming this predicate
---
*Last updated: 2026-02-19.*

View File

@@ -0,0 +1,74 @@
# Predicate Schema: stella.ops/federatedConsent@v1
## Overview
This predicate type represents a consent attestation for federated telemetry participation. A valid consent proof must exist before any telemetry data can be aggregated and shared with federation peers.
## Predicate Type
```
stella.ops/federatedConsent@v1
```
## Schema
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["tenantId", "grantedBy", "grantedAt", "type"],
"properties": {
"tenantId": {
"type": "string",
"description": "Identifier of the tenant granting consent"
},
"grantedBy": {
"type": "string",
"description": "Identity of the actor who granted consent (email or service account)"
},
"grantedAt": {
"type": "string",
"format": "date-time",
"description": "UTC timestamp when consent was granted"
},
"expiresAt": {
"type": ["string", "null"],
"format": "date-time",
"description": "Optional expiry timestamp; null means consent has no TTL"
},
"type": {
"type": "string",
"const": "stella.ops/federatedConsent@v1"
}
}
}
```
## Consent Lifecycle States
| State | Description | Transition |
|-------|-------------|------------|
| Not Granted | Default state; no federation data shared | -> Granted (via POST /consent/grant) |
| Granted | Active consent; federation data flows | -> Revoked (via POST /consent/revoke) or -> Expired (TTL) |
| Expired | TTL-based automatic revocation | -> Granted (re-grant) |
| Revoked | Explicit admin revocation | -> Granted (re-grant) |
## Validation Rules
1. `tenantId` must be non-empty and match the requesting tenant.
2. `grantedBy` must be a valid identity string.
3. `grantedAt` must be a valid UTC timestamp not in the future.
4. If `expiresAt` is set, it must be after `grantedAt`.
5. The consent proof is DSSE-signed; the digest is stored as `sha256:<hex>`.
## Example Payload
```json
{
"tenantId": "org-acme-production",
"grantedBy": "admin@acme.com",
"grantedAt": "2026-02-20T10:00:00Z",
"expiresAt": "2026-03-20T10:00:00Z",
"type": "stella.ops/federatedConsent@v1"
}
```

View File

@@ -0,0 +1,126 @@
# Predicate Schema: stella.ops/federatedTelemetry@v1
## Overview
This predicate type represents a privacy-preserving telemetry bundle produced by a Stella Ops instance participating in federated exploit intelligence sharing. Each bundle contains differentially private aggregated CVE observation data.
## Predicate Type
```
stella.ops/federatedTelemetry@v1
```
## Schema
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["id", "siteId", "predicateType", "aggregatedAt", "totalFacts", "suppressedBuckets", "epsilonSpent", "buckets", "consentDigest", "createdAt"],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique bundle identifier"
},
"siteId": {
"type": "string",
"description": "Identifier of the originating federation site"
},
"predicateType": {
"type": "string",
"const": "stella.ops/federatedTelemetry@v1"
},
"aggregatedAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp of the aggregation cycle"
},
"totalFacts": {
"type": "integer",
"minimum": 0,
"description": "Total number of telemetry facts processed"
},
"suppressedBuckets": {
"type": "integer",
"minimum": 0,
"description": "Number of buckets suppressed by k-anonymity or budget exhaustion"
},
"epsilonSpent": {
"type": "number",
"minimum": 0,
"description": "Total epsilon consumed in this aggregation"
},
"buckets": {
"type": "array",
"items": {
"type": "object",
"required": ["cveId", "noisyCount", "artifactCount"],
"properties": {
"cveId": {
"type": "string",
"description": "CVE identifier (e.g., CVE-2024-12345)"
},
"noisyCount": {
"type": "number",
"minimum": 0,
"description": "Observation count with Laplacian noise applied"
},
"artifactCount": {
"type": "integer",
"minimum": 0,
"description": "Number of distinct artifacts contributing to this bucket"
}
}
},
"description": "Non-suppressed aggregation buckets"
},
"consentDigest": {
"type": "string",
"description": "DSSE digest of the active consent proof at time of aggregation"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the bundle was created"
}
}
}
```
## Validation Rules
1. `id` must be a valid UUID v4.
2. `siteId` must be non-empty and match the originating site's configured identifier.
3. `epsilonSpent` must not exceed the site's total epsilon budget.
4. `consentDigest` must reference a valid, non-expired consent proof.
5. Each bucket's `artifactCount` must be >= the configured k-anonymity threshold.
6. `noisyCount` values are non-negative (noise-adjusted, may differ from true counts).
## Example Payload
```json
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"siteId": "site-production-us-east",
"predicateType": "stella.ops/federatedTelemetry@v1",
"aggregatedAt": "2026-02-20T14:30:00Z",
"totalFacts": 1547,
"suppressedBuckets": 3,
"epsilonSpent": 0.0833,
"buckets": [
{
"cveId": "CVE-2024-21626",
"noisyCount": 42.7,
"artifactCount": 12
},
{
"cveId": "CVE-2024-3094",
"noisyCount": 8.2,
"artifactCount": 6
}
],
"consentDigest": "sha256:abc123def456...",
"createdAt": "2026-02-20T14:30:05Z"
}
```

View File

@@ -0,0 +1,88 @@
# Remediation PR Predicate Schema v1
## Predicate Type
`https://stellaops.io/predicates/remediation-pr/v1`
## Purpose
Records the verification outcome of a remediation pull request, including scan delta evidence, reachability impact, and the signed fix-chain envelope. This predicate is produced at the end of the verification pipeline and attests that a specific PR either did or did not remediate the targeted CVE.
## Subject
The subject is the PR submission record, identified by its UUID:
```json
{
"subject": [
{
"name": "pr-submission",
"digest": {
"sha256": "<submission-record-digest>"
}
}
]
}
```
## Predicate Fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `cveId` | string | yes | The CVE identifier being remediated |
| `prUrl` | string | yes | URL of the pull request |
| `repositoryUrl` | string | yes | URL of the target repository |
| `sourceBranch` | string | yes | Source branch of the PR |
| `targetBranch` | string | yes | Target branch of the PR |
| `fixTemplateId` | string (UUID) | no | ID of the fix template used, if any |
| `preScanDigest` | string | no | SHA-256 digest of the pre-merge SBOM scan |
| `postScanDigest` | string | no | SHA-256 digest of the post-merge SBOM scan |
| `reachabilityDeltaDigest` | string | no | SHA-256 digest of the reachability delta report |
| `verdict` | string | yes | Verification outcome: `fixed`, `partial`, `not_fixed`, `inconclusive` |
| `affectedPaths` | string[] | no | Call graph paths affected by the fix |
| `contributorId` | string (UUID) | no | ID of the contributor who submitted the fix |
| `contributorTrustScore` | number | no | Trust score of the contributor at verification time |
| `verifiedAt` | string (ISO 8601) | yes | Timestamp of verification completion |
## Example
```json
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "pr-submission",
"digest": { "sha256": "abc123..." }
}
],
"predicateType": "https://stellaops.io/predicates/remediation-pr/v1",
"predicate": {
"cveId": "CVE-2024-1234",
"prUrl": "https://github.com/org/repo/pull/42",
"repositoryUrl": "https://github.com/org/repo",
"sourceBranch": "fix/CVE-2024-1234",
"targetBranch": "main",
"fixTemplateId": "a1b2c3d4-...",
"preScanDigest": "sha256:aaa...",
"postScanDigest": "sha256:bbb...",
"reachabilityDeltaDigest": "sha256:ccc...",
"verdict": "fixed",
"affectedPaths": [
"com.example.App -> org.vuln.Lib.method()"
],
"contributorId": "e5f6g7h8-...",
"contributorTrustScore": 0.85,
"verifiedAt": "2026-02-20T14:30:00Z"
}
}
```
## Envelope
The predicate is wrapped in a DSSE envelope and signed by the Remediation module's signing key. The `fixChainDsseDigest` on the `PrSubmission` record stores the SHA-256 digest of this envelope.
## Related
- [Remediation Architecture](../modules/remediation/architecture.md)
- [Triage Suppress Predicate](triage-suppress-v1.md)
- [Execution Evidence Contract](execution-evidence-v1.md)

View File

@@ -0,0 +1,131 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Artifact Canonical Record v1",
"description": "Unified evidence record aggregating all attestations, referrers, and VEX refs for a single artifact identified by canonical_id. See docs/contracts/artifact-canonical-record-v1.md.",
"type": "object",
"required": ["canonical_id", "format", "sbom_ref", "created_at"],
"properties": {
"canonical_id": {
"type": "string",
"description": "sha256:<hex> computed per canonical-sbom-id-v1.md",
"pattern": "^sha256:[a-f0-9]{64}$"
},
"format": {
"type": "string",
"description": "Canonicalization format identifier",
"const": "cyclonedx-jcs:1"
},
"sbom_ref": {
"type": "string",
"description": "Content-addressable reference to the SBOM (CAS URI or OCI ref)",
"examples": [
"cas://sbom/inventory/abc123.json",
"oci://registry/repo@sha256:abc123"
]
},
"attestations": {
"type": "array",
"description": "All DSSE attestations referencing this artifact",
"items": {
"type": "object",
"required": ["predicate_type", "dsse_digest", "signed_at"],
"properties": {
"predicate_type": {
"type": "string",
"description": "Predicate type URI from the predicate registry"
},
"dsse_digest": {
"type": "string",
"description": "SHA-256 of the DSSE envelope body",
"pattern": "^sha256:[a-f0-9]{64}$"
},
"signer_keyid": {
"type": "string",
"description": "Key ID of the signer"
},
"rekor_entry_id": {
"type": "string",
"description": "Rekor transparency log entry UUID (null if offline)"
},
"rekor_tile": {
"type": "string",
"description": "Rekor tile URL for inclusion proof verification"
},
"signed_at": {
"type": "string",
"format": "date-time"
}
}
}
},
"referrers": {
"type": "array",
"description": "OCI referrers (symbol bundles, attestation manifests)",
"items": {
"type": "object",
"required": ["media_type", "descriptor_digest"],
"properties": {
"media_type": {
"type": "string",
"description": "OCI media type",
"examples": [
"application/vnd.stella.symbols+tar",
"application/vnd.in-toto+json"
]
},
"descriptor_digest": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$"
},
"registry": {
"type": "string",
"description": "Registry hostname"
}
}
}
},
"vex_refs": {
"type": "array",
"description": "VEX consensus records targeting this artifact",
"items": {
"type": "object",
"required": ["vulnerability_id", "consensus_status"],
"properties": {
"vulnerability_id": {
"type": "string",
"description": "CVE or advisory ID"
},
"consensus_status": {
"type": "string",
"enum": ["affected", "not_affected", "under_investigation", "fixed"]
},
"confidence_score": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"consensus_digest": {
"type": "string",
"description": "SHA-256 of the VexLens consensus record"
},
"dsse_digest": {
"type": "string",
"description": "SHA-256 of the VEX attestation DSSE (if signed)"
},
"rekor_tile": {
"type": "string",
"description": "Rekor tile URL (if anchored)"
}
}
}
},
"created_at": {
"type": "string",
"format": "date-time"
},
"updated_at": {
"type": "string",
"format": "date-time"
}
}
}

View File

@@ -0,0 +1,60 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Triage Suppress Predicate v1",
"description": "Predicate schema for stella.ops/triageSuppress@v1. See docs/contracts/triage-suppress-v1.md.",
"type": "object",
"required": ["cve_id", "suppress_reason", "vex_consensus", "witness_evidence", "reachability_state", "timestamp"],
"properties": {
"cve_id": {
"type": "string",
"description": "CVE identifier (e.g., CVE-2025-0001)",
"pattern": "^CVE-\\d{4}-\\d{4,}$"
},
"suppress_reason": {
"type": "string",
"enum": ["vex_not_affected_with_unreachability_confirmation"],
"description": "Machine-readable reason for suppression"
},
"vex_consensus": {
"type": "object",
"required": ["status", "confidence_score", "consensus_digest", "computed_at"],
"properties": {
"status": { "type": "string", "enum": ["not_affected"] },
"justification": { "type": "string" },
"confidence_score": { "type": "number", "minimum": 0, "maximum": 1 },
"consensus_digest": { "type": "string", "description": "SHA-256 of the VexLens consensus record" },
"source_count": { "type": "integer", "description": "Number of VEX sources contributing" },
"computed_at": { "type": "string", "format": "date-time" }
}
},
"witness_evidence": {
"type": "object",
"required": ["witness_id", "dsse_digest", "observation_type"],
"properties": {
"witness_id": { "type": "string", "description": "Witness ID (wit:sha256:...)" },
"dsse_digest": { "type": "string", "description": "SHA-256 of the witness DSSE envelope" },
"observation_type": { "type": "string", "enum": ["RuntimeUnobserved", "ConfirmedUnreachable", "StaticallyUnreachable"] },
"predicate_type": { "type": "string", "description": "URI of the witness predicate type" }
}
},
"reachability_state": {
"type": "string",
"enum": ["ConfirmedUnreachable", "StaticallyUnreachable", "RuntimeUnobserved"],
"description": "Lattice state from the 8-state reachability model"
},
"timestamp": {
"type": "string",
"format": "date-time",
"description": "UTC timestamp of suppression evaluation"
},
"deterministic_replay_inputs": {
"type": "object",
"description": "Inputs sufficient to replay this suppression decision",
"properties": {
"canonical_id": { "type": "string" },
"vex_consensus_digest": { "type": "string" },
"witness_id": { "type": "string" }
}
}
}
}

View File

@@ -0,0 +1,177 @@
# Contract: Triage Auto-Suppress Predicate (v1)
## Status
- Status: DRAFT (2026-02-19)
- Owner: Signals Guild, VexLens Guild
- Sprint: SPRINT_20260219_012
## Purpose
Define the `stella.ops/triageSuppress@v1` predicate type used when a runtime micro-witness DSSE confirms that a vulnerability's `canonical_id` matches a VEX `not_affected` consensus, enabling automatic triage suppression with full auditability.
## Predicate Type URI
```
stella.ops/triageSuppress@v1
```
## When This Predicate Is Emitted
The TriageSuppressJoinService emits this predicate when ALL of the following conditions are met:
1. A micro-witness DSSE exists for `(canonical_id, cve_id)` with observation type `RuntimeUnobserved` or a path witness with lattice state `ConfirmedUnreachable` or `StaticallyUnreachable`
2. VexLens consensus for `(canonical_id, cve_id)` has status `not_affected`
3. VexLens consensus confidence score >= configured threshold (default: 0.75 for production)
## Suppression Rules Truth Table
| VEX Status | Reachability State | Action | Requires Human Review |
|---|---|---|---|
| `not_affected` | `ConfirmedUnreachable` (CU) | **AUTO-SUPPRESS** | No |
| `not_affected` | `StaticallyUnreachable` (SU) | **AUTO-SUPPRESS** | No |
| `not_affected` | `RuntimeUnobserved` (RU) | **AUTO-SUPPRESS** | No |
| `not_affected` | `Unknown` (U) | LOG ONLY | Yes |
| `not_affected` | `StaticallyReachable` (SR) | LOG ONLY | Yes |
| `not_affected` | `RuntimeObserved` (RO) | LOG ONLY, FLAG CONFLICT | Yes |
| `not_affected` | `ConfirmedReachable` (CR) | LOG ONLY, FLAG CONFLICT | Yes |
| `not_affected` | `Contested` (X) | LOG ONLY, FLAG CONFLICT | Yes |
| `affected` | any | NEVER SUPPRESS | Yes |
| `under_investigation` | any | NEVER SUPPRESS | Yes |
| `fixed` | any | NEVER SUPPRESS (separate workflow) | No |
## Predicate Schema
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Triage Suppress Predicate v1",
"type": "object",
"required": ["cve_id", "suppress_reason", "vex_consensus", "witness_evidence", "reachability_state", "timestamp"],
"properties": {
"cve_id": {
"type": "string",
"description": "CVE identifier (e.g., CVE-2025-0001)",
"pattern": "^CVE-\\d{4}-\\d{4,}$"
},
"suppress_reason": {
"type": "string",
"enum": ["vex_not_affected_with_unreachability_confirmation"],
"description": "Machine-readable reason for suppression"
},
"vex_consensus": {
"type": "object",
"required": ["status", "confidence_score", "consensus_digest", "computed_at"],
"properties": {
"status": { "type": "string", "enum": ["not_affected"] },
"justification": { "type": "string" },
"confidence_score": { "type": "number", "minimum": 0, "maximum": 1 },
"consensus_digest": { "type": "string", "description": "SHA-256 of the VexLens consensus record" },
"source_count": { "type": "integer", "description": "Number of VEX sources contributing" },
"computed_at": { "type": "string", "format": "date-time" }
}
},
"witness_evidence": {
"type": "object",
"required": ["witness_id", "dsse_digest", "observation_type"],
"properties": {
"witness_id": { "type": "string", "description": "Witness ID (wit:sha256:...)" },
"dsse_digest": { "type": "string", "description": "SHA-256 of the witness DSSE envelope" },
"observation_type": { "type": "string", "enum": ["RuntimeUnobserved", "ConfirmedUnreachable", "StaticallyUnreachable"] },
"predicate_type": { "type": "string", "description": "URI of the witness predicate type" }
}
},
"reachability_state": {
"type": "string",
"enum": ["ConfirmedUnreachable", "StaticallyUnreachable", "RuntimeUnobserved"],
"description": "Lattice state from the 8-state reachability model"
},
"timestamp": {
"type": "string",
"format": "date-time",
"description": "UTC timestamp of suppression evaluation"
},
"deterministic_replay_inputs": {
"type": "object",
"description": "Inputs sufficient to replay this suppression decision",
"properties": {
"canonical_id": { "type": "string" },
"vex_consensus_digest": { "type": "string" },
"witness_id": { "type": "string" }
}
}
}
}
```
## DSSE Envelope Shape
```json
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "artifact",
"digest": {
"sha256": "<canonical_id_hex>"
}
}
],
"predicateType": "stella.ops/triageSuppress@v1",
"predicate": {
"cve_id": "CVE-2025-0001",
"suppress_reason": "vex_not_affected_with_unreachability_confirmation",
"vex_consensus": {
"status": "not_affected",
"justification": "vulnerable_code_not_in_execute_path",
"confidence_score": 0.92,
"consensus_digest": "sha256:abc123...",
"source_count": 3,
"computed_at": "2026-02-19T12:00:00Z"
},
"witness_evidence": {
"witness_id": "wit:sha256:def456...",
"dsse_digest": "sha256:789abc...",
"observation_type": "ConfirmedUnreachable",
"predicate_type": "https://stella.ops/predicates/path-witness/v1"
},
"reachability_state": "ConfirmedUnreachable",
"timestamp": "2026-02-19T12:05:00Z",
"deterministic_replay_inputs": {
"canonical_id": "sha256:...",
"vex_consensus_digest": "sha256:abc123...",
"witness_id": "wit:sha256:def456..."
}
}
}
```
## Idempotency
Given the same `(canonical_id, cve_id, vex_consensus_digest, witness_id)`, the service MUST produce byte-identical DSSE output. This is enforced by:
- Deterministic JSON serialization (RFC 8785 / JCS)
- Deterministic timestamp: use the latest of `vex_consensus.computed_at` and witness `observed_at`
- No random or time-varying fields
## Policy Opt-In
Auto-suppress is disabled by default. Operators must enable it via policy configuration:
```json
{
"triage_suppress": {
"enabled": true,
"minimum_vex_confidence": 0.75,
"allowed_reachability_states": ["ConfirmedUnreachable", "StaticallyUnreachable", "RuntimeUnobserved"],
"require_rekor_anchor": true
}
}
```
## Audit Trail
Every suppression evaluation (whether it results in suppression or not) is logged:
- Suppressed: triageSuppress@v1 DSSE → Signer → Attestor → Rekor
- Not suppressed: audit log entry with reason (e.g., "vex_status=affected", "reachability=Contested")
## References
- SPRINT_20260219_012
- Reachability Lattice: `docs/modules/reach-graph/guides/lattice.md`
- VexLens Consensus: `docs/modules/vex-lens/architecture.md`
- Witness Contract: `docs/contracts/witness-v1.md`