10 KiB
Witness Schema v1 Contract
Version:
stellaops.witness.v1
Status: Draft
Sprint:SPRINT_3700_0001_0001_witness_foundation
Overview
A witness is a cryptographically-signed proof of a reachability path from an entrypoint to a vulnerable sink. Witnesses provide:
- Auditability - Proof that a path was found at scan time
- Offline verification - Verify claims without re-running analysis
- Provenance - Links to the source graph and analysis context
- Transparency - Can be published to transparency logs
Schema Definition
PathWitness
{
"$schema": "https://stellaops.org/schemas/witness-v1.json",
"schema_version": "stellaops.witness.v1",
"witness_id": "uuid",
"witness_hash": "blake3:abcd1234...",
"witness_type": "reachability_path",
"created_at": "2025-12-18T12:00:00Z",
"provenance": {
"graph_hash": "blake3:efgh5678...",
"scan_id": "uuid",
"run_id": "uuid",
"analyzer_version": "1.0.0",
"analysis_timestamp": "2025-12-18T11:59:00Z"
},
"path": {
"entrypoint": {
"fqn": "com.example.MyController.handleRequest",
"kind": "http_handler",
"location": {
"file": "src/main/java/com/example/MyController.java",
"line": 42
}
},
"sink": {
"fqn": "org.apache.log4j.Logger.log",
"cve": "CVE-2021-44228",
"package": "pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1"
},
"steps": [
{
"index": 0,
"fqn": "com.example.MyController.handleRequest",
"call_site": "MyController.java:45",
"edge_type": "call"
},
{
"index": 1,
"fqn": "com.example.LoggingService.logMessage",
"call_site": "LoggingService.java:23",
"edge_type": "call"
},
{
"index": 2,
"fqn": "org.apache.log4j.Logger.log",
"call_site": "Logger.java:156",
"edge_type": "sink"
}
],
"hop_count": 3
},
"gates": [
{
"type": "auth_required",
"location": "MyController.java:40",
"description": "Requires authenticated user"
}
],
"evidence": {
"graph_fragment_hash": "blake3:ijkl9012...",
"path_hash": "blake3:mnop3456..."
}
}
Field Definitions
Root Fields
| Field | Type | Required | Description |
|---|---|---|---|
schema_version |
string | Yes | Must be stellaops.witness.v1 |
witness_id |
UUID | Yes | Unique identifier |
witness_hash |
string | Yes | BLAKE3 hash of canonical JSON |
witness_type |
enum | Yes | reachability_path, gate_proof |
created_at |
ISO8601 | Yes | Witness creation timestamp (UTC) |
Provenance
| Field | Type | Required | Description |
|---|---|---|---|
graph_hash |
string | Yes | BLAKE3 hash of source rich graph |
scan_id |
UUID | No | Scan that produced the graph |
run_id |
UUID | No | Analysis run identifier |
analyzer_version |
string | Yes | Analyzer version |
analysis_timestamp |
ISO8601 | Yes | When analysis was performed |
Path
| Field | Type | Required | Description |
|---|---|---|---|
entrypoint |
object | Yes | Entry point of the path |
sink |
object | Yes | Vulnerable sink at end of path |
steps |
array | Yes | Ordered list of path steps |
hop_count |
integer | Yes | Number of edges in path |
Path Step
| Field | Type | Required | Description |
|---|---|---|---|
index |
integer | Yes | Position in path (0-indexed) |
fqn |
string | Yes | Fully qualified name of node |
call_site |
string | No | Source location of call |
edge_type |
enum | Yes | call, virtual, static, sink |
Gates
Optional array of protective controls encountered along the path.
| Field | Type | Required | Description |
|---|---|---|---|
type |
enum | Yes | auth_required, feature_flag, admin_only, non_default_config |
location |
string | No | Source location of gate |
description |
string | No | Human-readable description |
Hash Computation
The witness_hash is computed as:
- Serialize the witness to canonical JSON (sorted keys, no whitespace)
- Exclude
witness_id,witness_hash, andcreated_atfields - Compute BLAKE3 hash of the canonical bytes
- Prefix with
blake3:and hex-encode
var canonical = JsonSerializer.Serialize(witness, canonicalOptions);
var hash = Blake3.Hasher.Hash(Encoding.UTF8.GetBytes(canonical));
var witnessHash = $"blake3:{Convert.ToHexString(hash.AsSpan()).ToLowerInvariant()}";
DSSE Constants
Sprint: SPRINT_3700_0001_0001 (WIT-007C)
The following constants are used for DSSE envelope creation and verification:
| Constant | Value | Location |
|---|---|---|
| Predicate Type | stella.ops/pathWitness@v1 |
PredicateTypes.StellaOpsPathWitness |
| Payload Type | application/vnd.stellaops.witness.v1+json |
WitnessSchema.DssePayloadType |
| Schema Version | stellaops.witness.v1 |
WitnessSchema.Version |
| JSON Schema URI | https://stellaops.org/schemas/witness-v1.json |
WitnessSchema.JsonSchemaUri |
Witness Types
| Value | Description |
|---|---|
reachability_path |
Path witness from entrypoint to vulnerable sink |
gate_proof |
Evidence of mitigating control (gate) along path |
Canonical Predicate Type and Aliases
Sprint: SPRINT_20260112_004_SCANNER_path_witness_nodehash
Sprint: SPRINT_20260112_008_DOCS_path_witness_contracts (PW-DOC-001)
The canonical predicate type for path witnesses is:
https://stella.ops/predicates/path-witness/v1
The following aliases are recognized for backward compatibility:
| Alias | Status |
|---|---|
stella.ops/pathWitness@v1 |
Active (legacy short form) |
https://stella.ops/pathWitness/v1 |
Active (URL variant) |
Consumers must accept all aliases when verifying; producers should emit the canonical form.
Node Hash Recipe
Canonical node hash recipe for deterministic static/runtime evidence joining.
Recipe
NodeHash = SHA256(normalize(PURL) + ":" + normalize(SYMBOL_FQN))
Output format: sha256:<64-hex-chars>
PURL Normalization Rules
- Lowercase scheme (
pkg:) - Lowercase type (e.g.,
NPM->npm) - Preserve namespace/name case (some ecosystems are case-sensitive)
- Sort qualifiers alphabetically by key
- Remove trailing slashes
- Normalize empty version to
unversioned
Symbol FQN Normalization Rules
- Trim whitespace
- Normalize multiple dots (
..) to single dot - Normalize signature whitespace:
(type,type)->(type, type) - Empty signatures become
() - Replace
_type placeholders for module-level functions
Example
Input:
PURL: pkg:npm/lodash@4.17.21
Symbol: lodash.merge(object, object)
Normalized Input:
"pkg:npm/lodash@4.17.21:lodash.merge(object, object)"
Output:
sha256:a1b2c3d4e5f6... (64 hex chars)
Implementation
See src/__Libraries/StellaOps.Reachability.Core/NodeHashRecipe.cs
Path Hash Recipe
Canonical path hash recipe for deterministic path fingerprinting.
Recipe
PathHash = SHA256(nodeHash1 + ">" + nodeHash2 + ">" + ... + nodeHashN)
The > separator represents directed edges in the path.
Top-K Selection
For efficiency, witnesses include a top-K subset of node hashes:
- Take first K/2 nodes (entry points)
- Take last K/2 nodes (exit/vulnerable points)
- Deduplicate while preserving order
- Default K = 10
PathFingerprint Fields
| Field | Type | Description |
|---|---|---|
path_hash |
string | sha256:<hex> of full path |
node_count |
integer | Total nodes in path |
top_k_node_hashes |
array | Top-K node hashes for lookup |
source_node_hash |
string | Hash of entry node |
sink_node_hash |
string | Hash of vulnerable sink |
Implementation
See src/__Libraries/StellaOps.Reachability.Core/PathHashRecipe.cs
Evidence URI Fields
Path witnesses may include URIs to supporting evidence:
| Field | Format | Description |
|---|---|---|
graph_uri |
cas://<hash> |
Content-addressed graph reference |
sbom_uri |
cas://<hash> |
SBOM used during analysis |
attestation_uri |
cas://<hash> |
DSSE envelope reference |
rekor_uri |
https://rekor.sigstore.dev/... |
Transparency log entry |
Example:
{
"evidence_uris": {
"graph": "cas://sha256:abc123...",
"sbom": "cas://sha256:def456...",
"attestation": "cas://sha256:ghi789...",
"rekor": "https://rekor.sigstore.dev/api/v1/log/entries/abc123def456"
}
}
DSSE Signing
Witnesses are signed using DSSE (Dead Simple Signing Envelope):
{
"payloadType": "application/vnd.stellaops.witness.v1+json",
"payload": "<base64url-encoded witness JSON>",
"signatures": [
{
"keyid": "sha256:abcd1234...",
"sig": "<base64url-encoded signature>"
}
]
}
Verification
- Decode the payload from base64url
- Parse as PathWitness JSON
- Recompute witness_hash and compare
- Verify signature against known public key
- Optionally check transparency log for inclusion
Storage
Witnesses are stored in scanner.witnesses table:
| Column | Type | Description |
|---|---|---|
witness_id |
UUID | Primary key |
witness_hash |
TEXT | BLAKE3 hash (unique) |
payload_json |
JSONB | Full witness JSON |
dsse_envelope |
JSONB | Signed envelope (nullable) |
graph_hash |
TEXT | Source graph reference |
sink_cve |
TEXT | CVE for quick lookup |
API Endpoints
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/witnesses/{id} |
Get witness by ID |
GET |
/api/v1/witnesses?cve={cve} |
List witnesses for CVE |
GET |
/api/v1/witnesses?scan={scanId} |
List witnesses for scan |
POST |
/api/v1/witnesses/{id}/verify |
Verify witness signature |