287 lines
6.9 KiB
Markdown
287 lines
6.9 KiB
Markdown
# StellaOps Reproducibility Specification
|
|
|
|
This document defines the reproducibility guarantees, verdict identity computation, and replay procedures for StellaOps artifacts.
|
|
|
|
## Overview
|
|
|
|
StellaOps provides **deterministic, reproducible outputs** for all security artifacts:
|
|
- SBOM generation (CycloneDX 1.6, SPDX 3.0.1)
|
|
- VEX statements (OpenVEX)
|
|
- Policy verdicts
|
|
- Delta computations
|
|
- DSSE attestations and Sigstore bundles
|
|
|
|
**Core Guarantee:** Given identical inputs (image digest, advisory feeds, policies, tool versions), StellaOps produces byte-for-byte identical outputs with matching content-addressed identifiers.
|
|
|
|
## Verdict Identity Formula
|
|
|
|
### Content-Addressed Verdict ID
|
|
|
|
All verdicts use content-addressed identifiers computed as:
|
|
|
|
```
|
|
VerdictId = SHA256(Canonicalize(VerdictPayload))
|
|
```
|
|
|
|
Where `VerdictPayload` includes:
|
|
- **Delta ID**: Content hash of the security delta
|
|
- **Blocking Drivers**: Sorted list of risk-increasing factors
|
|
- **Warning Drivers**: Sorted list of advisory factors
|
|
- **Applied Exceptions**: Sorted list of exception IDs covering findings
|
|
- **Gate Level**: Recommended gate (G0-G4)
|
|
- **Input Stamps**: Hashes of all inputs (see below)
|
|
|
|
### Input Stamps
|
|
|
|
Every artifact includes `InputStamps` capturing the provenance of all inputs:
|
|
|
|
```json
|
|
{
|
|
"feedSnapshotHash": "sha256:abc123...",
|
|
"policyManifestHash": "sha256:def456...",
|
|
"sourceCodeHash": "sha256:789ghi...",
|
|
"baseImageDigest": "sha256:jkl012...",
|
|
"vexDocumentHashes": ["sha256:mno345..."],
|
|
"toolchainVersion": "1.0.0",
|
|
"custom": {}
|
|
}
|
|
```
|
|
|
|
### Determinism Manifest
|
|
|
|
The `DeterminismManifest` (schema v1.0) tracks artifact reproducibility:
|
|
|
|
```json
|
|
{
|
|
"schemaVersion": "1.0",
|
|
"artifact": {
|
|
"type": "verdict",
|
|
"name": "scan-verdict",
|
|
"version": "2025-12-24T12:00:00Z",
|
|
"format": "StellaOps.DeltaVerdict@1"
|
|
},
|
|
"canonicalHash": {
|
|
"algorithm": "SHA-256",
|
|
"value": "abc123def456...",
|
|
"encoding": "hex"
|
|
},
|
|
"inputs": {
|
|
"feedSnapshotHash": "sha256:...",
|
|
"policyManifestHash": "sha256:...",
|
|
"baseImageDigest": "sha256:..."
|
|
},
|
|
"toolchain": {
|
|
"platform": ".NET 10.0",
|
|
"components": [
|
|
{"name": "StellaOps.Scanner", "version": "1.0.0"},
|
|
{"name": "StellaOps.Policy", "version": "1.0.0"}
|
|
]
|
|
},
|
|
"reproducibility": {
|
|
"clockFixed": true,
|
|
"orderingGuarantee": "stable-sort",
|
|
"normalizationRules": ["UTF-8", "LF", "canonical-json"]
|
|
},
|
|
"generatedAt": "2025-12-24T12:00:00Z"
|
|
}
|
|
```
|
|
|
|
## Canonical JSON Serialization
|
|
|
|
All JSON outputs follow RFC 8785 (JSON Canonicalization Scheme):
|
|
1. Keys sorted lexicographically
|
|
2. No whitespace between tokens
|
|
3. Unicode escaping for non-ASCII
|
|
4. Numbers without leading zeros
|
|
5. UTF-8 encoding
|
|
|
|
## DSSE Attestation Format
|
|
|
|
### Envelope Structure
|
|
|
|
```json
|
|
{
|
|
"payloadType": "application/vnd.in-toto+json",
|
|
"payload": "<base64url-encoded statement>",
|
|
"signatures": [
|
|
{
|
|
"keyid": "sha256:...",
|
|
"sig": "<base64url-encoded signature>"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### In-toto Statement
|
|
|
|
```json
|
|
{
|
|
"_type": "https://in-toto.io/Statement/v1",
|
|
"subject": [
|
|
{
|
|
"name": "registry.example.com/image:tag",
|
|
"digest": {"sha256": "abc123..."}
|
|
}
|
|
],
|
|
"predicateType": "https://stellaops.io/attestation/verdict/v1",
|
|
"predicate": {
|
|
"verdictId": "sha256:...",
|
|
"status": "pass",
|
|
"gate": "G2",
|
|
"inputs": {...},
|
|
"evidence": [...]
|
|
}
|
|
}
|
|
```
|
|
|
|
## Sigstore Bundle Format
|
|
|
|
StellaOps produces Sigstore bundles (v0.3) for offline verification:
|
|
|
|
```json
|
|
{
|
|
"$mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json",
|
|
"verificationMaterial": {
|
|
"certificate": {...},
|
|
"tlogEntries": [{
|
|
"logIndex": "12345",
|
|
"logId": {...},
|
|
"inclusionProof": {...}
|
|
}]
|
|
},
|
|
"dsseEnvelope": {...}
|
|
}
|
|
```
|
|
|
|
## Replay Procedure
|
|
|
|
### Prerequisites
|
|
1. Offline bundle containing:
|
|
- Advisory feed snapshot
|
|
- Policy pack
|
|
- VEX documents
|
|
- Tool binaries (pinned versions)
|
|
|
|
### Steps
|
|
|
|
```bash
|
|
# 1. Extract offline bundle
|
|
stella offline extract --bundle offline-kit.tar.gz --output ./replay
|
|
|
|
# 2. Set deterministic environment
|
|
export STELLAOPS_DETERMINISTIC_SEED=42
|
|
export STELLAOPS_CLOCK_FIXED=2025-12-24T12:00:00Z
|
|
|
|
# 3. Run scan with pinned inputs
|
|
stella scan \
|
|
--image registry.example.com/image@sha256:abc123 \
|
|
--feeds ./replay/feeds \
|
|
--policies ./replay/policies \
|
|
--output ./replay/output
|
|
|
|
# 4. Verify hash matches original
|
|
stella verify \
|
|
--manifest ./replay/output/manifest.json \
|
|
--expected-hash sha256:def456...
|
|
|
|
# 5. Verify DSSE attestation
|
|
stella attest verify \
|
|
--bundle ./replay/output/bundle.sigstore \
|
|
--policy verification-policy.yaml
|
|
```
|
|
|
|
### Verification Policy
|
|
|
|
```yaml
|
|
apiVersion: stellaops.io/v1
|
|
kind: VerificationPolicy
|
|
metadata:
|
|
name: audit-verification
|
|
spec:
|
|
requiredPredicateTypes:
|
|
- https://stellaops.io/attestation/verdict/v1
|
|
trustedIssuers:
|
|
- https://accounts.stellaops.io
|
|
maxAge: 90d
|
|
requireRekorEntry: true
|
|
unknownBudget:
|
|
maxTotal: 5
|
|
action: fail
|
|
```
|
|
|
|
## Unknown Budget Attestation
|
|
|
|
Policy thresholds are attested in verdict bundles:
|
|
|
|
```json
|
|
{
|
|
"predicateType": "https://stellaops.io/attestation/budget-check/v1",
|
|
"predicate": {
|
|
"environment": "production",
|
|
"budgetConfig": {
|
|
"maxUnknownCount": 5,
|
|
"maxCumulativeUncertainty": 2.0,
|
|
"reasonLimits": {
|
|
"Reachability": 0,
|
|
"Identity": 2,
|
|
"Provenance": 2
|
|
}
|
|
},
|
|
"actualCounts": {
|
|
"total": 3,
|
|
"byReason": {"Identity": 2, "Provenance": 1}
|
|
},
|
|
"result": "pass",
|
|
"configHash": "sha256:..."
|
|
}
|
|
}
|
|
```
|
|
|
|
## Schema Versions
|
|
|
|
| Format | Version | Schema Location |
|
|
|--------|---------|-----------------|
|
|
| CycloneDX | 1.6 | `docs/schemas/cyclonedx-bom-1.6.schema.json` |
|
|
| SPDX | 3.0.1 | `docs/schemas/spdx-3.0.1.schema.json` |
|
|
| OpenVEX | 0.2.0 | `docs/schemas/openvex-0.2.0.schema.json` |
|
|
| Sigstore Bundle | 0.3 | `docs/schemas/sigstore-bundle-0.3.schema.json` |
|
|
| DeterminismManifest | 1.0 | `docs/schemas/determinism-manifest-1.0.schema.json` |
|
|
|
|
## CI Integration
|
|
|
|
### Schema Validation
|
|
|
|
```yaml
|
|
# .gitea/workflows/schema-validation.yml
|
|
- name: Validate CycloneDX
|
|
run: |
|
|
sbom-utility validate \
|
|
--input-file ${{ matrix.fixture }} \
|
|
--schema docs/schemas/cyclonedx-bom-1.6.schema.json
|
|
```
|
|
|
|
### Determinism Gate
|
|
|
|
```yaml
|
|
# .gitea/workflows/determinism-gate.yml
|
|
- name: Verify Verdict Hash
|
|
run: |
|
|
HASH1=$(stella scan --image test:latest --output /tmp/run1 | jq -r '.verdictId')
|
|
HASH2=$(stella scan --image test:latest --output /tmp/run2 | jq -r '.verdictId')
|
|
[ "$HASH1" = "$HASH2" ] || exit 1
|
|
```
|
|
|
|
## Related Documentation
|
|
|
|
- [Testing Strategy](testing/testing-strategy-models.md)
|
|
- [Determinism Verification](testing/determinism-verification.md)
|
|
- [DSSE Attestation Guide](modules/attestor/README.md)
|
|
- [Offline Operation](24_OFFLINE_KIT.md)
|
|
- [Proof Bundle Spec](modules/triage/proof-bundle-spec.md)
|
|
|
|
## Changelog
|
|
|
|
| Version | Date | Changes |
|
|
|---------|------|---------|
|
|
| 1.0 | 2025-12-24 | Initial specification based on product advisory gap analysis |
|