new advisories work and features gaps work
This commit is contained in:
151
docs/modules/attestor/vex-override-predicate.md
Normal file
151
docs/modules/attestor/vex-override-predicate.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# VEX Override Predicate Specification
|
||||
|
||||
## Overview
|
||||
|
||||
The VEX Override predicate provides a cryptographically signed record of operator decisions to override or annotate vulnerability assessments. This enables auditable, tamper-evident records of security triage decisions within the StellaOps vulnerability management workflow.
|
||||
|
||||
## Predicate Type URI
|
||||
|
||||
```
|
||||
https://stellaops.dev/attestations/vex-override/v1
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
1. **not_affected**: Document that a vulnerability does not affect the artifact in its deployed configuration
|
||||
2. **mitigated**: Record compensating controls that address the vulnerability
|
||||
3. **accepted**: Acknowledge an accepted risk with proper authorization
|
||||
4. **under_investigation**: Mark a vulnerability as being actively assessed
|
||||
|
||||
## Schema
|
||||
|
||||
### Required Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `artifactDigest` | string | Artifact digest this override applies to (e.g., `sha256:abc123...`) |
|
||||
| `vulnerabilityId` | string | CVE or vulnerability identifier being overridden |
|
||||
| `decision` | string/enum | One of: `not_affected`, `mitigated`, `accepted`, `under_investigation` |
|
||||
| `justification` | string | Human-readable explanation for the decision |
|
||||
| `decisionTime` | ISO 8601 | UTC timestamp when the decision was made |
|
||||
| `operatorId` | string | Identifier of the operator who made the decision |
|
||||
|
||||
### Optional Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `expiresAt` | ISO 8601 | When this override should be re-evaluated |
|
||||
| `evidenceRefs` | array | References to supporting documentation |
|
||||
| `tool` | object | Information about the tool creating the predicate |
|
||||
| `ruleDigest` | string | Digest of the policy rule that triggered evaluation |
|
||||
| `traceHash` | string | Hash of reachability analysis at decision time |
|
||||
| `metadata` | object | Additional key-value metadata |
|
||||
|
||||
### Evidence Reference Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "document|ticket|scan_report|attestation",
|
||||
"uri": "https://...",
|
||||
"digest": "sha256:...",
|
||||
"description": "Optional description"
|
||||
}
|
||||
```
|
||||
|
||||
### Tool Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "StellaOps",
|
||||
"version": "1.0.0",
|
||||
"vendor": "StellaOps Inc"
|
||||
}
|
||||
```
|
||||
|
||||
## Sample Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"artifactDigest": "sha256:a1b2c3d4e5f6...",
|
||||
"decision": "not_affected",
|
||||
"decisionTime": "2026-01-14T10:00:00Z",
|
||||
"evidenceRefs": [
|
||||
{
|
||||
"description": "Security review document",
|
||||
"digest": "sha256:def456...",
|
||||
"type": "document",
|
||||
"uri": "https://docs.example.com/security-review/123"
|
||||
}
|
||||
],
|
||||
"justification": "Component is compiled without the vulnerable code path due to build configuration",
|
||||
"operatorId": "security-team@example.com",
|
||||
"predicateType": "https://stellaops.dev/attestations/vex-override/v1",
|
||||
"ruleDigest": "sha256:rule789...",
|
||||
"tool": {
|
||||
"name": "StellaOps",
|
||||
"vendor": "StellaOps Inc",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"traceHash": "sha256:trace012...",
|
||||
"vulnerabilityId": "CVE-2024-12345"
|
||||
}
|
||||
```
|
||||
|
||||
## Canonicalization Rules
|
||||
|
||||
VEX override predicates MUST be serialized using RFC 8785 JSON Canonicalization Scheme (JCS) before signing:
|
||||
|
||||
1. **Key ordering**: All object keys are sorted lexicographically (Unicode code point order)
|
||||
2. **Number format**: No exponent notation, no leading zeros, no trailing zeros after decimal
|
||||
3. **String encoding**: Default JSON escaping (no relaxed escaping)
|
||||
4. **Whitespace**: Minified JSON (no whitespace between tokens)
|
||||
5. **Property naming**: Original snake_case field names preserved (no camelCase conversion)
|
||||
|
||||
### Serializer Configuration
|
||||
|
||||
```csharp
|
||||
// DO NOT use CamelCase naming policy
|
||||
// DO NOT use UnsafeRelaxedJsonEscaping
|
||||
// Use JsonCanonicalizer.Canonicalize() before signing
|
||||
|
||||
var canonicalJson = JsonCanonicalizer.Canonicalize(payloadJson);
|
||||
```
|
||||
|
||||
## DSSE Envelope Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"payloadType": "https://stellaops.dev/attestations/vex-override/v1",
|
||||
"payload": "<base64-encoded-canonical-predicate>",
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "key-identifier",
|
||||
"sig": "<base64-signature>"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
1. Decode the base64 payload
|
||||
2. Verify the signature using DSSE PAE (Pre-Authentication Encoding)
|
||||
3. Parse and validate the predicate schema
|
||||
4. Check expiration if `expiresAt` is present
|
||||
5. Optionally verify transparency log inclusion (Rekor anchoring)
|
||||
|
||||
## Offline Verification
|
||||
|
||||
The predicate supports offline verification when:
|
||||
- The signing certificate chain is bundled
|
||||
- Transparency log proofs are embedded
|
||||
- No network access is required for validation
|
||||
|
||||
See [Rekor Verification Design](./rekor-verification-design.md) for transparency log integration details.
|
||||
|
||||
## Related Documents
|
||||
|
||||
- [Attestor Architecture](./architecture.md)
|
||||
- [DSSE Roundtrip Verification](./dsse-roundtrip-verification.md)
|
||||
- [Transparency Logging](./transparency.md)
|
||||
- [VEX Consensus Guide](../../VEX_CONSENSUS_GUIDE.md)
|
||||
@@ -409,6 +409,143 @@ public SemanticFingerprint? SemanticFingerprint { get; init; }
|
||||
| False positive rate | <10% | <5% |
|
||||
| P95 fingerprint latency | <100ms | <50ms |
|
||||
|
||||
##### 2.2.5.7 B2R2 LowUIR Adapter
|
||||
|
||||
The B2R2LowUirLiftingService implements `IIrLiftingService` using B2R2's native lifting capabilities. This provides cross-platform IR representation for semantic analysis.
|
||||
|
||||
**Key Components:**
|
||||
|
||||
```csharp
|
||||
public sealed class B2R2LowUirLiftingService : IIrLiftingService
|
||||
{
|
||||
// Lifts to B2R2 LowUIR and maps to Stella IR model
|
||||
public Task<LiftedFunction> LiftToIrAsync(
|
||||
IReadOnlyList<DisassembledInstruction> instructions,
|
||||
string functionName,
|
||||
LiftOptions? options = null,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
```
|
||||
|
||||
**Supported ISAs:**
|
||||
- Intel (x86-32, x86-64)
|
||||
- ARM (ARMv7, ARMv8/ARM64)
|
||||
- MIPS (32/64)
|
||||
- RISC-V (64)
|
||||
- PowerPC, SPARC, SH4, AVR, EVM
|
||||
|
||||
**IR Statement Mapping:**
|
||||
| B2R2 LowUIR | Stella IR Kind |
|
||||
|-------------|----------------|
|
||||
| Put | IrStatementKind.Store |
|
||||
| Store | IrStatementKind.Store |
|
||||
| Get | IrStatementKind.Load |
|
||||
| Load | IrStatementKind.Load |
|
||||
| BinOp | IrStatementKind.BinaryOp |
|
||||
| UnOp | IrStatementKind.UnaryOp |
|
||||
| Jmp | IrStatementKind.Jump |
|
||||
| CJmp | IrStatementKind.ConditionalJump |
|
||||
| InterJmp | IrStatementKind.IndirectJump |
|
||||
| Call | IrStatementKind.Call |
|
||||
| SideEffect | IrStatementKind.SideEffect |
|
||||
|
||||
**Determinism Guarantees:**
|
||||
- Statements ordered by block address (ascending)
|
||||
- Blocks sorted by entry address (ascending)
|
||||
- Consistent IR IDs across identical inputs
|
||||
- InvariantCulture used for all string formatting
|
||||
|
||||
##### 2.2.5.8 B2R2 Lifter Pool
|
||||
|
||||
The `B2R2LifterPool` provides bounded pooling and warm preload for B2R2 lifting units to reduce per-call allocation overhead.
|
||||
|
||||
**Configuration (`B2R2LifterPoolOptions`):**
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `MaxPoolSizePerIsa` | 4 | Maximum pooled lifters per ISA |
|
||||
| `EnableWarmPreload` | true | Preload lifters at startup |
|
||||
| `WarmPreloadIsas` | ["intel-64", "intel-32", "armv8-64", "armv7-32"] | ISAs to warm |
|
||||
| `AcquireTimeout` | 5s | Timeout for acquiring a lifter |
|
||||
|
||||
**Pool Statistics:**
|
||||
- `TotalPooledLifters`: Lifters currently in pool
|
||||
- `TotalActiveLifters`: Lifters currently in use
|
||||
- `IsWarm`: Whether pool has been warmed
|
||||
- `IsaStats`: Per-ISA pool and active counts
|
||||
|
||||
**Usage:**
|
||||
```csharp
|
||||
using var lifter = _lifterPool.Acquire(isa);
|
||||
var stmts = lifter.LiftingUnit.LiftInstruction(address);
|
||||
// Lifter automatically returned to pool on dispose
|
||||
```
|
||||
|
||||
##### 2.2.5.9 Function IR Cache
|
||||
|
||||
The `FunctionIrCacheService` provides Valkey-backed caching for computed semantic fingerprints to avoid redundant IR lifting and graph hashing.
|
||||
|
||||
**Cache Key Structure:**
|
||||
```
|
||||
(isa, b2r2_version, normalization_recipe, canonical_ir_hash)
|
||||
```
|
||||
|
||||
**Configuration (`FunctionIrCacheOptions`):**
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `KeyPrefix` | "stellaops:binidx:funccache:" | Valkey key prefix |
|
||||
| `CacheTtl` | 4h | TTL for cached entries |
|
||||
| `MaxTtl` | 24h | Maximum TTL |
|
||||
| `Enabled` | true | Whether caching is enabled |
|
||||
| `B2R2Version` | "0.9.1" | B2R2 version for cache key |
|
||||
| `NormalizationRecipeVersion` | "v1" | Recipe version for cache key |
|
||||
|
||||
**Cache Entry (`CachedFunctionFingerprint`):**
|
||||
- `FunctionAddress`, `FunctionName`
|
||||
- `SemanticFingerprint`: The computed fingerprint
|
||||
- `IrStatementCount`, `BasicBlockCount`
|
||||
- `ComputedAtUtc`: ISO-8601 timestamp
|
||||
- `B2R2Version`, `NormalizationRecipe`
|
||||
|
||||
**Invalidation Rules:**
|
||||
- Cache entries expire after `CacheTtl` (default 4h)
|
||||
- Changing B2R2 version or normalization recipe results in cache misses
|
||||
- Manual invalidation via `RemoveAsync()`
|
||||
|
||||
**Statistics:**
|
||||
- Hits, Misses, Evictions
|
||||
- Hit Rate
|
||||
- Enabled status
|
||||
|
||||
##### 2.2.5.10 Ops Endpoints
|
||||
|
||||
BinaryIndex exposes operational endpoints for health, benchmarking, cache monitoring, and configuration visibility.
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/api/v1/ops/binaryindex/health` | GET | Health status with lifter warmness, cache availability |
|
||||
| `/api/v1/ops/binaryindex/bench/run` | POST | Run benchmark, return latency stats |
|
||||
| `/api/v1/ops/binaryindex/cache` | GET | Function IR cache hit/miss statistics |
|
||||
| `/api/v1/ops/binaryindex/config` | GET | Effective configuration (secrets redacted) |
|
||||
|
||||
**Health Response:**
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"timestamp": "2026-01-14T12:00:00Z",
|
||||
"lifterStatus": "warm",
|
||||
"lifterWarm": true,
|
||||
"lifterPoolStats": { "intel-64": 4, "armv8-64": 2 },
|
||||
"cacheStatus": "enabled",
|
||||
"cacheEnabled": true
|
||||
}
|
||||
```
|
||||
|
||||
**Determinism Constraints:**
|
||||
- All timestamps in ISO-8601 UTC format
|
||||
- ASCII-only output
|
||||
- Deterministic JSON key ordering
|
||||
- Secrets/credentials redacted from config endpoint
|
||||
|
||||
#### 2.2.6 Binary Vulnerability Service
|
||||
|
||||
Main query interface for consumers.
|
||||
|
||||
@@ -113,19 +113,51 @@ Semantic diffing is an advanced binary analysis capability that detects function
|
||||
|
||||
### Phase 1: IR-Level Semantic Analysis (Foundation)
|
||||
|
||||
**Sprint:** `SPRINT_20260105_001_001_BINDEX_semdiff_ir_semantics.md`
|
||||
**Sprints:**
|
||||
- `SPRINT_20260105_001_001_BINDEX_semdiff_ir_semantics.md`
|
||||
- `SPRINT_20260112_004_BINIDX_b2r2_lowuir_perf_cache.md` (Performance & Ops)
|
||||
|
||||
Leverage B2R2's Intermediate Representation (IR) for semantic-level function comparison.
|
||||
|
||||
**Key Components:**
|
||||
- `IrLiftingService` - Lift instructions to LowUIR
|
||||
- `B2R2LowUirLiftingService` - Lifts instructions to B2R2 LowUIR, maps to Stella IR model
|
||||
- `B2R2LifterPool` - Bounded pool with warm preload for lifter reuse
|
||||
- `FunctionIrCacheService` - Valkey-backed cache for semantic fingerprints
|
||||
- `SemanticGraphExtractor` - Build Key-Semantics Graph (KSG)
|
||||
- `WeisfeilerLehmanHasher` - Graph fingerprinting
|
||||
- `SemanticMatcher` - Semantic similarity scoring
|
||||
|
||||
**B2R2LowUirLiftingService Implementation:**
|
||||
- Supports Intel, ARM, MIPS, RISC-V, PowerPC, SPARC, SH4, AVR, EVM
|
||||
- Maps B2R2 LowUIR statements to `IrStatement` model
|
||||
- Applies SSA numbering to temporary registers
|
||||
- Deterministic block ordering (by entry address)
|
||||
- InvariantCulture formatting throughout
|
||||
|
||||
**B2R2LifterPool Implementation:**
|
||||
- Bounded per-ISA pooling (default 4 lifters/ISA)
|
||||
- Warm preload at startup for common ISAs
|
||||
- Per-ISA stats (pooled, active, max)
|
||||
- Automatic return on dispose
|
||||
|
||||
**FunctionIrCacheService Implementation:**
|
||||
- Cache key: `(isa, b2r2_version, normalization_recipe, canonical_ir_hash)`
|
||||
- Valkey as hot cache (default 4h TTL)
|
||||
- PostgreSQL persistence for fingerprint records
|
||||
- Hit/miss/eviction statistics
|
||||
|
||||
**Ops Endpoints:**
|
||||
- `GET /api/v1/ops/binaryindex/health` - Lifter warmness, cache status
|
||||
- `POST /api/v1/ops/binaryindex/bench/run` - Benchmark latency
|
||||
- `GET /api/v1/ops/binaryindex/cache` - Cache statistics
|
||||
- `GET /api/v1/ops/binaryindex/config` - Effective configuration
|
||||
|
||||
**Deliverables:**
|
||||
- `StellaOps.BinaryIndex.Semantic` library
|
||||
- 20 tasks, ~3 weeks
|
||||
- `StellaOps.BinaryIndex.Disassembly.B2R2` (LowUIR adapter, lifter pool)
|
||||
- `StellaOps.BinaryIndex.Cache` (function IR cache)
|
||||
- BinaryIndexOpsController
|
||||
- 20+ tasks, ~3 weeks
|
||||
|
||||
### Phase 2: Function Behavior Corpus (Scale)
|
||||
|
||||
|
||||
@@ -73,3 +73,19 @@ Filters hash: `sha256(sortedQueryString)`; stored alongside fixtures for replaya
|
||||
- Golden fixtures: `src/Findings/StellaOps.Findings.Ledger/fixtures/golden/*.ndjson`.
|
||||
- Checksum manifest: `docs/modules/findings-ledger/golden-checksums.json`.
|
||||
- Offline verifier: `tools/LedgerReplayHarness/scripts/verify_export.py`.
|
||||
|
||||
## 6) Rekor Entry Reference — `rekor.entry.ref.v1` (Sprint: SPRINT_20260112_004_FINDINGS)
|
||||
|
||||
| Field | Type | Notes |
|
||||
| --- | --- | --- |
|
||||
| `logIndex` | `long?` | Position in the Rekor log. |
|
||||
| `logId` | `string?` | Log identifier (hex-encoded public key hash). |
|
||||
| `uuid` | `string?` | Unique entry identifier. |
|
||||
| `integratedTime` | `long?` | Unix epoch seconds when entry was integrated. |
|
||||
| `integratedTimeRfc3339` | `string?` (UTC ISO-8601) | RFC3339 formatted integrated time for display/sorting. |
|
||||
| `entryUrl` | `string?` | Full URL to the Rekor entry for UI linking (e.g., `https://rekor.sigstore.dev/api/v1/log/entries/{uuid}`). |
|
||||
|
||||
**Usage:** Attached to `AttestationPointer` records and evidence graph signature metadata. The `integratedTimeRfc3339` field provides human-readable timestamps and deterministic sorting. The `entryUrl` enables direct linking from UI components.
|
||||
|
||||
**Offline mode:** When operating in air-gapped environments, `entryUrl` may be null or point to a local Rekor mirror. The `integratedTime` remains authoritative for timestamp verification.
|
||||
|
||||
|
||||
@@ -465,8 +465,113 @@ PolicyEngine:
|
||||
|
||||
---
|
||||
|
||||
## 11. Node Hash and Path Gating Extensions
|
||||
|
||||
Sprint: SPRINT_20260112_008_DOCS_path_witness_contracts (PW-DOC-004)
|
||||
|
||||
### 11.1 Extended ReachabilityInput Fields
|
||||
|
||||
The following fields extend `ReachabilityInput` for path-level gating:
|
||||
|
||||
```csharp
|
||||
public sealed record ReachabilityInput
|
||||
{
|
||||
// ... existing fields ...
|
||||
|
||||
/// <summary>Canonical path hash computed from entry to sink.</summary>
|
||||
public string? PathHash { get; init; }
|
||||
|
||||
/// <summary>Top-K node hashes along the path.</summary>
|
||||
public ImmutableArray<string> NodeHashes { get; init; }
|
||||
|
||||
/// <summary>Entry point node hash.</summary>
|
||||
public string? EntryNodeHash { get; init; }
|
||||
|
||||
/// <summary>Sink (vulnerable symbol) node hash.</summary>
|
||||
public string? SinkNodeHash { get; init; }
|
||||
|
||||
/// <summary>When runtime evidence was observed (UTC).</summary>
|
||||
public DateTimeOffset? RuntimeEvidenceAt { get; init; }
|
||||
|
||||
/// <summary>Whether path was observed at runtime.</summary>
|
||||
public bool ObservedAtRuntime { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
### 11.2 Node Hash Computation
|
||||
|
||||
Node hashes are computed using the canonical recipe:
|
||||
|
||||
```
|
||||
nodeHash = SHA256(normalize(purl) + ":" + normalize(symbol))
|
||||
```
|
||||
|
||||
See `docs/contracts/witness-v1.md` for normalization rules.
|
||||
|
||||
### 11.3 Policy DSL Access
|
||||
|
||||
The following fields are exposed in policy evaluation context:
|
||||
|
||||
| DSL Path | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `reachability.pathHash` | string | Canonical path hash |
|
||||
| `reachability.nodeHashes` | array | Top-K node hashes |
|
||||
| `reachability.entryNodeHash` | string | Entry point node hash |
|
||||
| `reachability.sinkNodeHash` | string | Sink node hash |
|
||||
| `reachability.runtimeEvidenceAt` | datetime | Runtime observation timestamp |
|
||||
| `reachability.observedAtRuntime` | boolean | Whether confirmed at runtime |
|
||||
| `reachability.runtimeEvidenceAge` | duration | Age of runtime evidence |
|
||||
|
||||
### 11.4 Path Gating Examples
|
||||
|
||||
Block paths confirmed at runtime:
|
||||
|
||||
```yaml
|
||||
match:
|
||||
reachability:
|
||||
pathHash:
|
||||
exists: true
|
||||
observedAtRuntime: true
|
||||
action: block
|
||||
```
|
||||
|
||||
Require fresh runtime evidence:
|
||||
|
||||
```yaml
|
||||
match:
|
||||
reachability:
|
||||
runtimeEvidenceAge:
|
||||
gt: 24h
|
||||
action: warn
|
||||
message: "Runtime evidence is stale"
|
||||
```
|
||||
|
||||
Block specific node patterns:
|
||||
|
||||
```yaml
|
||||
match:
|
||||
reachability:
|
||||
nodeHashes:
|
||||
contains_any:
|
||||
- "sha256:critical-auth-node..."
|
||||
action: block
|
||||
```
|
||||
|
||||
### 11.5 Runtime Evidence Freshness
|
||||
|
||||
Runtime evidence age is computed as:
|
||||
|
||||
```
|
||||
runtimeEvidenceAge = now() - runtimeEvidenceAt
|
||||
```
|
||||
|
||||
Freshness thresholds can be configured per environment in `DeterminizationOptions`.
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
| Version | Date | Changes |
|
||||
|---------|------|---------|
|
||||
| 1.1.0 | 2026-01-14 | Added node hash, path gating, and runtime evidence fields (SPRINT_20260112_008) |
|
||||
| 1.0.0 | 2025-12-19 | Initial release |
|
||||
|
||||
@@ -367,7 +367,126 @@ The Policy Engine reads uncertainty gate thresholds from configuration:
|
||||
|
||||
---
|
||||
|
||||
## 13 · Versioning & Compatibility
|
||||
## 13 · Signed Override Enforcement (Sprint 20260112.004)
|
||||
|
||||
Signed VEX overrides provide cryptographic assurance that operator decisions (not_affected, compensating controls) are authentic and auditable. The Policy Engine exposes override signature status to DSL rules for enforcement.
|
||||
|
||||
### 13.1 Override Signal Namespace
|
||||
|
||||
Within predicates and actions you may reference the following override signals:
|
||||
|
||||
| Signal | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `override.signed` | `bool` | `true` when the VEX override has a valid DSSE signature. |
|
||||
| `override.rekor_verified` | `bool` | `true` when the override signature is anchored in Rekor transparency log. |
|
||||
| `override.signing_key_id` | `string` | Key identifier used to sign the override. |
|
||||
| `override.signer_identity` | `string` | Identity (email, OIDC subject) of the signer. |
|
||||
| `override.envelope_digest` | `string` | SHA-256 digest of the DSSE envelope. |
|
||||
| `override.rekor_log_index` | `int?` | Rekor log index if anchored; `null` otherwise. |
|
||||
| `override.rekor_integrated_time` | `datetime?` | Timestamp when anchored in Rekor. |
|
||||
| `override.valid_from` | `datetime?` | Override validity window start (if specified). |
|
||||
| `override.valid_until` | `datetime?` | Override validity window end (if specified). |
|
||||
| `override.within_validity_period` | `bool` | `true` when current time is within validity window (or no window specified). |
|
||||
| `override.key_trust_level` | `string` | Trust level: `Unknown`, `LowTrust`, `OrganizationTrusted`, `HighlyTrusted`. |
|
||||
|
||||
### 13.2 Enforcement Rules
|
||||
|
||||
#### 13.2.1 Require Signed Overrides
|
||||
|
||||
Block unsigned VEX overrides from being accepted:
|
||||
|
||||
```dsl
|
||||
rule require_signed_overrides priority 1 {
|
||||
when vex.any(status in ["not_affected", "fixed"])
|
||||
and not override.signed
|
||||
then status := "under_investigation"
|
||||
annotate override_blocked := "Unsigned override rejected"
|
||||
because "Production environments require signed VEX overrides";
|
||||
}
|
||||
```
|
||||
|
||||
#### 13.2.2 Require Rekor Anchoring for Critical Assets
|
||||
|
||||
For critical assets, require transparency log anchoring:
|
||||
|
||||
```dsl
|
||||
rule require_rekor_for_critical priority 2 {
|
||||
when env.asset_tier == "critical"
|
||||
and vex.any(status == "not_affected")
|
||||
and override.signed
|
||||
and not override.rekor_verified
|
||||
then status := "under_investigation"
|
||||
warn message "Critical asset requires Rekor-anchored override"
|
||||
because "Critical assets require transparency log verification";
|
||||
}
|
||||
```
|
||||
|
||||
#### 13.2.3 Trust Level Gating
|
||||
|
||||
Gate override acceptance based on signer trust level:
|
||||
|
||||
```dsl
|
||||
rule gate_by_trust_level priority 5 {
|
||||
when override.signed
|
||||
and override.key_trust_level in ["Unknown", "LowTrust"]
|
||||
and env.security_posture == "strict"
|
||||
then status := "under_investigation"
|
||||
annotate trust_gate_failed := override.signer_identity
|
||||
because "Strict posture requires OrganizationTrusted or higher";
|
||||
}
|
||||
```
|
||||
|
||||
#### 13.2.4 Validity Period Enforcement
|
||||
|
||||
Reject expired or not-yet-valid overrides:
|
||||
|
||||
```dsl
|
||||
rule enforce_validity_period priority 3 {
|
||||
when override.signed
|
||||
and exists(override.valid_until)
|
||||
and not override.within_validity_period
|
||||
then status := "affected"
|
||||
annotate override_expired := override.valid_until
|
||||
because "VEX override has expired or is not yet valid";
|
||||
}
|
||||
```
|
||||
|
||||
### 13.3 Default Enforcement Profile
|
||||
|
||||
The default enforcement profile blocks unsigned overrides in production:
|
||||
|
||||
```dsl
|
||||
settings {
|
||||
require_signed_overrides = true;
|
||||
require_rekor_for_production = false;
|
||||
minimum_trust_level = "OrganizationTrusted";
|
||||
enforce_validity_period = true;
|
||||
}
|
||||
```
|
||||
|
||||
Override these settings in environment-specific policy packs.
|
||||
|
||||
### 13.4 Offline Mode Considerations
|
||||
|
||||
In sealed/offline deployments:
|
||||
|
||||
- `override.rekor_verified` evaluates to `false` (no network access to verify).
|
||||
- Use embedded proofs in the DSSE envelope for signature verification.
|
||||
- Policies should fall back to signature verification without requiring Rekor:
|
||||
|
||||
```dsl
|
||||
rule offline_safe_override priority 5 {
|
||||
when env.sealed_mode == true
|
||||
and override.signed
|
||||
and override.key_trust_level in ["OrganizationTrusted", "HighlyTrusted"]
|
||||
then status := vex.status
|
||||
because "Offline mode accepts signed overrides from trusted keys without Rekor";
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14 · Versioning & Compatibility
|
||||
|
||||
- `syntax "stella-dsl@1"` is mandatory.
|
||||
- Future revisions (`@2`, …) will be additive; existing packs continue to compile with their declared version.
|
||||
@@ -375,7 +494,7 @@ The Policy Engine reads uncertainty gate thresholds from configuration:
|
||||
|
||||
---
|
||||
|
||||
## 14 · Compliance Checklist
|
||||
## 15 · Compliance Checklist
|
||||
|
||||
- [ ] **Grammar validated:** Policy compiles with `stella policy lint` and matches `syntax "stella-dsl@1"`.
|
||||
- [ ] **Deterministic constructs only:** No use of forbidden namespaces (`DateTime.Now`, `Guid.NewGuid`, external services).
|
||||
|
||||
@@ -42,6 +42,121 @@
|
||||
- Ensure `analysisId` is propagated from Scanner/Zastava into Signals ingest to keep replay manifests linked.
|
||||
- Keep feeds frozen for reproducibility; avoid external downloads in union preparation.
|
||||
|
||||
---
|
||||
|
||||
## Node Hash Joins and Runtime Evidence Linkage
|
||||
|
||||
Sprint: SPRINT_20260112_008_DOCS_path_witness_contracts (PW-DOC-002)
|
||||
|
||||
### Overview
|
||||
|
||||
Node hashes provide a canonical way to join static reachability analysis with runtime observations. Each node in a callgraph can be identified by a stable hash computed from its PURL and symbol information, enabling:
|
||||
|
||||
1. **Static-to-runtime correlation**: Match runtime stack traces to static callgraph nodes
|
||||
2. **Cross-scan consistency**: Compare reachability across different analysis runs
|
||||
3. **Evidence linking**: Associate attestations with specific code paths
|
||||
|
||||
### Node Hash Recipe
|
||||
|
||||
A node hash is computed as:
|
||||
|
||||
```
|
||||
nodeHash = SHA256(normalize(purl) + ":" + normalize(symbol))
|
||||
```
|
||||
|
||||
Where:
|
||||
- `normalize(purl)` lowercases the PURL and sorts qualifiers alphabetically
|
||||
- `normalize(symbol)` removes whitespace and normalizes platform-specific decorations
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"purl": "pkg:npm/express@4.18.2",
|
||||
"symbol": "Router.handle",
|
||||
"nodeHash": "sha256:a1b2c3d4..."
|
||||
}
|
||||
```
|
||||
|
||||
### Path Hash and Top-K Selection
|
||||
|
||||
A path hash identifies a specific call path from entrypoint to sink:
|
||||
|
||||
```
|
||||
pathHash = SHA256(entryNodeHash + ":" + joinedIntermediateHashes + ":" + sinkNodeHash)
|
||||
```
|
||||
|
||||
For long paths, only the **top-K** most significant nodes are included (default K=10):
|
||||
- Entry node (always included)
|
||||
- Sink node (always included)
|
||||
- Intermediate nodes ranked by call frequency or security relevance
|
||||
|
||||
### Runtime Evidence Linkage
|
||||
|
||||
Runtime observations from Zastava can be linked to static analysis using node hashes:
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `observedNodeHashes` | Node hashes seen at runtime |
|
||||
| `observedPathHashes` | Path hashes confirmed by runtime traces |
|
||||
| `runtimeEvidenceAt` | Timestamp of runtime observation (RFC3339) |
|
||||
| `callstackHash` | Hash of the observed call stack |
|
||||
|
||||
### Join Example
|
||||
|
||||
To correlate static reachability with runtime evidence:
|
||||
|
||||
```sql
|
||||
-- Find statically-reachable vulnerabilities confirmed at runtime
|
||||
SELECT
|
||||
s.vulnerability_id,
|
||||
s.path_hash,
|
||||
r.observed_at
|
||||
FROM static_reachability s
|
||||
JOIN runtime_observations r
|
||||
ON s.sink_node_hash = ANY(r.observed_node_hashes)
|
||||
WHERE s.reachable = true
|
||||
AND r.observed_at > NOW() - INTERVAL '7 days';
|
||||
```
|
||||
|
||||
### SARIF Integration
|
||||
|
||||
Node hashes are exposed in SARIF outputs via `stellaops/*` property keys:
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [{
|
||||
"ruleId": "CVE-2024-1234",
|
||||
"properties": {
|
||||
"stellaops/nodeHash": "sha256:abc123...",
|
||||
"stellaops/pathHash": "sha256:def456...",
|
||||
"stellaops/topKNodeHashes": ["sha256:...", "sha256:..."],
|
||||
"stellaops/evidenceUri": "cas://evidence/...",
|
||||
"stellaops/observedAtRuntime": true
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### Policy Gate Usage
|
||||
|
||||
Policy rules can reference node and path hashes for fine-grained control:
|
||||
|
||||
```yaml
|
||||
rules:
|
||||
- name: block-confirmed-critical-path
|
||||
match:
|
||||
severity: CRITICAL
|
||||
reachability:
|
||||
pathHash:
|
||||
exists: true
|
||||
observedAtRuntime: true
|
||||
action: block
|
||||
```
|
||||
|
||||
See `policies/path-gates-advanced.yaml` for comprehensive examples.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
- Schema: `docs/modules/reach-graph/schemas/runtime-static-union-schema.md`
|
||||
- Delivery guide: `docs/modules/reach-graph/guides/DELIVERY_GUIDE.md`
|
||||
|
||||
Reference in New Issue
Block a user