doctor enhancements, setup, enhancements, ui functionality and design consolidation and , test projects fixes , product advisory attestation/rekor and delta verfications enhancements

This commit is contained in:
master
2026-01-19 09:02:59 +02:00
parent 8c4bf54aed
commit 17419ba7c4
809 changed files with 170738 additions and 12244 deletions

View File

@@ -0,0 +1,149 @@
# OPA/Rego Policy Examples for CVE Gating
This directory contains Open Policy Agent (OPA) Rego policies for CVE-aware release gating. These policies can be used alongside or instead of the Stella DSL for advanced policy scenarios.
## Quick Start
```bash
# Install OPA
brew install opa # macOS
# or download from https://www.openpolicyagent.org/docs/latest/#running-opa
# Run all tests
opa test . -v
# Evaluate a policy
opa eval -d epss-threshold.rego -i sample-input.json "data.stellaops.gates.epss.allow"
```
## Available Policies
| Policy | Description |
|--------|-------------|
| [cve-gate-base.rego](cve-gate-base.rego) | Base policy with DSSE signature and Rekor anchor verification |
| [epss-threshold.rego](epss-threshold.rego) | EPSS exploitation probability threshold enforcement |
| [kev-blocker.rego](kev-blocker.rego) | CISA KEV catalog blocking |
| [reachable-cve.rego](reachable-cve.rego) | Reachability-aware CVE blocking |
| [release-aggregate.rego](release-aggregate.rego) | Aggregate CVE count limits per release |
## Input Schema
All policies expect input conforming to `input-schema.json`. Key fields:
```json
{
"attestation": {
"dsse_envelope": { ... },
"rekor_entry": { ... }
},
"cve_findings": [
{
"cve_id": "CVE-2024-1234",
"cvss_score": 7.5,
"epss_score": 0.42,
"is_kev": false,
"is_reachable": true
}
],
"environment": "production",
"config": {
"epss_threshold": 0.6,
"max_critical": 0,
"max_high": 3
}
}
```
See [input-schema.json](input-schema.json) for full schema documentation.
## Policy Composition
Policies can be combined using OPA's standard composition:
```rego
package stellaops.gates.combined
import data.stellaops.gates.base
import data.stellaops.gates.epss
import data.stellaops.gates.kev
import data.stellaops.gates.reachable
# All gates must pass
default allow = false
allow {
base.valid_attestation
epss.allow
kev.allow
reachable.allow
}
# Collect all denial reasons
deny[msg] {
not base.valid_attestation
msg := base.deny[_]
}
deny[msg] {
not epss.allow
msg := epss.deny[_]
}
deny[msg] {
not kev.allow
msg := kev.deny[_]
}
deny[msg] {
not reachable.allow
msg := reachable.deny[_]
}
```
## Integration with Stella
These policies can be executed via the Stella CLI:
```bash
# Evaluate OPA policy against release candidate
stella policy evaluate --engine opa --policy examples/policies/opa/epss-threshold.rego --image myapp:v1.2.3
# Evaluate multiple policies
stella policy evaluate --engine opa --bundle examples/policies/opa/ --image myapp:v1.2.3
```
## Testing
Each policy has corresponding test files (`*_test.rego`). Run tests with:
```bash
# All tests
opa test . -v
# Specific policy tests
opa test epss-threshold.rego epss-threshold_test.rego -v
```
## Configuration
Policy configuration is passed via `input.config`. Environment-specific overrides are supported:
```json
{
"config": {
"epss_threshold": 0.6,
"environments": {
"production": {
"epss_threshold": 0.3
},
"staging": {
"epss_threshold": 0.7
}
}
}
}
```
---
*Last updated: 2026-01-19.*

View File

