old sprints work, new sprints for exposing functionality via cli, improve code_of_conduct and other agents instructions

This commit is contained in:
master
2026-01-15 18:37:59 +02:00
parent c631bacee2
commit 88a85cdd92
208 changed files with 32271 additions and 2287 deletions

View File

@@ -143,14 +143,62 @@ Produce remediation plan with fix versions and verification steps.
- **Response extensions:** `content.format` Markdown plus `context.remediation` with recommended fix versions (`package`, `fixedVersion`, `rationale`).
- **Errors:** `422 advisory.remediation.noFixAvailable` (vendor has not published fix), `409 advisory.remediation.policyHold` (policy forbids automated remediation).
### 7.4 `GET /v1/advisory-ai/outputs/{{outputHash}}`
### 7.4 `POST /v1/advisory-ai/remediation/apply`
Apply a remediation plan by creating a PR/MR in the target SCM. Requires `advisory-ai:operate` and tenant SCM connector configuration.
- **Request body:**
```json
{
"planId": "plan-abc123",
"scmType": "github"
}
```
- **Response:**
```json
{
"prId": "gh-pr-42",
"prNumber": 42,
"url": "https://github.com/owner/repo/pull/42",
"branchName": "stellaops/security-fix/plan-abc123",
"status": "Open",
"statusMessage": "Pull request created successfully",
"prBody": "## Security Remediation\n\n**Plan ID:** `plan-abc123`\n...",
"createdAt": "2026-01-14T12:00:00Z",
"updatedAt": "2026-01-14T12:00:00Z"
}
```
- **PR body includes:**
- Summary with vulnerability and component info
- Remediation steps (file changes)
- Expected SBOM changes (upgrades, additions, removals)
- Test requirements
- Rollback steps
- VEX claim context
- Evidence references
- **Supported SCM types:** `github`, `gitlab`, `azure-devops`, `gitea`
- **Errors:**
- `404 remediation.planNotFound` plan does not exist
- `400 remediation.scmTypeNotSupported` requested SCM type not configured
- `409 remediation.planNotReady` plan is not PR-ready (see `notReadyReason`)
- `502 remediation.scmError` SCM connector error (branch/file/PR creation failed)
### 7.5 `GET /v1/advisory-ai/remediation/status/{prId}`
Check the status of a PR created by the remediation apply endpoint.
- **Query parameters:** `scmType` (optional, defaults to `github`)
- **Response:** Same envelope as `POST /remediation/apply`
- **Errors:** `404 remediation.prNotFound`
### 7.6 `GET /v1/advisory-ai/outputs/{{outputHash}}`
Fetch cached artefact (same envelope as §6). Requires `advisory-ai:view`.
- **Headers:** Supports `If-None-Match` with the `outputHash` (Etag) for cache validation.
- **Errors:** `404 advisory.output.notFound` if cache expired or tenant lacks access.
### 7.5 `GET /v1/advisory-ai/plans/{{cacheKey}}` (optional)
### 7.7 `GET /v1/advisory-ai/plans/{{cacheKey}}` (optional)
When plan preview is enabled (feature flag `advisoryAi.planPreview.enabled`), this endpoint returns the orchestration plan using `AdvisoryPipelinePlanResponse` (task metadata, chunk/vector counts). Requires `advisory-ai:operate`.
@@ -208,3 +256,4 @@ Limits are enforced at the gateway; the API returns `429` with standard `Retry-A
| Date (UTC) | Change |
|------------|--------|
| 2025-11-03 | Initial sprint-110 preview covering summary/conflict/remediation endpoints, cache retrieval, plan preview, and error/rate limit model. |
| 2026-01-14 | Added PR generation endpoints (7.4, 7.5): `POST /remediation/apply` and `GET /remediation/status/{prId}`. PR body includes security remediation template with steps, expected changes, tests, rollback, VEX claim. Supported SCM types: github, gitlab, azure-devops, gitea. (SPRINT_20260112_007_BE_remediation_pr_generator) |

View File

@@ -1,20 +1,20 @@
# component_architecture_attestor.md **StellaOps Attestor** (2025Q4)
# component_architecture_attestor.md — **Stella Ops Attestor** (2025Q4)
> Derived from Epic19 Attestor Console with provenance hooks aligned to the Export Center bundle workflows scoped in Epic10.
> Derived from Epic 19 – Attestor Console with provenance hooks aligned to the Export Center bundle workflows scoped in Epic 10.
> **Scope.** Implementationready architecture for the **Attestor**: the service that **submits** DSSE envelopes to **Rekor v2**, retrieves/validates inclusion proofs, caches results, and exposes verification APIs. It accepts DSSE **only** from the **Signer** over mTLS, enforces chainoftrust to StellaOps roots, and returns `{uuid, index, proof, logURL}` to calling services (Scanner.WebService for SBOMs; backend for final reports; Excititor exports when configured).
> **Scope.** Implementation‑ready architecture for the **Attestor**: the service that **submits** DSSE envelopes to **Rekor v2**, retrieves/validates inclusion proofs, caches results, and exposes verification APIs. It accepts DSSE **only** from the **Signer** over mTLS, enforces chainâ€ofâ€trust to Stella Ops roots, and returns `{uuid, index, proof, logURL}` to calling services (Scanner.WebService for SBOMs; backend for final reports; Excititor exports when configured).
---
## 0) Mission & boundaries
**Mission.** Turn a signed DSSE envelope from the Signer into a **transparencylogged, verifiable fact** with a durable, replayable proof (Merkle inclusion + (optional) checkpoint anchoring). Provide **fast verification** for downstream consumers and a stable retrieval interface for UI/CLI.
**Mission.** Turn a signed DSSE envelope from the Signer into a **transparency‑logged, verifiable fact** with a durable, replayable proof (Merkle inclusion + (optional) checkpoint anchoring). Provide **fast verification** for downstream consumers and a stable retrieval interface for UI/CLI.
**Boundaries.**
* Attestor **does not sign**; it **must not** accept unsigned or thirdpartysigned bundles.
* Attestor **does not sign**; it **must not** accept unsigned or third‑party‑signed bundles.
* Attestor **does not decide PASS/FAIL**; it logs attestations for SBOMs, reports, and export artifacts.
* Rekor v2 backends may be **local** (selfhosted) or **remote**; Attestor handles both with retries, backoff, and idempotency.
* Rekor v2 backends may be **local** (self‑hosted) or **remote**; Attestor handles both with retries, backoff, and idempotency.
---
@@ -24,22 +24,22 @@
**Dependencies:**
* **Signer** (caller) authenticated via **mTLS** and **Authority** OpToks.
* **Rekor v2** tilebacked transparency log endpoint(s).
* **RustFS (S3-compatible)** optional archive store for DSSE envelopes & verification bundles.
* **PostgreSQL** local cache of `{uuid, index, proof, artifactSha256, bundleSha256}`; job state; audit.
* **Valkey** dedupe/idempotency keys and shortlived ratelimit buckets.
* **Licensing Service (optional)** — “endorse call for crosslog publishing when customer optsin.
* **Signer** (caller) — authenticated via **mTLS** and **Authority** OpToks.
* **Rekor v2** — tile‑backed transparency log endpoint(s).
* **RustFS (S3-compatible)** — optional archive store for DSSE envelopes & verification bundles.
* **PostgreSQL** — local cache of `{uuid, index, proof, artifactSha256, bundleSha256}`; job state; audit.
* **Valkey** — dedupe/idempotency keys and short‑lived rate‑limit buckets.
* **Licensing Service (optional)** — “endorse” call for cross‑log publishing when customer opts‑in.
Trust boundary: **Only the Signer** is allowed to call submission endpoints; enforced by **mTLS peer cert allowlist** + `aud=attestor` OpTok.
---
### Roles, identities & scopes
- **Subjects** immutable digests for artifacts (container images, SBOMs, reports) referenced in DSSE envelopes.
- **Issuers** authenticated builders/scanners/policy engines signing evidence; tracked with mode (`keyless`, `kms`, `hsm`, `fido2`) and tenant scope.
- **Consumers** Scanner, Export Center, CLI, Console, Policy Engine that verify proofs using Attestor APIs.
- **Authority scopes** `attestor.write`, `attestor.verify`, `attestor.read`, and administrative scopes for key management; all calls mTLS/DPoP-bound.
- **Subjects** — immutable digests for artifacts (container images, SBOMs, reports) referenced in DSSE envelopes.
- **Issuers** — authenticated builders/scanners/policy engines signing evidence; tracked with mode (`keyless`, `kms`, `hsm`, `fido2`) and tenant scope.
- **Consumers** — Scanner, Export Center, CLI, Console, Policy Engine that verify proofs using Attestor APIs.
- **Authority scopes** — `attestor.write`, `attestor.verify`, `attestor.read`, and administrative scopes for key management; all calls mTLS/DPoP-bound.
### Supported predicate types
- `StellaOps.BuildProvenance@1`
@@ -75,9 +75,9 @@ Each predicate embeds subject digests, issuer metadata, policy context, material
The Attestor implements RFC 6962-compliant Merkle inclusion proof verification for Rekor transparency log entries:
**Components:**
- `MerkleProofVerifier` Verifies Merkle audit paths per RFC 6962 Section 2.1.1
- `CheckpointSignatureVerifier` Parses and verifies Rekor checkpoint signatures (ECDSA/Ed25519)
- `RekorVerificationOptions` Configuration for public keys, offline mode, and checkpoint caching
- `MerkleProofVerifier` — Verifies Merkle audit paths per RFC 6962 Section 2.1.1
- `CheckpointSignatureVerifier` — Parses and verifies Rekor checkpoint signatures (ECDSA/Ed25519)
- `RekorVerificationOptions` — Configuration for public keys, offline mode, and checkpoint caching
**Verification Flow:**
1. Parse checkpoint body (origin, tree size, root hash)
@@ -92,10 +92,10 @@ The Attestor implements RFC 6962-compliant Merkle inclusion proof verification f
- `AllowOfflineWithoutSignature` for fully disconnected scenarios (reduced security)
**Metrics:**
- `attestor.rekor_inclusion_verify_total` Verification attempts by result
- `attestor.rekor_checkpoint_verify_total` Checkpoint signature verifications
- `attestor.rekor_offline_verify_total` Offline mode verifications
- `attestor.rekor_checkpoint_cache_hits/misses` Checkpoint cache performance
- `attestor.rekor_inclusion_verify_total` — Verification attempts by result
- `attestor.rekor_checkpoint_verify_total` — Checkpoint signature verifications
- `attestor.rekor_offline_verify_total` — Offline mode verifications
- `attestor.rekor_checkpoint_cache_hits/misses` — Checkpoint cache performance
### UI & CLI touchpoints
- Console: Evidence browser, verification report, chain-of-custody graph, issuer/key management, attestation workbench, bulk verification views.
@@ -103,9 +103,9 @@ The Attestor implements RFC 6962-compliant Merkle inclusion proof verification f
- SDKs expose sign/verify primitives for build pipelines.
### Performance & observability targets
- Throughput goal: ≥1000 envelopes/minute per worker with cached verification.
- Throughput goal: ≥1 000 envelopes/minute per worker with cached verification.
- Metrics: `attestor_submission_total`, `attestor_verify_seconds`, `attestor_rekor_latency_seconds`, `attestor_cache_hit_ratio`.
- Logs include `tenant`, `issuer`, `subjectDigest`, `rekorUuid`, `proofStatus`; traces cover submission Rekor cache response path.
- Logs include `tenant`, `issuer`, `subjectDigest`, `rekorUuid`, `proofStatus`; traces cover submission → Rekor → cache → response path.
---
@@ -171,8 +171,8 @@ Database: `attestor`
Indexes:
* `entries`: indexes on `artifact_sha256`, `bundle_sha256`, `created_at`, and composite `(status, created_at DESC)`.
* `dedupe`: unique index on `key`; scheduled job cleans rows where `ttl_at < NOW()` (2448h retention).
* `audit`: index on `ts` for timerange queries.
* `dedupe`: unique index on `key`; scheduled job cleans rows where `ttl_at < NOW()` (24–48h retention).
* `audit`: index on `ts` for time‑range queries.
---
@@ -330,10 +330,10 @@ SBOM-to-component linkage metadata.
**Attestor accepts only** DSSE envelopes that satisfy all of:
1. **mTLS** peer certificate maps to `signer` service (CApinned).
1. **mTLS** peer certificate maps to `signer` service (CA‑pinned).
2. **Authority** OpTok with `aud=attestor`, `scope=attestor.write`, DPoP or mTLS bound.
3. DSSE envelope is **signed by the Signers key** (or includes a **Fulcioissued** cert chain) and **chains to configured roots** (Fulcio/KMS).
4. **Predicate type** is one of StellaOps types (sbom/report/vexexport) with valid schema.
3. DSSE envelope is **signed by the Signer’s key** (or includes a **Fulcio‑issued** cert chain) and **chains to configured roots** (Fulcio/KMS).
4. **Predicate type** is one of Stella Ops types (sbom/report/vex‑export) with valid schema.
5. `subject[*].digest.sha256` is present and canonicalized.
**Wire shape (JSON):**
@@ -360,7 +360,7 @@ SBOM-to-component linkage metadata.
`POST /api/v1/attestations:sign` *(mTLS + OpTok required)*
* **Purpose**: Deterministically wrap StellaOps payloads in DSSE envelopes before Rekor submission. Reuses the submission rate limiter and honours caller tenancy/audience scopes.
* **Purpose**: Deterministically wrap Stella Ops payloads in DSSE envelopes before Rekor submission. Reuses the submission rate limiter and honours caller tenancy/audience scopes.
* **Body**:
```json
@@ -383,7 +383,7 @@ SBOM-to-component linkage metadata.
* **Behaviour**:
* Resolve the signing key from `attestor.signing.keys[]` (includes algorithm, provider, and optional KMS version).
* Compute DSSE preauthentication encoding, sign with the resolved provider (default EC, BouncyCastle Ed25519, or FileKMS ES256), and add static + request certificate chains.
* Compute DSSE pre‑authentication encoding, sign with the resolved provider (default EC, BouncyCastle Ed25519, or File‑KMS ES256), and add static + request certificate chains.
* Canonicalise the resulting bundle, derive `bundleSha256`, and mirror the request meta shape used by `/api/v1/rekor/entries`.
* Emit `attestor.sign_total{result,algorithm,provider}` and `attestor.sign_latency_seconds{algorithm,provider}` metrics and append an audit row (`action=sign`).
* **Response 200**:
@@ -415,13 +415,13 @@ SBOM-to-component linkage metadata.
```json
{
"uuid": "",
"uuid": "…",
"index": 123456,
"proof": {
"checkpoint": { "origin": "rekor@site", "size": 987654, "rootHash": "", "timestamp": "" },
"inclusion": { "leafHash": "", "path": ["…","…"] }
"checkpoint": { "origin": "rekor@site", "size": 987654, "rootHash": "…", "timestamp": "…" },
"inclusion": { "leafHash": "…", "path": ["…","…"] }
},
"logURL": "https://rekor/api/v2/log//entries/",
"logURL": "https://rekor…/api/v2/log/…/entries/…",
"status": "included"
}
```
@@ -434,28 +434,28 @@ SBOM-to-component linkage metadata.
* Returns `entries` row (refreshes proof from Rekor if stale/missing).
* Accepts `?refresh=true` to force backend query.
### 4.4 Verification (thirdparty or internal)
### 4.4 Verification (third‑party or internal)
`POST /api/v1/rekor/verify`
* **Body** (one of):
* `{ "uuid": "" }`
* `{ "bundle": { DSSE } }`
* `{ "artifactSha256": "" }` *(looks up most recent entry)*
* `{ "uuid": "…" }`
* `{ "bundle": { …DSSE… } }`
* `{ "artifactSha256": "…" }` *(looks up most recent entry)*
* **Checks**:
1. **Bundle signature** cert chain to Fulcio/KMS roots configured.
2. **Inclusion proof** recompute leaf hash; verify Merkle path against checkpoint root.
1. **Bundle signature** → cert chain to Fulcio/KMS roots configured.
2. **Inclusion proof** → recompute leaf hash; verify Merkle path against checkpoint root.
3. Optionally verify **checkpoint** against local trust anchors (if Rekor signs checkpoints).
4. Confirm **subject.digest** matches callerprovided hash (when given).
4. Confirm **subject.digest** matches caller‑provided hash (when given).
5. Fetch **transparency witness** statement when enabled; cache results and downgrade status to WARN when endorsements are missing or mismatched.
* **Response**:
```json
{ "ok": true, "uuid": "", "index": 123, "logURL": "", "checkedAt": "" }
{ "ok": true, "uuid": "…", "index": 123, "logURL": "…", "checkedAt": "…" }
```
### 4.5 Bulk verification
@@ -464,11 +464,11 @@ SBOM-to-component linkage metadata.
`GET /api/v1/rekor/verify:bulk/{jobId}` returns progress and per-item results (subject/uuid, status, issues, cached verification report if available). Jobs are tenant- and subject-scoped; only the initiating principal can read their progress.
**Worker path:** `BulkVerificationWorker` claims queued jobs (`status=queued running`), executes items sequentially through the cached verification service, updates progress counters, and records metrics:
**Worker path:** `BulkVerificationWorker` claims queued jobs (`status=queued → running`), executes items sequentially through the cached verification service, updates progress counters, and records metrics:
- `attestor.bulk_jobs_total{status}` completed/failed jobs
- `attestor.bulk_job_duration_seconds{status}` job runtime
- `attestor.bulk_items_total{status}` per-item outcomes (`succeeded`, `verification_failed`, `exception`)
- `attestor.bulk_jobs_total{status}` – completed/failed jobs
- `attestor.bulk_job_duration_seconds{status}` – job runtime
- `attestor.bulk_items_total{status}` – per-item outcomes (`succeeded`, `verification_failed`, `exception`)
The worker honours `bulkVerification.itemDelayMilliseconds` for throttling and reschedules persistence conflicts with optimistic version checks. Results hydrate the verification cache; failed items record the error reason without aborting the overall job.
@@ -478,7 +478,7 @@ The worker honours `bulkVerification.itemDelayMilliseconds` for throttling and r
* **Canonicalization**: DSSE envelopes are **normalized** (stable JSON ordering, no insignificant whitespace) before hashing and submission.
* **Transport**: HTTP/2 with retries (exponential backoff, jitter), budgeted timeouts.
* **Idempotency**: if backend returns already exists, map to existing `uuid`.
* **Idempotency**: if backend returns “already exists,” map to existing `uuid`.
* **Proof acquisition**:
* In synchronous mode, poll the log for inclusion up to `proofTimeoutMs`.
@@ -486,25 +486,25 @@ The worker honours `bulkVerification.itemDelayMilliseconds` for throttling and r
* **Mirrors/dual logs**:
* When `logPreference="both"`, submit to primary and mirror; store **both** UUIDs (primary canonical).
* Optional **cloud endorsement**: POST to the StellaOps cloud `/attest/endorse` with `{uuid, artifactSha256}`; store returned endorsement id.
* Optional **cloud endorsement**: POST to the Stella Ops cloud `/attest/endorse` with `{uuid, artifactSha256}`; store returned endorsement id.
---
## 6) Security model
* **mTLS required** for submission from **Signer** (CApinned).
* **mTLS required** for submission from **Signer** (CA‑pinned).
* **Authority token** with `aud=attestor` and DPoP/mTLS binding must be presented; Attestor verifies both.
* **Bundle acceptance policy**:
* DSSE signature must chain to the configured **Fulcio** (keyless) or **KMS/HSM** roots.
* SAN (Subject Alternative Name) must match **Signer identity** policy (e.g., `urn:stellaops:signer` or pinned OIDC issuer).
* Predicate `predicateType` must be on allowlist (sbom/report/vex-export).
* `subject.digest.sha256` values must be present and wellformed (hex).
* `subject.digest.sha256` values must be present and well‑formed (hex).
* **No public submission** path. **Never** accept bundles from untrusted clients.
* **Client certificate allowlists**: optional `security.mtls.allowedSubjects` / `allowedThumbprints` tighten peer identity checks beyond CA pinning.
* **Rate limits**: token-bucket per caller derived from `quotas.perCaller` (QPS/burst) returns `429` + `Retry-After` when exceeded.
* **Scope enforcement**: API separates `attestor.write`, `attestor.verify`, and `attestor.read` policies; verification/list endpoints accept read or verify scopes while submission endpoints remain write-only.
* **Request hygiene**: JSON content-type is mandatory (415 returned otherwise); DSSE payloads are capped (default 2MiB), certificate chains limited to six entries, and signatures to six per envelope to mitigate parsing abuse.
* **Request hygiene**: JSON content-type is mandatory (415 returned otherwise); DSSE payloads are capped (default 2 MiB), certificate chains limited to six entries, and signatures to six per envelope to mitigate parsing abuse.
* **Redaction**: Attestor never logs secret material; DSSE payloads **should** be public by design (SBOMs/reports). If customers require redaction, enforce policy at Signer (predicate minimization) **before** Attestor.
---
@@ -542,8 +542,8 @@ The worker honours `bulkVerification.itemDelayMilliseconds` for throttling and r
SLO guardrails:
* `attestor.verify_latency_seconds` P95 2s per policy.
* `attestor.verify_total{result="failed"}` 1% of `attestor.verify_total` over 30min rolling windows.
* `attestor.verify_latency_seconds` P95 ≤ 2 s per policy.
* `attestor.verify_total{result="failed"}` ≤ 1 % of `attestor.verify_total` over 30 min rolling windows.
**Correlation**:
@@ -636,7 +636,7 @@ attestor:
---
## 10) Endtoend sequences
## 10) Endâ€toâ€end sequences
**A) Submit & include (happy path)**
@@ -695,19 +695,19 @@ sequenceDiagram
* Stateless; scale horizontally.
* **Targets**:
* Submit+proof P95 **300ms** (warm log; local Rekor).
* Verify P95 **30ms** from cache; **120ms** with live proof fetch.
* Submit+proof P95 ≤ **300 ms** (warm log; local Rekor).
* Verify P95 ≤ **30 ms** from cache; ≤ **120 ms** with live proof fetch.
* 1k submissions/minute per replica sustained.
* **Hot caches**: `dedupe` (bundle hash uuid), recent `entries` by artifact sha256.
* **Hot caches**: `dedupe` (bundle hash → uuid), recent `entries` by artifact sha256.
---
## 13) Testing matrix
* **Happy path**: valid DSSE, inclusion within timeout.
* **Idempotency**: resubmit same `bundleSha256` same `uuid`.
* **Security**: reject nonSigner mTLS, wrong `aud`, DPoP replay, untrusted cert chain, forbidden predicateType.
* **Rekor variants**: promisethenproof, proof delayed, mirror dualsubmit, mirror failure.
* **Idempotency**: resubmit same `bundleSha256` → same `uuid`.
* **Security**: reject non‑Signer mTLS, wrong `aud`, DPoP replay, untrusted cert chain, forbidden predicateType.
* **Rekor variants**: promise‑then‑proof, proof delayed, mirror dual‑submit, mirror failure.
* **Verification**: corrupt leaf path, wrong root, tampered bundle.
* **Throughput**: soak test with 10k submissions; latency SLOs, zero drops.
@@ -718,16 +718,16 @@ sequenceDiagram
* Language: **.NET 10** minimal API; `HttpClient` with **sockets handler** tuned for HTTP/2.
* JSON: **canonical writer** for DSSE payload hashing.
* Crypto: use **BouncyCastle**/**System.Security.Cryptography**; PEM parsing for cert chains.
* Rekor client: pluggable driver; treat backend errors as retryable/nonretryable with granular mapping.
* Safety: size caps on bundles; decompress bombs guarded; strict UTF8.
* Rekor client: pluggable driver; treat backend errors as retryable/non‑retryable with granular mapping.
* Safety: size caps on bundles; decompress bombs guarded; strict UTF‑8.
* CLI integration: `stellaops verify attestation <uuid|bundle|artifact>` calls `/rekor/verify`.
---
## 15) Optional features
* **Duallog** write (primary + mirror) and **crosslog proof** packaging.
* **Cloud endorsement**: send `{uuid, artifactSha256}` to StellaOps cloud; store returned endorsement id for marketing/chainofcustody.
* **Dual‑log** write (primary + mirror) and **cross‑log proof** packaging.
* **Cloud endorsement**: send `{uuid, artifactSha256}` to Stella Ops cloud; store returned endorsement id for marketing/chainâ€ofâ€custody.
* **Checkpoint pinning**: periodically pin latest Rekor checkpoints to an external audit store for independent monitoring.
---
@@ -739,3 +739,54 @@ sequenceDiagram
- Health endpoints: `/health/liveness`, `/health/readiness`, `/status`; verification probe `/api/attestations/verify` once demo bundle is available (see runbook).
- Alert hints: signing latency > 1s p99, verification failure spikes, tlog submission lag >10s, key rotation age over policy threshold, backlog above configured threshold.
---
## 17) Rekor Entry Events
> Sprint: SPRINT_20260112_007_ATTESTOR_rekor_entry_events
Attestor emits deterministic events when DSSE bundles are logged to Rekor and inclusion proofs become available. These events drive policy reanalysis.
### Event Types
| Event Type | Constant | Description |
|------------|----------|-------------|
| `rekor.entry.logged` | `RekorEventTypes.EntryLogged` | Bundle successfully logged with inclusion proof |
| `rekor.entry.queued` | `RekorEventTypes.EntryQueued` | Bundle queued for logging (async mode) |
| `rekor.entry.inclusion_verified` | `RekorEventTypes.InclusionVerified` | Inclusion proof independently verified |
| `rekor.entry.failed` | `RekorEventTypes.EntryFailed` | Logging or verification failed |
### RekorEntryEvent Schema
```jsonc
{
"eventId": "rekor-evt-sha256:...",
"eventType": "rekor.entry.logged",
"tenant": "default",
"bundleDigest": "sha256:abc123...",
"artifactDigest": "sha256:def456...",
"predicateType": "StellaOps.ScanResults@1",
"rekorEntry": {
"uuid": "24296fb24b8ad77a...",
"logIndex": 123456789,
"logUrl": "https://rekor.sigstore.dev",
"integratedTime": "2026-01-15T10:30:02Z"
},
"reanalysisHints": {
"cveIds": ["CVE-2026-1234"],
"productKeys": ["pkg:npm/lodash@4.17.21"],
"mayAffectDecision": true,
"reanalysisScope": "immediate"
},
"occurredAtUtc": "2026-01-15T10:30:05Z"
}
```
### Offline Mode Behavior
When operating in offline/air-gapped mode:
1. Events are not emitted when Rekor is unreachable
2. Bundles are queued locally for later submission
3. Verification uses bundled checkpoints
4. Events are generated when connectivity is restored

View File

@@ -0,0 +1,330 @@
# Break-Glass Account Operations
This document describes the break-glass emergency access mechanism for Stella Ops Authority when normal authentication is unavailable.
## Overview
Break-glass accounts provide emergency administrative access when:
- PostgreSQL database is unavailable
- Identity provider (IdP) is unreachable
- Network partition isolates Authority service
- Disaster recovery scenarios
## Security Model
### Activation Requirements
| Requirement | Description |
|-------------|-------------|
| Reason code | Mandatory selection from approved list |
| Reason details | Free-text justification (logged) |
| Time limit | Maximum 15 minutes per session |
| Extensions | Maximum 2 extensions with re-authentication |
| Alert dispatch | Immediate notification to security team |
### Approved Reason Codes
| Code | Description | Use Case |
|------|-------------|----------|
| `emergency-incident` | Active security incident | Security team responding to breach |
| `database-outage` | PostgreSQL unavailable | DBA performing recovery |
| `security-event` | Proactive security response | Patching critical vulnerability |
| `scheduled-maintenance` | Planned maintenance window | Pre-approved maintenance |
| `disaster-recovery` | DR scenario activation | DR team executing runbook |
## Configuration
### Local Policy File
```yaml
# /etc/stellaops/authority/local-policy.yaml
schemaVersion: "1.0.0"
lastUpdated: "2026-01-15T12:00:00Z"
breakGlass:
enabled: true
accounts:
- id: "break-glass-admin"
name: "Emergency Administrator"
passwordHash: "$argon2id$v=19$m=65536,t=3,p=4$..."
roles: ["admin"]
permissions:
- "authority:*"
- "platform:admin"
- "orch:operate"
sessionTimeoutMinutes: 15
maxExtensions: 2
requireReasonCode: true
allowedReasonCodes:
- "emergency-incident"
- "database-outage"
- "security-event"
- "scheduled-maintenance"
- "disaster-recovery"
- id: "break-glass-readonly"
name: "Emergency Read-Only"
passwordHash: "$argon2id$v=19$m=65536,t=3,p=4$..."
roles: ["auditor"]
permissions:
- "audit:read"
- "obs:incident"
sessionTimeoutMinutes: 30
maxExtensions: 1
requireReasonCode: true
allowedReasonCodes:
- "emergency-incident"
- "security-event"
alerting:
onActivation: true
channels:
- type: "email"
recipients: ["security@company.com", "oncall@company.com"]
- type: "slack"
webhook: "${SLACK_SECURITY_WEBHOOK}"
- type: "pagerduty"
serviceKey: "${PAGERDUTY_SERVICE_KEY}"
```
### Password Generation
```bash
# Generate Argon2id hash for break-glass password
# Use a strong, unique password stored securely offline
# Option 1: Using argon2 CLI
echo -n "StrongBreakGlassPassword123!" | argon2 "$(openssl rand -hex 16)" -id -t 3 -m 16 -p 4 -e
# Option 2: Using Python
python3 << 'EOF'
from argon2 import PasswordHasher
ph = PasswordHasher(time_cost=3, memory_cost=65536, parallelism=4)
hash = ph.hash("StrongBreakGlassPassword123!")
print(hash)
EOF
```
### Secure Storage
Break-glass credentials should be:
1. Stored in a physical safe (not digital-only)
2. Split between multiple custodians (M-of-N)
3. Sealed with tamper-evident packaging
4. Inventoried and audited quarterly
## Activation Procedure
### Step 1: Initiate Break-Glass
```bash
# Via CLI
stella auth break-glass \
--account break-glass-admin \
--reason emergency-incident \
--details "PostgreSQL cluster unreachable, DBA on-call"
# Via API
curl -X POST https://authority.company.com/auth/break-glass \
-H "Content-Type: application/json" \
-d '{
"accountId": "break-glass-admin",
"password": "StrongBreakGlassPassword123!",
"reasonCode": "emergency-incident",
"reasonDetails": "PostgreSQL cluster unreachable, DBA on-call"
}'
```
### Step 2: Receive Session Token
```json
{
"sessionId": "bg-session-abc123",
"token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresAt": "2026-01-15T12:49:56Z",
"permissions": ["authority:*", "platform:admin", "orch:operate"],
"extensionsRemaining": 2
}
```
### Step 3: Perform Emergency Operations
```bash
# Use session token for operations
stella --token "${BG_TOKEN}" system status
stella --token "${BG_TOKEN}" service restart authority
```
### Step 4: Extend Session (If Needed)
```bash
# Extend session before expiration
stella auth break-glass extend \
--session bg-session-abc123 \
--reason "Recovery still in progress"
```
### Step 5: Terminate Session
```bash
# Always explicitly terminate when done
stella auth break-glass terminate \
--session bg-session-abc123 \
--resolution "Database recovered, normal auth restored"
```
## Audit Trail
### Event Types
| Event | Description | Severity |
|-------|-------------|----------|
| `break_glass.activated` | Session started | WARNING |
| `break_glass.extended` | Session extended | WARNING |
| `break_glass.terminated` | Session ended | INFO |
| `break_glass.expired` | Session timed out | WARNING |
| `break_glass.action` | Action performed | INFO |
| `break_glass.denied` | Access denied | ERROR |
### Sample Audit Entry
```json
{
"eventType": "authority.break_glass.activated",
"timestamp": "2026-01-15T12:34:56.789Z",
"severity": "warning",
"session": {
"id": "bg-session-abc123",
"accountId": "break-glass-admin",
"reasonCode": "database-outage",
"reasonDetails": "PostgreSQL cluster unreachable, DBA on-call"
},
"client": {
"ip": "10.0.0.5",
"userAgent": "StellaOps-CLI/2027.Q1"
},
"timing": {
"activatedAt": "2026-01-15T12:34:56Z",
"expiresAt": "2026-01-15T12:49:56Z",
"extensionsRemaining": 2
}
}
```
### Audit Query
```bash
# Query break-glass audit events
stella audit query \
--type "break_glass.*" \
--since "2026-01-01" \
--format json
# Generate break-glass usage report
stella audit report break-glass \
--period monthly \
--output break-glass-report.pdf
```
## Alert Configuration
### Email Template
```
Subject: [ALERT] Break-Glass Access Activated - ${REASON_CODE}
A break-glass account has been activated:
Account: ${ACCOUNT_ID}
Reason: ${REASON_CODE}
Details: ${REASON_DETAILS}
Session ID: ${SESSION_ID}
Activated: ${ACTIVATED_AT}
Expires: ${EXPIRES_AT}
Client IP: ${CLIENT_IP}
This session will automatically expire in 15 minutes.
If this activation was not authorized, take immediate action:
1. Terminate the session: stella auth break-glass terminate --session ${SESSION_ID}
2. Investigate the access attempt
3. Contact Security Operations
```
### Slack Alert
```json
{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "Break-Glass Access Activated"
}
},
{
"type": "section",
"fields": [
{"type": "mrkdwn", "text": "*Account:*\n${ACCOUNT_ID}"},
{"type": "mrkdwn", "text": "*Reason:*\n${REASON_CODE}"},
{"type": "mrkdwn", "text": "*Session:*\n${SESSION_ID}"},
{"type": "mrkdwn", "text": "*Expires:*\n${EXPIRES_AT}"}
]
}
]
}
```
## Testing
### Quarterly Drill
Conduct quarterly break-glass activation drills:
1. Schedule maintenance window
2. Simulate database outage
3. Activate break-glass account
4. Perform test operations
5. Verify audit trail
6. Terminate session
7. Document drill results
### Test Checklist
- [ ] Break-glass activation successful
- [ ] Alerts dispatched correctly
- [ ] Session timeout enforced
- [ ] Extension mechanism works
- [ ] Audit events captured
- [ ] Session termination works
- [ ] Post-drill report generated
## Incident Response
### On Unauthorized Break-Glass Activation
1. **Immediate**: Terminate session
```bash
stella auth break-glass terminate --session ${SESSION_ID} --force
```
2. **Contain**: Disable break-glass temporarily
```bash
stella config set authority.breakGlass.enabled false --apply
```
3. **Investigate**: Query audit logs
```bash
stella audit query --type "break_glass.*" --session ${SESSION_ID}
```
4. **Remediate**: Rotate credentials if compromised
5. **Report**: File incident report per security policy
## Related Documentation
- [Local RBAC Fallback](../local-rbac-fallback.md)
- [Authority Architecture](../architecture.md)
- [Incident Response Playbook](../../security/incident-response.md)

View File

@@ -54,6 +54,9 @@ evidence-{findingId}/
├── README.md # Human-readable documentation
├── sbom.cdx.json # CycloneDX SBOM slice
├── reachability.json # Reachability analysis data
├── binary-diff.json # Binary diff evidence (if available)
├── binary-diff.dsse.json # Signed binary diff envelope (if attested)
├── delta-proof.json # Semantic fingerprint diff summary (if available)
├── vex/
│ ├── vendor.json # Vendor VEX statements
│ ├── nvd.json # NVD VEX data
@@ -322,6 +325,80 @@ done
| `.md` | `text/markdown` | Markdown documentation |
| `.txt` | `text/plain` | Plain text |
## Binary Diff Evidence Files
> Sprint: SPRINT_20260112_009_SCANNER_binary_diff_bundle_export (BINDIFF-SCAN-003)
Evidence bundles may include binary diff files when comparing binary artifacts across versions:
### binary-diff.json
Contains binary diff evidence comparing current and previous binary versions:
```json
{
"status": "available",
"diffType": "semantic",
"previousBinaryDigest": "sha256:abc123...",
"currentBinaryDigest": "sha256:def456...",
"similarityScore": 0.95,
"functionChangeCount": 3,
"securityChangeCount": 1,
"functionChanges": [
{
"functionName": "process_input",
"operation": "modified",
"previousHash": "sha256:...",
"currentHash": "sha256:..."
}
],
"securityChanges": [
{
"changeType": "mitigation_added",
"description": "Stack canaries enabled",
"severity": "info"
}
],
"semanticDiff": {
"previousFingerprint": "fp:abc...",
"currentFingerprint": "fp:def...",
"similarityScore": 0.92,
"semanticChanges": ["control_flow_modified"]
}
}
```
### binary-diff.dsse.json
DSSE-signed wrapper when binary diff evidence is attested:
```json
{
"payloadType": "application/vnd.stellaops.binary-diff+json",
"payload": { /* binary-diff.json content */ },
"attestationRef": {
"id": "attest-12345",
"rekorLogIndex": 123456789,
"bundleDigest": "sha256:..."
}
}
```
### delta-proof.json
Semantic fingerprint summary for quick verification:
```json
{
"previousFingerprint": "fp:abc...",
"currentFingerprint": "fp:def...",
"similarityScore": 0.92,
"semanticChanges": ["control_flow_modified", "data_flow_changed"],
"functionChangeCount": 3,
"securityChangeCount": 1
}
```
## See Also
- [stella scan replay Command Reference](../cli/guides/commands/scan-replay.md)

View File

@@ -431,6 +431,111 @@ correlation: { replaces?: sha256, replacedBy?: sha256 }
* Indexes: `{type:1, occurredAt:-1}`, TTL on `occurredAt` for configurable retention.
### 3.3 VEX Change Events
> Sprint: SPRINT_20260112_006_EXCITITOR_vex_change_events
Excititor emits deterministic VEX change events when statements are added, superseded, or conflict. These events drive policy reanalysis in downstream systems.
#### Event Types
| Event Type | Constant | Description |
|------------|----------|-------------|
| `vex.statement.added` | `VexTimelineEventTypes.StatementAdded` | New VEX statement ingested |
| `vex.statement.superseded` | `VexTimelineEventTypes.StatementSuperseded` | Statement replaced by newer version |
| `vex.statement.conflict` | `VexTimelineEventTypes.StatementConflict` | Conflicting statuses detected |
| `vex.status.changed` | `VexTimelineEventTypes.StatusChanged` | Effective status changed for a product-vulnerability pair |
#### VexStatementChangeEvent Schema
```jsonc
{
"eventId": "vex-evt-sha256:abc123...", // Deterministic hash-based ID
"eventType": "vex.statement.added",
"tenant": "default",
"vulnerabilityId": "CVE-2026-1234",
"productKey": "pkg:npm/lodash@4.17.21",
"newStatus": "not_affected",
"previousStatus": null, // null for new statements
"providerId": "vendor:redhat",
"observationId": "default:redhat:VEX-2026-0001:v1",
"supersededBy": null,
"supersedes": [],
"provenance": {
"documentHash": "sha256:...",
"documentUri": "https://vendor/vex/...",
"sourceTimestamp": "2026-01-15T10:00:00Z",
"author": "security@vendor.com",
"trustScore": 0.95
},
"conflictDetails": null,
"occurredAtUtc": "2026-01-15T10:30:00Z",
"traceId": "trace-xyz789"
}
```
#### VexConflictDetails Schema
When `eventType` is `vex.statement.conflict`:
```jsonc
{
"conflictType": "status_mismatch", // status_mismatch | trust_tie | supersession_conflict
"conflictingStatuses": [
{
"providerId": "vendor:redhat",
"status": "not_affected",
"justification": "CODE_NOT_REACHABLE",
"trustScore": 0.95
},
{
"providerId": "vendor:ubuntu",
"status": "affected",
"justification": null,
"trustScore": 0.85
}
],
"resolutionStrategy": "highest_trust", // or null if unresolved
"autoResolved": false
}
```
#### Event ID Computation
Event IDs are deterministic SHA-256 hashes computed from:
- Event type
- Tenant
- Vulnerability ID
- Product key
- Observation ID
- Occurred timestamp (truncated to seconds)
This ensures idempotent event emission across retries.
#### Policy Engine Integration
Policy Engine subscribes to VEX events to trigger reanalysis:
```yaml
# Policy event subscription
subscriptions:
- event: vex.statement.*
action: reanalyze
filter:
trustScore: { $gte: 0.7 }
- event: vex.statement.conflict
action: queue_for_review
filter:
autoResolved: false
```
#### Emission Ordering
Events are emitted with deterministic ordering:
1. Statement events ordered by `occurredAtUtc` ascending
2. Conflict events emitted after all related statement events
3. Events for the same vulnerability sorted by provider ID
**`vex.consensus`** (optional rollups)
```

