- Introduced `ReachabilityState`, `RuntimeHit`, `ExploitabilitySignal`, `ReachabilitySignal`, `SignalEnvelope`, `SignalType`, `TrustSignal`, and `UnknownSymbolSignal` records to define various signal types and their properties. - Implemented JSON serialization attributes for proper data interchange. - Created project files for the new signal contracts library and corresponding test projects. - Added deterministic test fixtures for micro-interaction testing. - Included cryptographic keys for secure operations with cosign.
7.8 KiB
7.8 KiB
Competitor Ingest Fallback Hierarchy (CM6)
Status: Draft · Date: 2025-12-04 Scope: Establish fallback hierarchy when external SBOM/scan data is incomplete, with explicit decision traces.
Objectives
- Define clear fallback levels for incomplete data.
- Ensure transparent decision tracking.
- Enable policy-based confidence scoring.
- Support offline fallback evaluation.
Fallback Levels
Hierarchy Definition
Level 1: Signed SBOM with valid provenance
│
└──► Level 2: Unsigned SBOM with tool metadata
│
└──► Level 3: Scan-only results
│
└──► Level 4: Reject (no evidence)
Level Details
| Level | Source | Confidence | Requirements | Warnings |
|---|---|---|---|---|
| 1 | Signed SBOM | 1.0 | Valid signature, valid provenance | None |
| 2 | Unsigned SBOM | 0.7 | Tool metadata, component purl, scan timestamp | provenance_unknown |
| 3 | Scan-only | 0.5 | Scan timestamp | degraded_confidence, no_sbom |
| 4 | Reject | 0.0 | None met | - |
Level 1: Signed SBOM
Requirements:
- DSSE/COSE/JWS signature present
- Signature verification passes
- Signer key in trusted keyring
- Provenance metadata valid
{
"fallback": {
"level": 1,
"source": "signed_sbom",
"confidence": 1.0,
"decision": {
"reason": "Valid signature and provenance",
"checks": {
"signaturePresent": true,
"signatureValid": true,
"keyTrusted": true,
"provenanceValid": true
}
}
}
}
Level 2: Unsigned SBOM
Requirements (all must be present):
- Tool name and version
- Component list with PURLs
- At least one SHA-256 hash per component
- Scan timestamp
{
"fallback": {
"level": 2,
"source": "unsigned_sbom",
"confidence": 0.7,
"decision": {
"reason": "Valid SBOM without signature",
"checks": {
"signaturePresent": false,
"toolMetadata": true,
"componentPurls": true,
"componentHashes": true,
"scanTimestamp": true
},
"warnings": ["provenance_unknown"]
}
}
}
Level 3: Scan-only
Requirements:
- Scan timestamp present
- At least one finding or component
{
"fallback": {
"level": 3,
"source": "scan_only",
"confidence": 0.5,
"decision": {
"reason": "Scan results without SBOM",
"checks": {
"signaturePresent": false,
"toolMetadata": false,
"scanTimestamp": true,
"hasFindings": true
},
"warnings": ["degraded_confidence", "no_sbom"]
}
}
}
Level 4: Reject
When no requirements met:
{
"fallback": {
"level": 4,
"source": "reject",
"confidence": 0.0,
"decision": {
"reason": "No acceptable evidence found",
"checks": {
"signaturePresent": false,
"toolMetadata": false,
"scanTimestamp": false,
"hasFindings": false
},
"action": "reject",
"errorCode": "E2010"
}
}
}
Decision Evaluation
Evaluation Algorithm
def evaluate_fallback(input_data: dict) -> FallbackDecision:
checks = {
"signaturePresent": has_signature(input_data),
"signatureValid": False,
"keyTrusted": False,
"provenanceValid": False,
"toolMetadata": has_tool_metadata(input_data),
"componentPurls": has_component_purls(input_data),
"componentHashes": has_component_hashes(input_data),
"scanTimestamp": has_scan_timestamp(input_data),
"hasFindings": has_findings(input_data)
}
# Level 1 check
if checks["signaturePresent"]:
sig_result = verify_signature(input_data)
checks["signatureValid"] = sig_result.valid
checks["keyTrusted"] = sig_result.key_trusted
checks["provenanceValid"] = verify_provenance(input_data)
if all([checks["signatureValid"], checks["keyTrusted"], checks["provenanceValid"]]):
return FallbackDecision(level=1, confidence=1.0, checks=checks)
# Level 2 check
if all([checks["toolMetadata"], checks["componentPurls"],
checks["componentHashes"], checks["scanTimestamp"]]):
return FallbackDecision(
level=2, confidence=0.7, checks=checks,
warnings=["provenance_unknown"]
)
# Level 3 check
if checks["scanTimestamp"] and checks["hasFindings"]:
return FallbackDecision(
level=3, confidence=0.5, checks=checks,
warnings=["degraded_confidence", "no_sbom"]
)
# Level 4: Reject
return FallbackDecision(
level=4, confidence=0.0, checks=checks,
action="reject", error_code="E2010"
)
Decision Trace
Trace Format
{
"trace": {
"id": "trace-12345",
"timestamp": "2025-12-04T12:00:00Z",
"input": {
"hash": "b3:...",
"size": 12345,
"format": "cyclonedx-1.6"
},
"evaluation": {
"steps": [
{
"check": "signaturePresent",
"result": false,
"details": "No DSSE/COSE/JWS envelope found"
},
{
"check": "toolMetadata",
"result": true,
"details": "Found tool: syft v1.0.0"
},
{
"check": "componentPurls",
"result": true,
"details": "42 components with valid PURLs"
},
{
"check": "componentHashes",
"result": true,
"details": "42 components with SHA-256 hashes"
},
{
"check": "scanTimestamp",
"result": true,
"details": "Timestamp: 2025-12-04T00:00:00Z"
}
],
"decision": {
"level": 2,
"confidence": 0.7,
"warnings": ["provenance_unknown"]
}
}
}
}
Trace Persistence
Decision traces are:
- Stored with normalized output
- Included in API responses
- Available for audit queries
- Deterministic (same input = same trace)
Policy Integration
Confidence Thresholds
{
"policy": {
"minConfidence": {
"production": 0.8,
"staging": 0.5,
"development": 0.0
},
"allowedLevels": {
"production": [1],
"staging": [1, 2],
"development": [1, 2, 3]
}
}
}
Policy Evaluation
# policy/ingest/fallback.rego
package ingest.fallback
import rego.v1
default allow = false
allow if {
input.fallback.level <= max_allowed_level
input.fallback.confidence >= min_confidence
}
max_allowed_level := data.policy.allowedLevels[input.environment][_]
min_confidence := data.policy.minConfidence[input.environment]
deny contains msg if {
input.fallback.level > max_allowed_level
msg := sprintf("Fallback level %d not allowed in %s", [input.fallback.level, input.environment])
}
warn contains msg if {
warning := input.fallback.decision.warnings[_]
msg := sprintf("Fallback warning: %s", [warning])
}
Override Mechanism
Manual Override
# Accept unsigned SBOM in production (requires approval)
stellaops ingest import \
--input external-sbom.json \
--allow-unsigned \
--override-reason "Emergency import per ticket INC-12345" \
--override-approver security-admin@example.com
Override Record
{
"override": {
"enabled": true,
"level": 2,
"originalDecision": {
"level": 4,
"reason": "Would normally reject"
},
"overrideReason": "Emergency import per ticket INC-12345",
"approver": "security-admin@example.com",
"approvedAt": "2025-12-04T12:00:00Z",
"expiresAt": "2025-12-05T12:00:00Z"
}
}
Links
- Sprint:
docs/implplan/SPRINT_0186_0001_0001_record_deterministic_execution.md(CM6) - Verification:
docs/modules/scanner/design/competitor-signature-verification.md(CM2) - Normalization:
docs/modules/scanner/design/competitor-ingest-normalization.md(CM1)