@@ -0,0 +1,99 @@
# -----------------------------------------------------------------------------
# cve-gate-base.rego
# Sprint: SPRINT_20260118_027_Policy_cve_release_gates
# Task: TASK-027-08 - OPA/Rego Policy Examples
# Description: Base policy for DSSE signature and Rekor anchor verification
# -----------------------------------------------------------------------------
package stellaops.gates.base
import future.keywords.if
import future.keywords.in
# Default deny - require explicit allow
default valid_attestation = false
# Attestation is valid if DSSE envelope has valid signature from trusted key
valid_attestation if {
valid_dsse_envelope
valid_signature
valid_rekor_anchor
}
# Allow without Rekor if not required
valid_attestation if {
valid_dsse_envelope
valid_signature
not config_require_rekor
}
# DSSE envelope structure validation
valid_dsse_envelope if {
input.attestation.dsse_envelope.payloadType
input.attestation.dsse_envelope.payload
count(input.attestation.dsse_envelope.signatures) > 0
}
# Signature validation - at least one signature from trusted key
valid_signature if {
some sig in input.attestation.dsse_envelope.signatures
sig.keyid in trusted_keys
sig.sig != ""
}
# Rekor anchor validation
valid_rekor_anchor if {
input.attestation.rekor_entry.log_index >= 0
input.attestation.rekor_entry.integrated_time > 0
input.attestation.rekor_entry.inclusion_proof.root_hash != ""
}
# Configuration helpers
config_require_rekor if {
input.config.require_rekor == true
}
# Get trusted keys from input or use default
trusted_keys := input.attestation.trusted_keys if {
input.attestation.trusted_keys
} else := []
# Denial messages
deny[msg] if {
not input.attestation.dsse_envelope
msg := "Missing DSSE envelope in attestation"
}
deny[msg] if {
input.attestation.dsse_envelope
not valid_dsse_envelope
msg := "Invalid DSSE envelope structure"
}
deny[msg] if {
valid_dsse_envelope
not valid_signature
msg := "No valid signature from trusted key"
}
deny[msg] if {
config_require_rekor
not input.attestation.rekor_entry
msg := "Rekor anchor required but not present"
}
deny[msg] if {
config_require_rekor
input.attestation.rekor_entry
not valid_rekor_anchor
msg := "Invalid Rekor inclusion proof"
}
# Metadata for debugging
attestation_info := {
"has_dsse": valid_dsse_envelope,
"has_valid_sig": valid_signature,
"has_rekor": valid_rekor_anchor,
"signature_count": count(input.attestation.dsse_envelope.signatures),
"trusted_key_count": count(trusted_keys),
}

View File

@@ -0,0 +1,103 @@
# -----------------------------------------------------------------------------
# cve-gate-base_test.rego
# Tests for base attestation verification policy
# -----------------------------------------------------------------------------
package stellaops.gates.base
import future.keywords.if
# Test valid attestation with DSSE and Rekor
test_valid_attestation_with_rekor if {
valid_attestation with input as {
"attestation": {
"dsse_envelope": {
"payloadType": "application/vnd.in-toto+json",
"payload": "eyJzdWJqZWN0IjpbXX0=",
"signatures": [{"keyid": "key-1", "sig": "abc123"}]
},
"rekor_entry": {
"log_index": 12345,
"integrated_time": 1705689600,
"inclusion_proof": {"root_hash": "abc", "tree_size": 100, "hashes": []}
},
"trusted_keys": ["key-1"]
},
"config": {"require_rekor": true}
}
}
# Test valid attestation without Rekor when not required
test_valid_attestation_no_rekor_not_required if {
valid_attestation with input as {
"attestation": {
"dsse_envelope": {
"payloadType": "application/vnd.in-toto+json",
"payload": "eyJzdWJqZWN0IjpbXX0=",
"signatures": [{"keyid": "key-1", "sig": "abc123"}]
},
"trusted_keys": ["key-1"]
},
"config": {"require_rekor": false}
}
}
# Test invalid - missing DSSE envelope
test_invalid_missing_dsse if {
not valid_attestation with input as {
"attestation": {},
"config": {}
}
}
# Test invalid - untrusted key
test_invalid_untrusted_key if {
not valid_attestation with input as {
"attestation": {
"dsse_envelope": {
"payloadType": "application/vnd.in-toto+json",
"payload": "eyJzdWJqZWN0IjpbXX0=",
"signatures": [{"keyid": "untrusted-key", "sig": "abc123"}]
},
"trusted_keys": ["key-1"]
},
"config": {}
}
}
# Test invalid - Rekor required but missing
test_invalid_rekor_required_but_missing if {
not valid_attestation with input as {
"attestation": {
"dsse_envelope": {
"payloadType": "application/vnd.in-toto+json",
"payload": "eyJzdWJqZWN0IjpbXX0=",
"signatures": [{"keyid": "key-1", "sig": "abc123"}]
},
"trusted_keys": ["key-1"]
},
"config": {"require_rekor": true}
}
}
# Test denial messages
test_deny_missing_dsse if {
"Missing DSSE envelope in attestation" in deny with input as {
"attestation": {},
"config": {}
}
}
test_deny_no_valid_signature if {
"No valid signature from trusted key" in deny with input as {
"attestation": {
"dsse_envelope": {
"payloadType": "application/vnd.in-toto+json",
"payload": "eyJzdWJqZWN0IjpbXX0=",
"signatures": [{"keyid": "bad-key", "sig": "abc123"}]
},
"trusted_keys": ["key-1"]
},
"config": {}
}
}

View File