View File

@@ -84,3 +84,75 @@ Provide a single, deterministic aggregation layer for cross-service UX workflows
## Gateway exposure
The Platform Service is exposed via Gateway and registered through Router discovery. It does not expose direct ingress outside Gateway in production.
## Setup Wizard
The Platform Service exposes setup wizard endpoints to support first-run configuration and reconfiguration workflows. These endpoints replace UI-mock implementations with real backend state management.
### API surface (v1)
#### Sessions
- `GET /api/v1/setup/sessions` - Get current setup session for tenant
- `POST /api/v1/setup/sessions` - Create new setup session
- `POST /api/v1/setup/sessions/resume` - Resume existing or create new session
- `POST /api/v1/setup/sessions/finalize` - Finalize setup session
#### Steps
- `POST /api/v1/setup/steps/execute` - Execute a setup step (runs Doctor checks)
- `POST /api/v1/setup/steps/skip` - Skip an optional setup step
#### Definitions
- `GET /api/v1/setup/definitions/steps` - List all step definitions
### Setup step identifiers
| Step ID | Title | Required | Depends On |
|---------|-------|----------|------------|
| `Database` | Database Setup | Yes | - |
| `Valkey` | Valkey/Redis Setup | Yes | - |
| `Migrations` | Database Migrations | Yes | Database |
| `Admin` | Admin Bootstrap | Yes | Migrations |
| `Crypto` | Crypto Profile | Yes | Admin |
| `Vault` | Vault Integration | No | - |
| `Scm` | SCM Integration | No | - |
| `Notifications` | Notification Channels | No | - |
| `Environments` | Environment Definition | No | Admin |
| `Agents` | Agent Registration | No | Environments |
### Setup session states
| Status | Description |
|--------|-------------|
| `NotStarted` | Setup not begun |
| `InProgress` | Setup in progress |
| `Completed` | All steps completed |
| `CompletedPartial` | Required steps completed, optional skipped |
| `Failed` | Required step failed |
| `Abandoned` | Setup abandoned by user |
### Setup step states
| Status | Description |
|--------|-------------|
| `Pending` | Not yet started |
| `Current` | Currently active step |
| `Passed` | Completed successfully |
| `Failed` | Validation failed |
| `Skipped` | Explicitly skipped |
| `Blocked` | Blocked by dependency |
### Security and scopes
- Read: `platform.setup.read`
- Write: `platform.setup.write`
- Admin: `platform.setup.admin`
### Offline posture
- Sessions include `DataAsOfUtc` for offline rendering with stale indicators
- Step results cached with Doctor check pass/fail status
- Suggested fixes generated for failed checks
### Related documentation
- UX flow specification: `docs/setup/setup-wizard-ux.md`
- Repository inventory: `docs/setup/setup-wizard-inventory.md`
- Doctor checks: `docs/setup/setup-wizard-doctor-contract.md`

