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

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

LevelSourceConfidenceRequirementsWarnings
1Signed SBOM1.0Valid signature, valid provenanceNone
2Unsigned SBOM0.7Tool metadata, component purl, scan timestampprovenance_unknown
3Scan-only0.5Scan timestampdegraded_confidence, no_sbom
4Reject0.0None met-

Level 1: Signed SBOM

Requirements:

{
  "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):

{
  "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:

{
  "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:

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"
  }
}