@@ -0,0 +1,70 @@
# -----------------------------------------------------------------------------
# epss-threshold.rego
# Sprint: SPRINT_20260118_027_Policy_cve_release_gates
# Task: TASK-027-08 - OPA/Rego Policy Examples
# Description: EPSS exploitation probability threshold enforcement
# -----------------------------------------------------------------------------
package stellaops.gates.epss
import future.keywords.if
import future.keywords.in
# Default allow if no CVEs exceed threshold
default allow = true
# Block if any CVE exceeds EPSS threshold
allow = false if {
some cve in relevant_cves
cve.epss_score > epss_threshold
}
# Get CVEs to evaluate (optionally filtered by reachability)
relevant_cves := [cve |
some cve in input.cve_findings
config_only_reachable
cve.is_reachable == true
]
relevant_cves := input.cve_findings if {
not config_only_reachable
}
# Get threshold with environment override support
epss_threshold := env_config.epss_threshold if {
env_config := input.config.environments[input.environment]
env_config.epss_threshold
} else := input.config.epss_threshold if {
input.config.epss_threshold
} else := 0.6 # Default threshold
# Configuration flags
config_only_reachable if {
input.config.only_reachable == true
}
# Denial messages with CVE details
deny[msg] if {
some cve in relevant_cves
cve.epss_score > epss_threshold
msg := sprintf("CVE %s exceeds EPSS threshold: %.2f > %.2f", [
cve.cve_id,
cve.epss_score,
epss_threshold
])
}
# Count CVEs exceeding threshold
exceeding_cves := [cve |
some cve in relevant_cves
cve.epss_score > epss_threshold
]
# Summary for reporting
summary := {
"total_cves": count(relevant_cves),
"exceeding_count": count(exceeding_cves),
"threshold": epss_threshold,
"environment": input.environment,
"exceeding_cves": [cve.cve_id | some cve in exceeding_cves],
}

View File

@@ -0,0 +1,93 @@
# -----------------------------------------------------------------------------
# epss-threshold_test.rego
# Tests for EPSS threshold policy
# -----------------------------------------------------------------------------
package stellaops.gates.epss
import future.keywords.if
# Test allow - all CVEs below threshold
test_allow_below_threshold if {
allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "epss_score": 0.3},
{"cve_id": "CVE-2024-0002", "epss_score": 0.5}
],
"config": {"epss_threshold": 0.6}
}
}
# Test deny - CVE above threshold
test_deny_above_threshold if {
not allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "epss_score": 0.3},
{"cve_id": "CVE-2024-0002", "epss_score": 0.7}
],
"config": {"epss_threshold": 0.6}
}
}
# Test allow - empty findings
test_allow_empty_findings if {
allow with input as {
"cve_findings": [],
"config": {"epss_threshold": 0.6}
}
}
# Test environment override
test_environment_override if {
not allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "epss_score": 0.4}
],
"environment": "production",
"config": {
"epss_threshold": 0.6,
"environments": {
"production": {"epss_threshold": 0.3}
}
}
}
}
# Test only_reachable filter
test_only_reachable_filters_unreachable if {
allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "epss_score": 0.8, "is_reachable": false},
{"cve_id": "CVE-2024-0002", "epss_score": 0.3, "is_reachable": true}
],
"config": {"epss_threshold": 0.6, "only_reachable": true}
}
}
# Test denial message content
test_deny_message_content if {
msg := deny[_] with input as {
"cve_findings": [
{"cve_id": "CVE-2024-1234", "epss_score": 0.72}
],
"config": {"epss_threshold": 0.6}
}
contains(msg, "CVE-2024-1234")
contains(msg, "0.72")
}
# Test summary output
test_summary_structure if {
s := summary with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "epss_score": 0.3},
{"cve_id": "CVE-2024-0002", "epss_score": 0.7}
],
"environment": "staging",
"config": {"epss_threshold": 0.6}
}
s.total_cves == 2
s.exceeding_count == 1
s.threshold == 0.6
s.environment == "staging"
}

View File