View File

@@ -91,7 +91,49 @@ When receiving `GuardedPass`:
## 4. Determinization Rules
The gate evaluates rules in priority order:
The gate evaluates rules in priority order.
### 4.1 Anchored Evidence Rules (v1.1)
> **Sprint:** SPRINT_20260112_004_BE_policy_determinization_attested_rules
Anchored evidence (DSSE-signed with optional Rekor transparency) takes highest priority in rule evaluation. These rules short-circuit evaluation when cryptographically attested evidence is present.
| Priority | Rule | Condition | Result |
|----------|------|-----------|--------|
| 1 | AnchoredAffectedWithRuntimeHardFail | Anchored VEX affected + anchored runtime telemetry confirms loading | **Blocked** (hard fail) |
| 2 | AnchoredVexNotAffectedAllow | Anchored VEX not_affected or fixed | Pass (short-circuit) |
| 3 | AnchoredBackportProofAllow | Anchored backport proof detected | Pass (short-circuit) |
| 4 | AnchoredUnreachableAllow | Anchored reachability shows unreachable | Pass (short-circuit) |
**Anchor Metadata Fields:**
Evidence anchoring is tracked via these fields on each evidence type:
```json
{
"anchor": {
"anchored": true,
"envelope_digest": "sha256:abc123...",
"predicate_type": "https://stellaops.io/vex/v1",
"rekor_log_index": 12345678,
"rekor_entry_id": "24296fb24b8ad77a...",
"scope": "finding",
"verified": true,
"attested_at": "2026-01-14T12:00:00Z"
}
}
```
Evidence types with anchor support:
- `VexClaimSummary` (via `VexClaimAnchor`)
- `BackportEvidence`
- `RuntimeEvidence`
- `ReachabilityEvidence`
### 4.2 Standard Rules
Standard rules apply when no anchored evidence short-circuits evaluation:
| Priority | Rule | Condition | Result |
|----------|------|-----------|--------|

