178 lines
6.5 KiB
Markdown
178 lines
6.5 KiB
Markdown
# 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`
|