@@ -0,0 +1,240 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://stellaops.io/schemas/opa/policy-input.json",
"title": "Stella OPA Policy Input Schema",
"description": "Input schema for OPA/Rego CVE gating policies",
"type": "object",
"required": ["attestation", "cve_findings", "environment"],
"properties": {
"attestation": {
"type": "object",
"description": "Attestation data including DSSE envelope and Rekor entry",
"required": ["dsse_envelope"],
"properties": {
"dsse_envelope": {
"type": "object",
"description": "DSSE envelope containing signed statement",
"required": ["payloadType", "payload", "signatures"],
"properties": {
"payloadType": {
"type": "string",
"description": "DSSE payload type URI",
"examples": ["application/vnd.in-toto+json"]
},
"payload": {
"type": "string",
"description": "Base64-encoded payload (in-toto statement)"
},
"signatures": {
"type": "array",
"items": {
"type": "object",
"required": ["keyid", "sig"],
"properties": {
"keyid": {
"type": "string",
"description": "Key identifier"
},
"sig": {
"type": "string",
"description": "Base64-encoded signature"
}
}
}
}
}
},
"rekor_entry": {
"type": "object",
"description": "Rekor transparency log entry (optional)",
"properties": {
"log_index": {
"type": "integer",
"description": "Rekor log index"
},
"log_id": {
"type": "string",
"description": "Rekor log ID (base64 SHA256)"
},
"integrated_time": {
"type": "integer",
"description": "Unix timestamp of log inclusion"
},
"inclusion_proof": {
"type": "object",
"properties": {
"root_hash": { "type": "string" },
"tree_size": { "type": "integer" },
"hashes": {
"type": "array",
"items": { "type": "string" }
}
}
}
}
},
"trusted_keys": {
"type": "array",
"description": "List of trusted signing key IDs",
"items": { "type": "string" }
}
}
},
"cve_findings": {
"type": "array",
"description": "CVE findings from scan results",
"items": {
"type": "object",
"required": ["cve_id"],
"properties": {
"cve_id": {
"type": "string",
"pattern": "^CVE-\\d{4}-\\d{4,}$",
"description": "CVE identifier"
},
"cvss_score": {
"type": "number",
"minimum": 0,
"maximum": 10,
"description": "CVSS v3 base score"
},
"severity": {
"type": "string",
"enum": ["critical", "high", "medium", "low", "unknown"],
"description": "Severity classification"
},
"epss_score": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "EPSS exploitation probability (0-1)"
},
"epss_percentile": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "EPSS percentile (0-100)"
},
"is_kev": {
"type": "boolean",
"description": "Whether CVE is in CISA KEV catalog"
},
"kev_due_date": {
"type": "string",
"format": "date",
"description": "KEV remediation due date (YYYY-MM-DD)"
},
"is_reachable": {
"type": "boolean",
"description": "Whether vulnerable code is reachable"
},
"reachability_state": {
"type": "string",
"enum": ["confirmed_reachable", "runtime_observed", "statically_reachable", "not_reachable", "unknown"],
"description": "Detailed reachability state"
},
"is_suppressed": {
"type": "boolean",
"description": "Whether CVE is suppressed/excepted"
},
"package_name": {
"type": "string",
"description": "Affected package name"
},
"package_version": {
"type": "string",
"description": "Affected package version"
},
"fix_available": {
"type": "boolean",
"description": "Whether a fix is available"
},
"fixed_version": {
"type": "string",
"description": "Version containing the fix"
}
}
}
},
"baseline_cve_findings": {
"type": "array",
"description": "CVE findings from baseline release (for delta comparison)",
"items": { "$ref": "#/properties/cve_findings/items" }
},
"environment": {
"type": "string",
"description": "Target deployment environment",
"examples": ["development", "staging", "production"]
},
"release": {
"type": "object",
"description": "Release metadata",
"properties": {
"id": { "type": "string" },
"version": { "type": "string" },
"image_digest": { "type": "string" },
"baseline_digest": { "type": "string" }
}
},
"config": {
"type": "object",
"description": "Policy configuration",
"properties": {
"epss_threshold": {
"type": "number",
"description": "EPSS score threshold (0-1)"
},
"epss_percentile_threshold": {
"type": "number",
"description": "EPSS percentile threshold (0-100)"
},
"severity_threshold": {
"type": "number",
"description": "CVSS severity threshold"
},
"max_critical": {
"type": "integer",
"description": "Maximum allowed critical CVEs"
},
"max_high": {
"type": "integer",
"description": "Maximum allowed high CVEs"
},
"max_medium": {
"type": "integer",
"description": "Maximum allowed medium CVEs"
},
"max_low": {
"type": "integer",
"description": "Maximum allowed low CVEs"
},
"max_total": {
"type": "integer",
"description": "Maximum total CVEs"
},
"require_rekor": {
"type": "boolean",
"description": "Require Rekor anchor for attestations"
},
"count_suppressed": {
"type": "boolean",
"description": "Include suppressed CVEs in counts"
},
"only_reachable": {
"type": "boolean",
"description": "Only evaluate reachable CVEs"
},
"environments": {
"type": "object",
"description": "Per-environment configuration overrides",
"additionalProperties": { "$ref": "#/properties/config" }
}
}
},
"current_time": {
"type": "string",
"format": "date-time",
"description": "Current evaluation timestamp (ISO 8601)"
}
}
}

View File

