old sprints work, new sprints for exposing functionality via cli, improve code_of_conduct and other agents instructions
This commit is contained in:
@@ -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) |
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
# component_architecture_attestor.md — **Stella Ops Attestor** (2025Q4)
|
||||
# component_architecture_attestor.md — **Stella Ops Attestor** (2025Q4)
|
||||
|
||||
> Derived from Epic 19 – Attestor Console with provenance hooks aligned to the Export Center bundle workflows scoped in Epic 10.
|
||||
> Derived from Epic 19 – Attestor Console with provenance hooks aligned to the Export Center bundle workflows scoped in Epic 10.
|
||||
|
||||
> **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).
|
||||
> **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 **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.
|
||||
**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 third‑party‑signed 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** (self‑hosted) 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** — 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.
|
||||
* **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: ≥1 000 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()` (24–48h retention).
|
||||
* `audit`: index on `ts` for time‑range 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 (CA‑pinned).
|
||||
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 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.
|
||||
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 Stella Ops 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 pre‑authentication encoding, sign with the resolved provider (default EC, BouncyCastle Ed25519, or File‑KMS 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 (third‑party 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 caller‑provided 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 Stella Ops 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** (CA‑pinned).
|
||||
* **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 well‑formed (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 2 MiB), 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 ≤ 2 s per policy.
|
||||
* `attestor.verify_total{result="failed"}` ≤ 1 % of `attestor.verify_total` over 30 min 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) End‑to‑end sequences
|
||||
## 10) End‑to‑end sequences
|
||||
|
||||
**A) Submit & include (happy path)**
|
||||
|
||||
@@ -695,19 +695,19 @@ sequenceDiagram
|
||||
* Stateless; scale horizontally.
|
||||
* **Targets**:
|
||||
|
||||
* Submit+proof P95 ≤ **300 ms** (warm log; local Rekor).
|
||||
* Verify P95 ≤ **30 ms** from cache; ≤ **120 ms** 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 non‑Signer mTLS, wrong `aud`, DPoP replay, untrusted cert chain, forbidden predicateType.
|
||||
* **Rekor variants**: promise‑then‑proof, proof delayed, mirror dual‑submit, 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/non‑retryable with granular mapping.
|
||||
* Safety: size caps on bundles; decompress bombs guarded; strict UTF‑8.
|
||||
* 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
|
||||
|
||||
* **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.
|
||||
* **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
|
||||
|
||||
330
docs/modules/authority/operations/break-glass-account.md
Normal file
330
docs/modules/authority/operations/break-glass-account.md
Normal 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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
```
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|----------|------|-----------|--------|
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
334
docs/modules/scanner/signed-sbom-archive-spec.md
Normal file
334
docs/modules/scanner/signed-sbom-archive-spec.md
Normal 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/)
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user