View File

@@ -538,9 +538,26 @@ Evidence packets can be exported in multiple formats:
| Format | Use Case |
|--------|----------|
| JSON | API consumption, archival |
| SignedJSON | DSSE-signed JSON for verification workflows |
| Markdown | Human-readable documentation |
| HTML | Styled web reports |
| PDF | Human-readable compliance reports |
| CSV | Spreadsheet analysis |
| SLSA | SLSA provenance format |
| **EvidenceCard** | Single-file evidence card with SBOM excerpt, DSSE envelope, and Rekor receipt (v1.1) |
| **EvidenceCardCompact** | Compact evidence card without full SBOM (v1.1) |
### Evidence Card Format (v1.1)
The evidence-card format packages related artifacts into a single JSON file for offline verification:
- **SBOM Excerpt**: Relevant component information from the full SBOM
- **DSSE Envelope**: Dead Simple Signing Envelope containing the signed payload
- **Rekor Receipt**: Optional Sigstore Rekor transparency log receipt for audit trail
Content type: `application/vnd.stellaops.evidence-card+json`
See [Evidence Decision API](../../../api/evidence-decision-api.openapi.yaml) for schema details.
## References

View File

@@ -0,0 +1,334 @@
# Signed SBOM Archive Specification
Version: 1.0.0
Status: Draft
Last Updated: 2026-01-15
## Overview
This specification defines a self-contained, cryptographically signed SBOM archive format that bundles:
- The SBOM document (SPDX or CycloneDX)
- DSSE signature envelope
- Verification materials (certificates, transparency proofs)
- Metadata (tool versions, timestamps)
- Offline verification resources
## Archive Structure
```
signed-sbom-{digest_short}-{timestamp}.tar.gz
|
+-- sbom.spdx.json # OR sbom.cdx.json (CycloneDX)
+-- sbom.dsse.json # DSSE envelope containing signature
+-- manifest.json # Archive inventory with hashes
+-- metadata.json # Generation metadata
+-- certs/
| +-- signing-cert.pem # Signing certificate
| +-- signing-chain.pem # Full certificate chain
| +-- fulcio-root.pem # Fulcio root CA (for keyless)
+-- rekor-proof/ # Optional: transparency log proof
| +-- inclusion-proof.json
| +-- checkpoint.sig
| +-- rekor-public.pem
+-- schemas/ # Bundled validation schemas
| +-- spdx-2.3.schema.json
| +-- spdx-3.0.1.schema.json
| +-- cyclonedx-1.7.schema.json
| +-- dsse.schema.json
+-- VERIFY.md # Human-readable verification guide
```
## File Specifications
### sbom.spdx.json / sbom.cdx.json
The primary SBOM document in either:
- **SPDX**: Versions 2.3 or 3.0.1 (JSON format)
- **CycloneDX**: Versions 1.4, 1.5, 1.6, or 1.7 (JSON format)
Requirements:
- UTF-8 encoding without BOM
- Canonical JSON formatting (RFC 8785 compliant)
- No trailing whitespace or newlines
### sbom.dsse.json
DSSE envelope containing the SBOM signature:
```json
{
"payloadType": "application/vnd.stellaops.sbom+json",
"payload": "<base64-encoded-sbom>",
"signatures": [
{
"keyid": "SHA256:abc123...",
"sig": "<base64-encoded-signature>"
}
]
}
```
### manifest.json
Archive inventory with integrity hashes:
```json
{
"schemaVersion": "1.0.0",
"archiveId": "signed-sbom-abc123-20260115T123456Z",
"generatedAt": "2026-01-15T12:34:56Z",
"files": [
{
"path": "sbom.spdx.json",
"sha256": "abc123...",
"size": 45678,
"mediaType": "application/spdx+json"
},
{
"path": "sbom.dsse.json",
"sha256": "def456...",
"size": 1234,
"mediaType": "application/vnd.dsse+json"
}
],
"merkleRoot": "sha256:789abc...",
"totalFiles": 12,
"totalSize": 98765
}
```
### metadata.json
Generation and tool metadata:
```json
{
"schemaVersion": "1.0.0",
"stellaOps": {
"suiteVersion": "2027.Q1",
"scannerVersion": "1.2.3",
"scannerDigest": "sha256:scanner-image-digest",
"signerVersion": "1.0.0",
"sbomServiceVersion": "1.1.0"
},
"generation": {
"timestamp": "2026-01-15T12:34:56Z",
"hlcTimestamp": "1737000000000000000",
"operator": "build@company.com"
},
"input": {
"imageRef": "registry.company.com/app:v1.0.0",
"imageDigest": "sha256:image-digest-here",
"platform": "linux/amd64"
},
"sbom": {
"format": "spdx-2.3",
"componentCount": 142,
"packageCount": 89,
"fileCount": 1247
},
"signature": {
"type": "keyless",
"issuer": "https://accounts.google.com",
"subject": "build@company.com",
"signedAt": "2026-01-15T12:34:57Z"
},
"reproducibility": {
"deterministic": true,
"expectedDigest": "sha256:expected-sbom-digest"
}
}
```
### VERIFY.md
Human-readable verification instructions:
```markdown
# SBOM Archive Verification
## Quick Verification
```bash
# Verify archive integrity
sha256sum -c <<EOF
abc123... sbom.spdx.json
def456... sbom.dsse.json
EOF
# Verify signature using cosign
cosign verify-blob \
--signature sbom.dsse.json \
--certificate certs/signing-cert.pem \
--certificate-chain certs/signing-chain.pem \
sbom.spdx.json
```
## Offline Verification
```bash
# Using bundled Fulcio root
cosign verify-blob \
--signature sbom.dsse.json \
--certificate certs/signing-cert.pem \
--certificate-chain certs/signing-chain.pem \
--certificate-oidc-issuer https://accounts.google.com \
--offline \
sbom.spdx.json
```
## Rekor Inclusion Proof
```bash
# Verify transparency log inclusion
rekor-cli verify \
--artifact sbom.spdx.json \
--signature sbom.dsse.json \
--public-key certs/signing-cert.pem \
--rekor-server https://rekor.sigstore.dev
```
```
## Cryptographic Requirements
### Hash Algorithms
| Purpose | Algorithm | Format |
|---------|-----------|--------|
| File hashes | SHA-256 | Lowercase hex |
| Merkle tree | SHA-256 | Lowercase hex with `sha256:` prefix |
| Certificate fingerprint | SHA-256 | Uppercase hex with colons |
### Signature Algorithms
Supported signature algorithms:
- **ECDSA-P256**: Recommended for keyless (Fulcio)
- **ECDSA-P384**: High-security environments
- **RSA-PSS-4096**: Legacy compatibility
- **Ed25519**: High-performance signing
### DSSE Envelope
DSSE (Dead Simple Signing Envelope) per specification:
- PAE (Pre-Authentication Encoding) for signing
- Base64 encoding for payload and signatures
- Multiple signatures supported for threshold signing
## Verification Process
### Step 1: Archive Integrity
```python
# Verify tar.gz integrity
import tarfile
import hashlib
with tarfile.open("signed-sbom.tar.gz", "r:gz") as tar:
manifest = json.load(tar.extractfile("manifest.json"))
for file_entry in manifest["files"]:
content = tar.extractfile(file_entry["path"]).read()
actual_hash = hashlib.sha256(content).hexdigest()
assert actual_hash == file_entry["sha256"]
```
### Step 2: Signature Verification
```python
# Verify DSSE signature
from sigstore.verify import Verifier
verifier = Verifier.production()
result = verifier.verify(
artifact=sbom_content,
signature=dsse_envelope,
certificate=signing_cert
)
assert result.success
```
### Step 3: Certificate Chain Validation
```python
# Validate certificate chain
from cryptography import x509
chain = load_certificate_chain("certs/signing-chain.pem")
root = load_certificate("certs/fulcio-root.pem")
validate_chain(chain, root)
```
### Step 4: Transparency Log (Optional)
```python
# Verify Rekor inclusion
from rekor_client import verify_inclusion
result = verify_inclusion(
artifact_hash=sbom_hash,
proof=inclusion_proof,
checkpoint=checkpoint
)
assert result.verified
```
## Compatibility
### SBOM Formats
| Format | Version | Status |
|--------|---------|--------|
| SPDX | 2.3 | Supported |
| SPDX | 3.0.1 | Supported |
| CycloneDX | 1.4 | Supported |
| CycloneDX | 1.5 | Supported |
| CycloneDX | 1.6 | Supported |
| CycloneDX | 1.7 | Supported |
### Compression
| Format | Extension | Status |
|--------|-----------|--------|
| gzip | .tar.gz | Default |
| zstd | .tar.zst | Recommended (smaller) |
| none | .tar | Supported |
## API Endpoint
```
GET /scans/{scanId}/exports/signed-sbom-archive
```
Query Parameters:
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| format | string | spdx-2.3 | SBOM format |
| compression | string | gzip | Compression type |
| includeRekor | bool | true | Include Rekor proof |
| includeSchemas | bool | true | Bundle JSON schemas |
Response Headers:
| Header | Description |
|--------|-------------|
| Content-Type | application/gzip or application/zstd |
| Content-Disposition | attachment; filename="signed-sbom-{digest}.tar.gz" |
| X-SBOM-Digest | SHA-256 of SBOM content |
| X-Archive-Merkle-Root | Merkle root of archive |
| X-Rekor-Log-Index | Rekor log index (if applicable) |
## Security Considerations
1. **Determinism**: All outputs must be reproducible given same inputs
2. **Canonicalization**: JSON must be RFC 8785 canonical before signing
3. **Time sources**: Use injected TimeProvider, not system clock
4. **Key material**: Never include private keys in archives
5. **Offline verification**: Bundle all necessary verification materials
## Related Specifications
- [DSSE Specification](https://github.com/secure-systems-lab/dsse)
- [Sigstore Signing Specification](https://docs.sigstore.dev)
- [SPDX Specification](https://spdx.github.io/spdx-spec/)
- [CycloneDX Specification](https://cyclonedx.org/specification/)
- [Rekor Transparency Log](https://docs.sigstore.dev/rekor/)

View File

@@ -376,6 +376,86 @@ The following metrics are exposed for monitoring:
| `signals_unknowns_scoring_duration_seconds` | Histogram | Scoring computation time |
| `signals_unknowns_band_transitions_total` | Counter | Band changes (e.g., WARM->HOT) |
---
## Runtime Updated Events
> Sprint: SPRINT_20260112_008_SIGNALS_runtime_telemetry_events
When runtime observations change for a CVE and product pair, the Signals module emits `runtime.updated` events to drive policy reanalysis of unknowns.
### Event Types
| Event Type | Constant | Description |
|------------|----------|-------------|
| `runtime.updated` | `RuntimeEventTypes.Updated` | Runtime observations changed for a subject |
| `runtime.ingested` | `RuntimeEventTypes.Ingested` | New runtime observation batch ingested |
| `runtime.confirmed` | `RuntimeEventTypes.Confirmed` | Runtime fact confirmed by additional evidence |
| `runtime.exploit_detected` | `RuntimeEventTypes.ExploitDetected` | Exploit behavior detected at runtime |
### Update Types
| Type | Description |
|------|-------------|
| `NewObservation` | First runtime observation for a subject |
| `StateChange` | Reachability state changed from previous observation |
| `ConfidenceIncrease` | Additional hits increased confidence score |
| `NewCallPath` | Previously unseen call path observed |
| `ExploitTelemetry` | Exploit behavior detected (always triggers reanalysis) |
### Event Schema
```jsonc
{
"eventId": "sha256:abc123...", // Deterministic based on content
"eventType": "runtime.updated",
"version": "1.0.0",
"tenant": "default",
"cveId": "CVE-2026-1234", // Optional
"purl": "pkg:npm/lodash@4.17.21", // Optional
"subjectKey": "cve:CVE-2026-1234|purl:pkg:npm/lodash@4.17.21",
"callgraphId": "cg-scan-001",
"evidenceDigest": "sha256:def456...", // Digest of runtime evidence
"updateType": "NewCallPath",
"previousState": "observed", // Null for new observations
"newState": "observed",
"confidence": 0.85, // 0.0-1.0
"fromRuntime": true,
"runtimeMethod": "ebpf", // "ebpf", "agent", "probe"
"observedNodeHashes": ["sha256:...", "sha256:..."],
"pathHash": "sha256:...", // Optional
"triggerReanalysis": true,
"reanalysisReason": "New call path observed at runtime",
"occurredAtUtc": "2026-01-15T10:30:00Z",
"traceId": "abc123" // Optional correlation ID
}
```
### Reanalysis Triggers
The `triggerReanalysis` flag is set to `true` when:
1. **Exploit telemetry detected** (always triggers)
2. **State change** from previous observation
3. **High-confidence runtime observation** (confidence >= 0.8 and fromRuntime=true)
4. **New observation** (no previous runtime data)
### Event Emission Points
Runtime updated events are emitted from:
1. `RuntimeFactsIngestionService.IngestAsync` - After runtime facts are persisted
2. `ReachabilityScoringService` - When scores are recomputed with new runtime data
### Deterministic Event IDs
Event IDs are computed deterministically using SHA-256 of:
- `subjectKey`
- `evidenceDigest`
- `occurredAtUtc` (ISO 8601 format)
This ensures idempotent event handling and deduplication.
## Related Documentation
- [Unknowns Registry](./unknowns-registry.md) - Data model and API for unknowns

View File

@@ -0,0 +1,247 @@
# Signed VEX Override Workflow
This guide describes how to create and manage signed VEX override decisions using DSSE attestations for audit-grade provenance.
## Overview
VEX (Vulnerability Exploitability eXchange) decisions allow operators to mark vulnerabilities as not-affected, mitigated, or accepted-risk. When attestation signing is enabled, each override produces a DSSE envelope that:
1. Cryptographically binds the decision to the operator's identity
2. Records the decision in an immutable attestation log
3. Optionally anchors the attestation to Sigstore Rekor for transparency
4. Enables downstream policy engines to require signed overrides
## API Endpoints
### Create Signed Override
```http
POST /v1/vex-decisions
Content-Type: application/json
Authorization: Bearer <token>
{
"findingId": "find-abc123",
"status": "NOT_AFFECTED",
"justification": "CODE_NOT_REACHABLE",
"justificationText": "Static analysis confirms code path is unreachable in production configuration",
"scope": {
"environments": ["production"],
"projects": ["myapp"]
},
"validity": {
"notBefore": "2026-01-15T00:00:00Z",
"notAfter": "2026-07-15T00:00:00Z"
},
"attestationOptions": {
"sign": true,
"keyRef": "default-signing-key",
"rekorUpload": true,
"predicateType": "https://stella.ops/predicates/vex-override/v1"
}
}
```
### Response with Attestation Reference
```json
{
"id": "vex-dec-xyz789",
"findingId": "find-abc123",
"status": "NOT_AFFECTED",
"justification": "CODE_NOT_REACHABLE",
"justificationText": "Static analysis confirms code path is unreachable in production configuration",
"createdAt": "2026-01-15T10:30:00Z",
"createdBy": "user@example.com",
"signedOverride": {
"envelopeDigest": "sha256:abc123def456...",
"signatureAlgorithm": "ECDSA_P256_SHA256",
"signedAt": "2026-01-15T10:30:01Z",
"keyId": "default-signing-key",
"rekorInfo": {
"logIndex": 123456789,
"entryId": "24296fb24b8ad77a...",
"integratedTime": "2026-01-15T10:30:02Z",
"logId": "c0d23d6ad406973f..."
},
"verificationStatus": "VERIFIED"
}
}
```
### Update Signed Override
Updates create superseding records while preserving history:
```http
PATCH /v1/vex-decisions/{id}
Content-Type: application/json
Authorization: Bearer <token>
{
"status": "AFFECTED_MITIGATED",
"justification": "COMPENSATING_CONTROLS",
"justificationText": "WAF rule deployed to block exploit vectors",
"attestationOptions": {
"sign": true,
"supersedes": "vex-dec-xyz789"
}
}
```
### List Decisions with Attestation Filter
```http
GET /v1/vex-decisions?signedOnly=true&rekorAnchored=true
```
### Verify Attestation
```http
POST /v1/vex-decisions/{id}/verify
```
Response:
```json
{
"verified": true,
"signatureValid": true,
"rekorEntryValid": true,
"certificateChain": ["CN=signing-key,..."],
"verifiedAt": "2026-01-15T10:35:00Z"
}
```
## CLI Usage
### Create Signed Override
```bash
stella vex create \
--finding find-abc123 \
--status NOT_AFFECTED \
--justification CODE_NOT_REACHABLE \
--reason "Static analysis confirms unreachable" \
--sign \
--key default-signing-key \
--rekor
```
### View Override with Attestation
```bash
stella vex show vex-dec-xyz789 --include-attestation
```
Output:
```
VEX Decision: vex-dec-xyz789
Finding: find-abc123
Status: NOT_AFFECTED
Justification: CODE_NOT_REACHABLE
Created: 2026-01-15T10:30:00Z
Created By: user@example.com
Attestation:
Envelope Digest: sha256:abc123def456...
Algorithm: ECDSA_P256_SHA256
Signed At: 2026-01-15T10:30:01Z
Verification: VERIFIED
Rekor Entry:
Log Index: 123456789
Entry ID: 24296fb24b8ad77a...
Integrated Time: 2026-01-15T10:30:02Z
```
### Verify Override Attestation
```bash
stella vex verify vex-dec-xyz789
```
### Export Override Evidence
```bash
stella vex export vex-dec-xyz789 \
--format bundle \
--output override-evidence.zip
```
## Policy Engine Integration
Signed overrides can be required by policy rules:
```yaml
# Policy requiring signed VEX overrides
rules:
- id: require-signed-vex
condition: |
vex.status in ["NOT_AFFECTED", "AFFECTED_MITIGATED"]
and (vex.signedOverride == null or vex.signedOverride.verificationStatus != "VERIFIED")
action: FAIL
message: "VEX overrides must be signed and verified"
```
## Attestation Predicate Schema
The VEX override predicate follows in-toto attestation format:
```json
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "finding:find-abc123",
"digest": { "sha256": "..." }
}
],
"predicateType": "https://stella.ops/predicates/vex-override/v1",
"predicate": {
"decision": {
"id": "vex-dec-xyz789",
"status": "NOT_AFFECTED",
"justification": "CODE_NOT_REACHABLE",
"justificationText": "...",
"scope": { "environments": ["production"] },
"validity": { "notBefore": "...", "notAfter": "..." }
},
"finding": {
"id": "find-abc123",
"cve": "CVE-2026-1234",
"package": "example-pkg",
"version": "1.2.3"
},
"operator": {
"identity": "user@example.com",
"authorizedAt": "2026-01-15T10:30:00Z"
},
"supersedes": null
}
}
```
## Security Considerations
1. **Key Management**: Signing keys should be managed through Authority with appropriate access controls
2. **Rekor Anchoring**: Enable Rekor upload for public transparency; disable for air-gapped deployments
3. **Expiry**: Set appropriate validity windows; expired overrides surface warnings
4. **Audit Trail**: All signed overrides are recorded in the findings ledger history
## Offline/Air-Gap Mode
For air-gapped deployments:
1. Rekor upload is disabled automatically
2. Attestations are stored locally with envelope digests
3. Verification uses local trust roots
4. Export bundles include all attestation evidence for manual verification
## Related Documentation
- [VEX Consensus Guide](../../../VEX_CONSENSUS_GUIDE.md)
- [Attestor Architecture](../../attestor/architecture.md)
- [Findings Ledger](./findings-ledger.md)
- [Policy Integration](../../policy/guides/vex-trust-model.md)