@@ -0,0 +1,78 @@
# -----------------------------------------------------------------------------
# kev-blocker.rego
# Sprint: SPRINT_20260118_027_Policy_cve_release_gates
# Task: TASK-027-08 - OPA/Rego Policy Examples
# Description: CISA KEV catalog blocking policy
# -----------------------------------------------------------------------------
package stellaops.gates.kev
import future.keywords.if
import future.keywords.in
# Default allow if no KEV CVEs found
default allow = true
# Block if any CVE is in KEV catalog
allow = false if {
some cve in relevant_cves
cve.is_kev == true
}
# Get CVEs to evaluate (optionally filtered by reachability)
relevant_cves := [cve |
some cve in input.cve_findings
config_only_reachable
cve.is_reachable == true
]
relevant_cves := input.cve_findings if {
not config_only_reachable
}
# Configuration flags
config_only_reachable if {
input.config.only_reachable == true
}
# Denial messages with KEV details
deny[msg] if {
some cve in relevant_cves
cve.is_kev == true
not cve.kev_due_date
msg := sprintf("CVE %s is in CISA KEV catalog (actively exploited)", [cve.cve_id])
}
deny[msg] if {
some cve in relevant_cves
cve.is_kev == true
cve.kev_due_date
msg := sprintf("CVE %s is in CISA KEV catalog, due date: %s", [
cve.cve_id,
cve.kev_due_date
])
}
# Collect all KEV CVEs
kev_cves := [cve |
some cve in relevant_cves
cve.is_kev == true
]
# Check for overdue KEV CVEs (if current_time provided)
overdue_kev_cves := [cve |
some cve in kev_cves
cve.kev_due_date
input.current_time
time.parse_rfc3339_ns(cve.kev_due_date) < time.parse_rfc3339_ns(input.current_time)
]
# Summary for reporting
summary := {
"total_cves": count(relevant_cves),
"kev_count": count(kev_cves),
"overdue_count": count(overdue_kev_cves),
"kev_cves": [{"cve_id": cve.cve_id, "due_date": cve.kev_due_date} |
some cve in kev_cves
],
}

View File

@@ -0,0 +1,86 @@
# -----------------------------------------------------------------------------
# kev-blocker_test.rego
# Tests for KEV blocker policy
# -----------------------------------------------------------------------------
package stellaops.gates.kev
import future.keywords.if
# Test allow - no KEV CVEs
test_allow_no_kev if {
allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "is_kev": false},
{"cve_id": "CVE-2024-0002", "is_kev": false}
],
"config": {}
}
}
# Test deny - KEV CVE present
test_deny_kev_present if {
not allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "is_kev": false},
{"cve_id": "CVE-2024-0002", "is_kev": true}
],
"config": {}
}
}
# Test allow - empty findings
test_allow_empty_findings if {
allow with input as {
"cve_findings": [],
"config": {}
}
}
# Test only_reachable filters unreachable KEV
test_only_reachable_filters_unreachable_kev if {
allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "is_kev": true, "is_reachable": false}
],
"config": {"only_reachable": true}
}
}
# Test denial message includes due date
test_deny_message_with_due_date if {
msg := deny[_] with input as {
"cve_findings": [
{"cve_id": "CVE-2024-1234", "is_kev": true, "kev_due_date": "2024-02-15"}
],
"config": {}
}
contains(msg, "CVE-2024-1234")
contains(msg, "2024-02-15")
}
# Test denial message without due date
test_deny_message_without_due_date if {
msg := deny[_] with input as {
"cve_findings": [
{"cve_id": "CVE-2024-5678", "is_kev": true}
],
"config": {}
}
contains(msg, "CVE-2024-5678")
contains(msg, "actively exploited")
}
# Test summary structure
test_summary_structure if {
s := summary with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "is_kev": false},
{"cve_id": "CVE-2024-0002", "is_kev": true, "kev_due_date": "2024-02-15"},
{"cve_id": "CVE-2024-0003", "is_kev": true}
],
"config": {}
}
s.total_cves == 3
s.kev_count == 2
}

View File

@@ -0,0 +1,81 @@
# -----------------------------------------------------------------------------
# reachable-cve.rego
# Sprint: SPRINT_20260118_027_Policy_cve_release_gates
# Task: TASK-027-08 - OPA/Rego Policy Examples
# Description: Reachability-aware CVE blocking policy
# -----------------------------------------------------------------------------
package stellaops.gates.reachable
import future.keywords.if
import future.keywords.in
# Default allow if no reachable high-severity CVEs
default allow = true
# Block if any reachable CVE exceeds severity threshold
allow = false if {
some cve in input.cve_findings
is_blocking_reachable(cve)
cve.cvss_score >= severity_threshold
}
# Determine if CVE's reachability state is blocking
is_blocking_reachable(cve) if {
cve.is_reachable == true
}
is_blocking_reachable(cve) if {
cve.reachability_state in blocking_states
}
# Reachability states that trigger blocking
blocking_states := {"confirmed_reachable", "runtime_observed", "statically_reachable"}
# Non-blocking reachability states
non_blocking_states := {"not_reachable", "unknown"}
# Get severity threshold with environment override support
severity_threshold := env_config.severity_threshold if {
env_config := input.config.environments[input.environment]
env_config.severity_threshold
} else := input.config.severity_threshold if {
input.config.severity_threshold
} else := 7.0 # Default threshold (High severity)
# Denial messages with reachability details
deny[msg] if {
some cve in input.cve_findings
is_blocking_reachable(cve)
cve.cvss_score >= severity_threshold
msg := sprintf("Reachable CVE %s exceeds severity threshold: CVSS %.1f >= %.1f (state: %s)", [
cve.cve_id,
cve.cvss_score,
severity_threshold,
object.get(cve, "reachability_state", "reachable")
])
}
# Collect blocking CVEs
blocking_cves := [cve |
some cve in input.cve_findings
is_blocking_reachable(cve)
cve.cvss_score >= severity_threshold
]
# Collect allowed unreachable CVEs (for reporting)
allowed_unreachable := [cve |
some cve in input.cve_findings
cve.cvss_score >= severity_threshold
not is_blocking_reachable(cve)
]
# Summary for reporting
summary := {
"total_cves": count(input.cve_findings),
"reachable_high_severity": count(blocking_cves),
"unreachable_high_severity": count(allowed_unreachable),
"severity_threshold": severity_threshold,
"blocking_cves": [cve.cve_id | some cve in blocking_cves],
"allowed_unreachable": [cve.cve_id | some cve in allowed_unreachable],
}

View File

@@ -0,0 +1,101 @@
# -----------------------------------------------------------------------------
# reachable-cve_test.rego
# Tests for reachability-aware CVE policy
# -----------------------------------------------------------------------------
package stellaops.gates.reachable
import future.keywords.if
# Test allow - high severity but not reachable
test_allow_high_not_reachable if {
allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "cvss_score": 9.0, "is_reachable": false}
],
"config": {"severity_threshold": 7.0}
}
}
# Test allow - reachable but below threshold
test_allow_reachable_below_threshold if {
allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "cvss_score": 5.0, "is_reachable": true}
],
"config": {"severity_threshold": 7.0}
}
}
# Test deny - reachable and above threshold
test_deny_reachable_above_threshold if {
not allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "cvss_score": 8.5, "is_reachable": true}
],
"config": {"severity_threshold": 7.0}
}
}
# Test deny - confirmed_reachable state
test_deny_confirmed_reachable_state if {
not allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "cvss_score": 8.5, "reachability_state": "confirmed_reachable"}
],
"config": {"severity_threshold": 7.0}
}
}
# Test allow - not_reachable state
test_allow_not_reachable_state if {
allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "cvss_score": 9.5, "reachability_state": "not_reachable"}
],
"config": {"severity_threshold": 7.0}
}
}
# Test environment threshold override
test_environment_threshold_override if {
not allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "cvss_score": 5.0, "is_reachable": true}
],
"environment": "production",
"config": {
"severity_threshold": 7.0,
"environments": {
"production": {"severity_threshold": 4.0}
}
}
}
}
# Test denial message content
test_deny_message_content if {
msg := deny[_] with input as {
"cve_findings": [
{"cve_id": "CVE-2024-1234", "cvss_score": 8.1, "is_reachable": true}
],
"config": {"severity_threshold": 7.0}
}
contains(msg, "CVE-2024-1234")
contains(msg, "8.1")
}
# Test summary structure
test_summary_structure if {
s := summary with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "cvss_score": 9.0, "is_reachable": true},
{"cve_id": "CVE-2024-0002", "cvss_score": 8.0, "is_reachable": false},
{"cve_id": "CVE-2024-0003", "cvss_score": 5.0, "is_reachable": true}
],
"config": {"severity_threshold": 7.0}
}
s.total_cves == 3
s.reachable_high_severity == 1
s.unreachable_high_severity == 1
}

View File

@@ -0,0 +1,192 @@
# -----------------------------------------------------------------------------
# release-aggregate.rego
# Sprint: SPRINT_20260118_027_Policy_cve_release_gates
# Task: TASK-027-08 - OPA/Rego Policy Examples
# Description: Aggregate CVE count limits per release
# -----------------------------------------------------------------------------
package stellaops.gates.aggregate
import future.keywords.if
import future.keywords.in
# Default allow if all counts within limits
default allow = true
# Block if any severity count exceeds limit
allow = false if {
counts.critical > max_critical
}
allow = false if {
counts.high > max_high
}
allow = false if {
counts.medium > max_medium
}
allow = false if {
max_low != null
counts.low > max_low
}
allow = false if {
max_total != null
counts.total > max_total
}
# Get CVEs to count (optionally filtered)
counted_cves := [cve |
some cve in input.cve_findings
should_count(cve)
]
# Determine if CVE should be counted
should_count(cve) if {
not config_only_reachable
not config_exclude_suppressed
}
should_count(cve) if {
config_only_reachable
cve.is_reachable == true
not config_exclude_suppressed
}
should_count(cve) if {
not config_only_reachable
config_exclude_suppressed
not cve.is_suppressed
}
should_count(cve) if {
config_only_reachable
cve.is_reachable == true
config_exclude_suppressed
not cve.is_suppressed
}
# Classify severity from CVSS score
severity(cve) := "critical" if {
cve.cvss_score >= 9.0
}
severity(cve) := "high" if {
cve.cvss_score >= 7.0
cve.cvss_score < 9.0
}
severity(cve) := "medium" if {
cve.cvss_score >= 4.0
cve.cvss_score < 7.0
}
severity(cve) := "low" if {
cve.cvss_score >= 0.1
cve.cvss_score < 4.0
}
severity(cve) := "unknown" if {
not cve.cvss_score
}
# Count CVEs by severity
counts := {
"critical": count([c | some c in counted_cves; severity(c) == "critical"]),
"high": count([c | some c in counted_cves; severity(c) == "high"]),
"medium": count([c | some c in counted_cves; severity(c) == "medium"]),
"low": count([c | some c in counted_cves; severity(c) == "low"]),
"unknown": count([c | some c in counted_cves; severity(c) == "unknown"]),
"total": count(counted_cves),
}
# Get limits with environment override support
max_critical := env_config.max_critical if {
env_config := input.config.environments[input.environment]
env_config.max_critical != null
} else := input.config.max_critical if {
input.config.max_critical != null
} else := 0 # Default: no critical allowed
max_high := env_config.max_high if {
env_config := input.config.environments[input.environment]
env_config.max_high != null
} else := input.config.max_high if {
input.config.max_high != null
} else := 3 # Default: max 3 high
max_medium := env_config.max_medium if {
env_config := input.config.environments[input.environment]
env_config.max_medium != null
} else := input.config.max_medium if {
input.config.max_medium != null
} else := 20 # Default: max 20 medium
max_low := env_config.max_low if {
env_config := input.config.environments[input.environment]
env_config.max_low != null
} else := input.config.max_low if {
input.config.max_low != null
} else := null # Default: unlimited
max_total := env_config.max_total if {
env_config := input.config.environments[input.environment]
env_config.max_total != null
} else := input.config.max_total if {
input.config.max_total != null
} else := null # Default: unlimited
# Configuration flags
config_only_reachable if {
input.config.only_reachable == true
}
config_exclude_suppressed if {
input.config.count_suppressed == false
}
config_exclude_suppressed if {
not input.config.count_suppressed
}
# Denial messages
deny[msg] if {
counts.critical > max_critical
msg := sprintf("Critical CVE count exceeds limit: %d > %d", [counts.critical, max_critical])
}
deny[msg] if {
counts.high > max_high
msg := sprintf("High CVE count exceeds limit: %d > %d", [counts.high, max_high])
}
deny[msg] if {
counts.medium > max_medium
msg := sprintf("Medium CVE count exceeds limit: %d > %d", [counts.medium, max_medium])
}
deny[msg] if {
max_low != null
counts.low > max_low
msg := sprintf("Low CVE count exceeds limit: %d > %d", [counts.low, max_low])
}
deny[msg] if {
max_total != null
counts.total > max_total
msg := sprintf("Total CVE count exceeds limit: %d > %d", [counts.total, max_total])
}
# Summary for reporting
summary := {
"counts": counts,
"limits": {
"max_critical": max_critical,
"max_high": max_high,
"max_medium": max_medium,
"max_low": max_low,
"max_total": max_total,
},
"environment": input.environment,
}

View File

@@ -0,0 +1,137 @@
# -----------------------------------------------------------------------------
# release-aggregate_test.rego
# Tests for aggregate CVE limits policy
# -----------------------------------------------------------------------------
package stellaops.gates.aggregate
import future.keywords.if
# Test allow - within all limits
test_allow_within_limits if {
allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "cvss_score": 8.0},
{"cve_id": "CVE-2024-0002", "cvss_score": 7.5},
{"cve_id": "CVE-2024-0003", "cvss_score": 5.0}
],
"config": {"max_critical": 0, "max_high": 3, "max_medium": 20}
}
}
# Test deny - critical exceeds limit
test_deny_critical_exceeds if {
not allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "cvss_score": 9.5}
],
"config": {"max_critical": 0}
}
}
# Test deny - high exceeds limit
test_deny_high_exceeds if {
not allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "cvss_score": 8.0},
{"cve_id": "CVE-2024-0002", "cvss_score": 7.5},
{"cve_id": "CVE-2024-0003", "cvss_score": 8.5},
{"cve_id": "CVE-2024-0004", "cvss_score": 7.0}
],
"config": {"max_high": 3}
}
}
# Test allow - empty findings
test_allow_empty_findings if {
allow with input as {
"cve_findings": [],
"config": {"max_critical": 0, "max_high": 3}
}
}
# Test only_reachable filter
test_only_reachable_filters if {
allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "cvss_score": 9.5, "is_reachable": false}
],
"config": {"max_critical": 0, "only_reachable": true}
}
}
# Test exclude suppressed
test_exclude_suppressed if {
allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "cvss_score": 9.5, "is_suppressed": true}
],
"config": {"max_critical": 0, "count_suppressed": false}
}
}
# Test environment override
test_environment_override if {
allow with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "cvss_score": 9.5}
],
"environment": "staging",
"config": {
"max_critical": 0,
"environments": {
"staging": {"max_critical": 1}
}
}
}
}
# Test severity classification
test_severity_classification if {
c := counts with input as {
"cve_findings": [
{"cve_id": "CVE-001", "cvss_score": 9.5},
{"cve_id": "CVE-002", "cvss_score": 8.0},
{"cve_id": "CVE-003", "cvss_score": 7.0},
{"cve_id": "CVE-004", "cvss_score": 5.0},
{"cve_id": "CVE-005", "cvss_score": 3.0},
{"cve_id": "CVE-006"}
],
"config": {}
}
c.critical == 1
c.high == 2
c.medium == 1
c.low == 1
c.unknown == 1
c.total == 6
}
# Test denial message content
test_deny_message_critical if {
msg := deny[_] with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "cvss_score": 9.5}
],
"config": {"max_critical": 0}
}
contains(msg, "Critical")
contains(msg, "1 > 0")
}
# Test summary structure
test_summary_structure if {
s := summary with input as {
"cve_findings": [
{"cve_id": "CVE-2024-0001", "cvss_score": 8.0},
{"cve_id": "CVE-2024-0002", "cvss_score": 5.0}
],
"environment": "production",
"config": {"max_high": 3, "max_medium": 20}
}
s.counts.high == 1
s.counts.medium == 1
s.limits.max_high == 3
s.limits.max_medium == 20
s.environment == "production"
}

View File

@@ -0,0 +1,130 @@
{
"attestation": {
"dsse_envelope": {
"payloadType": "application/vnd.in-toto+json",
"payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoibXlhcHA6djEuMi4zIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImFiYzEyMyJ9fV19",
"signatures": [
{
"keyid": "stella-release-key-001",
"sig": "MEUCIQDcJT8...signature..."
}
]
},
"rekor_entry": {
"log_index": 12345678,
"log_id": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=",
"integrated_time": 1705689600,
"inclusion_proof": {
"root_hash": "abc123def456...",
"tree_size": 98765432,
"hashes": ["hash1", "hash2", "hash3"]
}
},
"trusted_keys": ["stella-release-key-001", "stella-release-key-002"]
},
"cve_findings": [
{
"cve_id": "CVE-2024-1234",
"cvss_score": 9.1,
"severity": "critical",
"epss_score": 0.72,
"epss_percentile": 95,
"is_kev": false,
"is_reachable": true,
"reachability_state": "confirmed_reachable",
"is_suppressed": false,
"package_name": "vulnerable-lib",
"package_version": "1.2.3",
"fix_available": true,
"fixed_version": "1.2.4"
},
{
"cve_id": "CVE-2024-5678",
"cvss_score": 7.5,
"severity": "high",
"epss_score": 0.42,
"epss_percentile": 78,
"is_kev": false,
"is_reachable": false,
"reachability_state": "not_reachable",
"is_suppressed": false,
"package_name": "another-lib",
"package_version": "2.0.0",
"fix_available": false
},
{
"cve_id": "CVE-2024-9012",
"cvss_score": 5.3,
"severity": "medium",
"epss_score": 0.15,
"epss_percentile": 45,
"is_kev": false,
"is_reachable": true,
"reachability_state": "statically_reachable",
"is_suppressed": false,
"package_name": "common-util",
"package_version": "3.1.0"
},
{
"cve_id": "CVE-2023-44487",
"cvss_score": 7.5,
"severity": "high",
"epss_score": 0.89,
"epss_percentile": 99,
"is_kev": true,
"kev_due_date": "2024-02-15",
"is_reachable": true,
"reachability_state": "runtime_observed",
"is_suppressed": true,
"package_name": "http2-lib",
"package_version": "1.0.0"
}
],
"baseline_cve_findings": [
{
"cve_id": "CVE-2024-5678",
"cvss_score": 7.5
},
{
"cve_id": "CVE-2024-0001",
"cvss_score": 6.0
}
],
"environment": "production",
"release": {
"id": "rel-2024-01-19-001",
"version": "1.2.3",
"image_digest": "sha256:abc123...",
"baseline_digest": "sha256:def456..."
},
"config": {
"epss_threshold": 0.6,
"severity_threshold": 7.0,
"max_critical": 0,
"max_high": 3,
"max_medium": 20,
"require_rekor": true,
"count_suppressed": false,
"only_reachable": false,
"environments": {
"production": {
"epss_threshold": 0.3,
"severity_threshold": 7.0,
"max_critical": 0,
"max_high": 0,
"only_reachable": true
},
"staging": {
"epss_threshold": 0.7,
"max_critical": 1,
"max_high": 5
},
"development": {
"epss_threshold": 0.9,
"max_critical": null,
"max_high": null
}
}
},
"current_time": "2024-01-19T12:00:00Z"
}