From 4dc7cf834ab4d8a9fa37098da69b4fcec47ab797 Mon Sep 17 00:00:00 2001 From: StellaOps Bot Date: Thu, 4 Dec 2025 08:54:32 +0200 Subject: [PATCH] Add sample proof bundle configurations and verification script - Introduced sample proof bundle configuration files for testing, including `sample-proof-bundle-config.dsse.json`, `sample-proof-bundle.dsse.json`, and `sample-proof-bundle.json`. - Implemented a verification script `test_verify_sample.sh` to validate proof bundles against specified schemas and catalogs. - Updated existing proof bundle configurations with new metadata, including versioning, created timestamps, and justification details. - Enhanced evidence entries with expiration dates and hashes for better integrity checks. - Ensured all new configurations adhere to the defined schema for consistency and reliability in testing. --- .gitea/workflows/vex-proof-bundles.yml | 38 +++ docs/AGENTS.md | 50 ++-- docs/advisory-ai/console-fixtures.sha256 | 8 + docs/advisory-ai/console.md | 29 +- docs/benchmarks/vex-evidence-playbook.md | 54 ++-- .../vex-evidence-playbook.schema.json | 225 +++++++++++++++ .../vex-justifications.catalog.dsse.json | 19 ++ .../vex-justifications.catalog.json | 265 ++++++++++++++++++ .../SPRINT_0116_0001_0005_concelier_v.md | 3 + .../SPRINT_0124_0001_0001_policy_reasoning.md | 6 +- .../SPRINT_0136_0001_0001_scanner_surface.md | 7 +- .../SPRINT_0142_0001_0001_sbomservice.md | 10 +- .../SPRINT_0161_0001_0001_evidencelocker.md | 4 +- .../SPRINT_0162_0001_0001_exportcenter_i.md | 4 +- .../SPRINT_0171_0001_0001_notifier_i.md | 4 +- docs/implplan/SPRINT_0209_0001_0001_ui_i.md | 6 +- .../SPRINT_0301_0001_0001_docs_md_i.md | 7 +- ...1_0001_0001_reachability_evidence_chain.md | 6 +- docs/implplan/SPRINT_124_policy_reasoning.md | 42 --- docs/implplan/SPRINT_126_policy_reasoning.md | 19 +- docs/implplan/tasks-all.md | 12 +- .../evidence-locker/bundle-packaging.md | 2 + .../evidence-locker/eb-gaps-161-007-plan.md | 32 +++ .../schemas/bundle.manifest.schema.json | 142 ++++++++++ .../schemas/checksums.schema.json | 47 ++++ .../modules/evidence-locker/verify-offline.md | 51 ++++ docs/modules/export-center/determinism.md | 80 ++++-- docs/modules/export-center/mirror-bundles.md | 9 +- .../operations/verify-export-kit.sh | 99 +++++++ docs/modules/export-center/profiles.md | 12 +- .../export-center/provenance-and-signing.md | 7 +- .../schemas/export-manifest.schema.json | 254 +++++++++++++++++ .../schemas/export-profile.schema.json | 206 ++++++++++++++ docs/modules/export-center/trivy-adapter.md | 21 +- docs/modules/sbomservice/architecture.md | 9 +- scripts/vex/requirements.txt | 2 + scripts/vex/verify_proof_bundle.py | 176 ++++++++++++ .../__fixtures/export-kit/README.md | 10 + .../__fixtures/export-kit/manifest.dsse | 10 + .../__fixtures/export-kit/manifest.json | 142 ++++++++++ .../__fixtures/export-kit/manifest.sha256 | 1 + .../__fixtures/export-kit/provenance.json | 57 ++++ src/Notifier/StellaOps.Notifier/TASKS.md | 1 + .../ConsoleSimulationDiffServiceTests.cs | 6 +- .../SbomMongoStorageTests.cs | 156 +++++++++++ .../StellaOps.SbomService.Tests.csproj | 1 + .../Models/CatalogRecord.cs | 15 + .../Options/SbomMongoOptions.cs | 15 + .../StellaOps.SbomService/Program.cs | 87 ++++-- .../Repositories/FileCatalogRepository.cs | 21 +- .../FileComponentLookupRepository.cs | 2 +- .../Repositories/ICatalogRepository.cs | 5 + .../Repositories/InMemoryCatalogRepository.cs | 19 ++ .../Repositories/MongoCatalogRepository.cs | 138 +++++++++ .../MongoComponentLookupRepository.cs | 80 ++++++ .../Services/InMemorySbomQueryService.cs | 195 +++---------- .../Services/OrchestratorControlService.cs | 1 + .../Services/SbomEvents.cs | 6 +- .../StellaOps.SbomService.csproj | 1 + src/UI/StellaOps.UI/TASKS.md | 1 + tests/EvidenceLocker/Bundles/Golden/README.md | 20 ++ tests/Vex/ProofBundles/cas/config.lock | 2 + tests/Vex/ProofBundles/cas/coverage.json | 16 ++ .../ProofBundles/cas/coverage.json.dsse.json | 19 ++ tests/Vex/ProofBundles/cas/flags.json | 1 + tests/Vex/ProofBundles/cas/graph.json | 18 ++ .../Vex/ProofBundles/cas/graph.json.dsse.json | 19 ++ .../ProofBundles/cas/negative-tests.ndjson | 2 + .../Vex/ProofBundles/cas/runtime-trace.ndjson | 2 + tests/Vex/ProofBundles/openvex-config.json | 28 ++ tests/Vex/ProofBundles/openvex-sample.json | 35 +++ .../sample-proof-bundle-config.dsse.json | 19 ++ .../sample-proof-bundle-config.json | 126 +++++++++ .../sample-proof-bundle.dsse.json | 19 ++ .../Vex/ProofBundles/sample-proof-bundle.json | 126 +++++++++ tests/Vex/ProofBundles/test_verify_sample.sh | 17 ++ 76 files changed, 3051 insertions(+), 355 deletions(-) create mode 100644 .gitea/workflows/vex-proof-bundles.yml create mode 100644 docs/advisory-ai/console-fixtures.sha256 create mode 100644 docs/benchmarks/vex-evidence-playbook.schema.json create mode 100644 docs/benchmarks/vex-justifications.catalog.dsse.json create mode 100644 docs/benchmarks/vex-justifications.catalog.json delete mode 100644 docs/implplan/SPRINT_124_policy_reasoning.md create mode 100644 docs/modules/evidence-locker/eb-gaps-161-007-plan.md create mode 100644 docs/modules/evidence-locker/schemas/bundle.manifest.schema.json create mode 100644 docs/modules/evidence-locker/schemas/checksums.schema.json create mode 100644 docs/modules/evidence-locker/verify-offline.md create mode 100644 docs/modules/export-center/operations/verify-export-kit.sh create mode 100644 docs/modules/export-center/schemas/export-manifest.schema.json create mode 100644 docs/modules/export-center/schemas/export-profile.schema.json create mode 100644 scripts/vex/requirements.txt create mode 100644 scripts/vex/verify_proof_bundle.py create mode 100644 src/ExportCenter/__fixtures/export-kit/README.md create mode 100644 src/ExportCenter/__fixtures/export-kit/manifest.dsse create mode 100644 src/ExportCenter/__fixtures/export-kit/manifest.json create mode 100644 src/ExportCenter/__fixtures/export-kit/manifest.sha256 create mode 100644 src/ExportCenter/__fixtures/export-kit/provenance.json create mode 100644 src/SbomService/StellaOps.SbomService.Tests/SbomMongoStorageTests.cs create mode 100644 src/SbomService/StellaOps.SbomService/Models/CatalogRecord.cs create mode 100644 src/SbomService/StellaOps.SbomService/Options/SbomMongoOptions.cs create mode 100644 src/SbomService/StellaOps.SbomService/Repositories/MongoCatalogRepository.cs create mode 100644 src/SbomService/StellaOps.SbomService/Repositories/MongoComponentLookupRepository.cs create mode 100644 tests/EvidenceLocker/Bundles/Golden/README.md create mode 100644 tests/Vex/ProofBundles/cas/config.lock create mode 100644 tests/Vex/ProofBundles/cas/coverage.json create mode 100644 tests/Vex/ProofBundles/cas/coverage.json.dsse.json create mode 100644 tests/Vex/ProofBundles/cas/flags.json create mode 100644 tests/Vex/ProofBundles/cas/graph.json create mode 100644 tests/Vex/ProofBundles/cas/graph.json.dsse.json create mode 100644 tests/Vex/ProofBundles/cas/negative-tests.ndjson create mode 100644 tests/Vex/ProofBundles/cas/runtime-trace.ndjson create mode 100644 tests/Vex/ProofBundles/openvex-config.json create mode 100644 tests/Vex/ProofBundles/openvex-sample.json create mode 100644 tests/Vex/ProofBundles/sample-proof-bundle-config.dsse.json create mode 100644 tests/Vex/ProofBundles/sample-proof-bundle-config.json create mode 100644 tests/Vex/ProofBundles/sample-proof-bundle.dsse.json create mode 100644 tests/Vex/ProofBundles/sample-proof-bundle.json create mode 100644 tests/Vex/ProofBundles/test_verify_sample.sh diff --git a/.gitea/workflows/vex-proof-bundles.yml b/.gitea/workflows/vex-proof-bundles.yml new file mode 100644 index 000000000..9dd5df896 --- /dev/null +++ b/.gitea/workflows/vex-proof-bundles.yml @@ -0,0 +1,38 @@ +name: VEX Proof Bundles + +on: + pull_request: + paths: + - 'scripts/vex/**' + - 'tests/Vex/ProofBundles/**' + - 'docs/benchmarks/vex-evidence-playbook*' + - '.gitea/workflows/vex-proof-bundles.yml' + push: + branches: [ main ] + paths: + - 'scripts/vex/**' + - 'tests/Vex/ProofBundles/**' + - 'docs/benchmarks/vex-evidence-playbook*' + - '.gitea/workflows/vex-proof-bundles.yml' + +jobs: + verify-bundles: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install deps + run: pip install --disable-pip-version-check --no-cache-dir -r scripts/vex/requirements.txt + + - name: Verify proof bundles (offline) + env: + PYTHONHASHSEED: "0" + run: | + chmod +x tests/Vex/ProofBundles/test_verify_sample.sh + tests/Vex/ProofBundles/test_verify_sample.sh diff --git a/docs/AGENTS.md b/docs/AGENTS.md index b58076872..dbda50f79 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -1,34 +1,26 @@ -# Docs & Enablement Guild +# AGENTS · Documentation Working Directory -## Mission -Produce and maintain offline-friendly documentation for StellaOps modules, covering architecture, configuration, operator workflows, and developer onboarding. +## Scope & Roles +- Working directory: `docs/` (includes `docs/assets/**` fixtures and `docs/api/console/samples/**`). +- Roles: Documentation author (primary), QA/fixtures reviewer, module SMEs (Console/UI, Advisory AI, Policy/Airgap) for accuracy checks. +- Only documentation and fixture assets live here; code changes belong to module repos and must be coordinated via the owning sprint. -## Scope Highlights -- Authority docs (`docs/dev/31_AUTHORITY_PLUGIN_DEVELOPER_GUIDE.md`, upcoming `docs/11_AUTHORITY.md`). -- Concelier quickstarts, CLI guides, Offline Kit manuals. -- Release notes and migration playbooks. +## Required Reading (treat as read before DOING) +- `docs/README.md` and `docs/07_HIGH_LEVEL_ARCHITECTURE.md`. +- Module dossiers relevant to the document being edited (e.g., `docs/modules/advisory-ai/architecture.md`, `docs/modules/ui/architecture.md`, `docs/modules/airgap/architecture.md`, `docs/modules/platform/architecture-overview.md`). +- Active sprint file: `docs/implplan/SPRINT_0301_0001_0001_docs_md_i.md` (Docs Tasks Md.I). -## Operating Principles -- Keep guides deterministic and in sync with shipped configuration samples. -- Prefer tables/checklists for operator steps; flag security-sensitive actions. -- When work involves a specific `StellaOps.` project, consult both `docs/07_HIGH_LEVEL_ARCHITECTURE.md` and the matching dossier `docs/modules//architecture.md` before drafting or editing content. -- Update `docs/TASKS.md` whenever work items change status (TODO/DOING/REVIEW/DONE/BLOCKED). +## Working Agreements +- Determinism: Keep fixtures and captures reproducible. Store payload JSON alongside SVG/PNG captures; record sha256 hashes in the doc and verify with `sha256sum` before publishing. +- Offline posture: Use sealed/fixture data only; no external fonts/CDNs or live calls in regeneration scripts. Capture timestamps in UTC. +- Status discipline: Update task status in the sprint Delivery Tracker (`TODO → DOING → DONE/BLOCKED`) and log changes in the sprint Execution Log. +- Cross-links: When documentation applies a design/advisory change, update the relevant module doc and link it from the sprint’s **Decisions & Risks**. +- Testing: For regeneration scripts, keep them self-contained (stdlib-only) and record expected hashes so QA can diff outputs deterministically. -## Coordination -- Authority Core & Plugin teams for auth-related changes. -- Security Guild for threat-model outputs and mitigations. -- DevEx for tooling diagrams and documentation pipeline. +## Boundaries +- Do not edit source code outside `docs/` without an explicit sprint note. +- Asset placement: use `docs/assets//` for captures and `docs/api//samples/` for JSON fixtures. Name captures `yyyyMMdd-HHmmss--.` in UTC. -## Required Reading -- `docs/README.md` -- `docs/07_HIGH_LEVEL_ARCHITECTURE.md` -- `docs/modules/platform/architecture-overview.md` -- `docs/dev/31_AUTHORITY_PLUGIN_DEVELOPER_GUIDE.md` -- Module-specific README and architecture dossiers for the area you are updating (for example, `docs/modules/concelier/README.md` and `docs/modules/concelier/architecture.md`) - -## Working Agreement -- 1. Update task status to `DOING`/`DONE` in both correspoding sprint file `../implplan/SPRINT_*.md` and the local `TASKS.md` when you start or finish work. -- 2. Review this charter and the Required Reading documents before coding; confirm prerequisites are met. -- 3. Keep changes deterministic (stable ordering, timestamps, hashes) and align with offline/air-gap expectations. -- 4. Coordinate doc updates, tests, and cross-guild communication whenever contracts or workflows change. -- 5. Revert to `TODO` if you pause the task without shipping changes; leave notes in commit/PR descriptions for context. +## Escalation / Blockers +- Missing fixtures or conflicting contracts → mark the task `BLOCKED` in the sprint file, describe the needed artifact or contract in **Decisions & Risks**, then continue with other unblocked work. +- If new advisories land, run the advisory-sync workflow: update high-level docs, deep area docs, add sprint tasks, and carry code samples into fixtures/tests immediately. diff --git a/docs/advisory-ai/console-fixtures.sha256 b/docs/advisory-ai/console-fixtures.sha256 new file mode 100644 index 000000000..49d138118 --- /dev/null +++ b/docs/advisory-ai/console-fixtures.sha256 @@ -0,0 +1,8 @@ +bd85eb2ab4528825c17cd0549b547c2d1a6a5e8ee697a6b4615119245665cc02 docs/api/console/samples/advisory-ai-guardrail-banner.json +57d7bf9ab226b561e19b3e23e3c8d6c88a3a1252c1ea471ef03bf7a237de8079 docs/api/console/samples/vex-statement-sse.ndjson +af3459e8cf7179c264d1ac1f82a968e26e273e7e45cd103c8966d0dd261c3029 docs/api/console/samples/vuln-findings-sample.json +336c55d72abea77bf4557f1e3dcaa4ab8366d79008670d87020f900dcfc833c0 docs/assets/advisory-ai/console/20251203-0000-list-view-build-r2-payload.json +c55217e8526700c2d303677a66351a706007381219adab99773d4728cc61f293 docs/assets/advisory-ai/console/20251203-0000-list-view-build-r2.svg +9bc89861ba873c7f470c5a30c97fb2cd089d6af23b085fba2095e88f8d1f8ede docs/assets/advisory-ai/console/evidence-drawer-b1820ad.svg +f6093257134f38033abb88c940d36f7985b48f4f79870d5b6310d70de5a586f9 docs/samples/console/console-vex-30-001.json +921bcb360454e801bb006a3df17f62e1fcfecaaccda471ae66f167147539ad1e docs/samples/console/console-vuln-29-001.json diff --git a/docs/advisory-ai/console.md b/docs/advisory-ai/console.md index dda60fe11..b2df32411 100644 --- a/docs/advisory-ai/console.md +++ b/docs/advisory-ai/console.md @@ -1,6 +1,6 @@ # Advisory AI Console Workflows -_Last updated: 2025-12-03_ +_Last updated: 2025-12-04_ This guide documents the forthcoming Advisory AI console experience so that console, docs, and QA guilds share a single reference while the new endpoints finish landing. @@ -65,10 +65,24 @@ This guide documents the forthcoming Advisory AI console experience so that cons - VEX statement SSE stream: `docs/api/console/samples/vex-statement-sse.ndjson`. - Guardrail banner projection: `docs/api/console/samples/advisory-ai-guardrail-banner.json` (fixed to valid JSON on 2025-12-03). - Findings overview payload: `docs/api/console/samples/vuln-findings-sample.json`. - - Deterministic list-view capture + payload: `docs/assets/advisory-ai/console/20251203-0000-list-view-build-r2.{svg,json}`. Payload sha256: `336c55d72abea77bf4557f1e3dcaa4ab8366d79008670d87020f900dcfc833c0`; svg sha256: `c55217e8526700c2d303677a66351a706007381219adab99773d4728cc61f293`. + - Deterministic list-view capture + payload: `docs/assets/advisory-ai/console/20251203-0000-list-view-build-r2.{svg,json}` (hashes in table below). - When capturing screenshots, point the console to a dev workspace seeded with the above fixtures and record the build hash displayed in the footer to keep captures reproducible. - Store captures under `docs/assets/advisory-ai/console/` using the scheme `yyyyMMdd-HHmmss--.png` (UTC clock) so regeneration is deterministic. Keep the original JSON alongside each screenshot by saving the response as `…-payload.json` in the same folder. +#### Fixture hashes (run from repo root) +- Verify deterministically: `sha256sum --check docs/advisory-ai/console-fixtures.sha256`. + +| Fixture | sha256 | Notes | +| --- | --- | --- | +| `docs/api/console/samples/advisory-ai-guardrail-banner.json` | `bd85eb2ab4528825c17cd0549b547c2d1a6a5e8ee697a6b4615119245665cc02` | Guardrail ribbon projection. | +| `docs/api/console/samples/vex-statement-sse.ndjson` | `57d7bf9ab226b561e19b3e23e3c8d6c88a3a1252c1ea471ef03bf7a237de8079` | SSE stream sample. | +| `docs/api/console/samples/vuln-findings-sample.json` | `af3459e8cf7179c264d1ac1f82a968e26e273e7e45cd103c8966d0dd261c3029` | Findings overview payload. | +| `docs/assets/advisory-ai/console/20251203-0000-list-view-build-r2-payload.json` | `336c55d72abea77bf4557f1e3dcaa4ab8366d79008670d87020f900dcfc833c0` | List-view sealed payload. | +| `docs/assets/advisory-ai/console/20251203-0000-list-view-build-r2.svg` | `c55217e8526700c2d303677a66351a706007381219adab99773d4728cc61f293` | Deterministic list-view capture. | +| `docs/assets/advisory-ai/console/evidence-drawer-b1820ad.svg` | `9bc89861ba873c7f470c5a30c97fb2cd089d6af23b085fba2095e88f8d1f8ede` | Evidence drawer mock (keep until live capture). | +| `docs/samples/console/console-vex-30-001.json` | `f6093257134f38033abb88c940d36f7985b48f4f79870d5b6310d70de5a586f9` | Console VEX search fixture. | +| `docs/samples/console/console-vuln-29-001.json` | `921bcb360454e801bb006a3df17f62e1fcfecaaccda471ae66f167147539ad1e` | Console vuln search fixture. | + ## 3. Accessibility & offline requirements - Console screens must pass WCAG 2.2 AA contrast and provide focus order that matches the keyboard shortcuts planned for Advisory AI (see `docs/advisory-ai/overview.md`). - All screenshots captured for this doc must come from sealed-mode bundles (no external fonts/CDNs). Store them under `docs/assets/advisory-ai/console/` with hashed filenames. @@ -117,7 +131,7 @@ This guide documents the forthcoming Advisory AI console experience so that cons - **Console wiring** – the guardrail ribbon pulls `guardrail.blocked`, `guardrail.violations`, and `guardrail.metadata.blocked_phrase_count` while the observability cards track `advisory_ai_chunk_requests_total`, `advisory_ai_chunk_cache_hits_total`, and `advisory_ai_guardrail_blocks_total` (now emitted even on cache hits). Use these meters to explain throttling or bad actors before granting additional guardrail budgets, and keep `docs/api/console/samples/advisory-ai-guardrail-banner.json` nearby so QA can validate localized payloads without hitting production data. -## 5. Publication state +## 7. Publication state - [x] Fixture-backed payloads and captures committed (`20251203-0000-list-view-build-r2.svg`, `evidence-drawer-b1820ad.svg`). - [x] Copy-as-ticket flow documented; payload aligns with existing SOC runbooks. - [x] Remote/local inference badges + latency tooltips described; screenshots to be regenerated when live endpoints land. @@ -203,4 +217,13 @@ svg = f"""=95", + "negative_tests", + "config_hash" + ], + "expiry_days": 90, + "reevaluate_on": [ + "sbom_change", + "graph_change", + "runtime_change" + ], + "rbac": [ + "vex-author", + "policy-admin" + ], + "policy_links": [ + "docs/policy/dsl.md#requirevex" + ], + "uncertainty_gate": "U1-low" + }, + { + "id": "VEX2.component_not_present", + "title": "Component not present in runtime image", + "description": "SBOM and runtime inventory confirm the vulnerable component is absent from the shipped artifact.", + "applicability": [ + "not_affected" + ], + "required_evidence": [ + "sbom_digest", + "runtime_inventory", + "config_hash" + ], + "expiry_days": 60, + "reevaluate_on": [ + "sbom_change", + "runtime_change" + ], + "rbac": [ + "vex-author" + ], + "policy_links": [ + "docs/modules/excititor/architecture.md#normalization" + ], + "uncertainty_gate": "U1-low" + }, + { + "id": "VEX3.config_not_vulnerable", + "title": "Configuration disables vulnerable feature", + "description": "Configuration and feature flags disable the vulnerable execution path; enforced by config/flag hashing and negative tests.", + "applicability": [ + "not_affected" + ], + "required_evidence": [ + "config_hash", + "flags_hash", + "negative_tests" + ], + "expiry_days": 45, + "reevaluate_on": [ + "config_change", + "flags_change", + "runtime_change" + ], + "rbac": [ + "vex-author", + "release-manager" + ], + "policy_links": [ + "docs/benchmarks/vex-evidence-playbook.md" + ], + "uncertainty_gate": "U2-medium" + }, + { + "id": "VEX4.vulnerable_code_not_in_execute_path", + "title": "Code not reachable from declared entrypoints", + "description": "Reachability analysis shows no call paths from declared entrypoints to vulnerable functions; runtime probes corroborate.", + "applicability": [ + "not_affected" + ], + "required_evidence": [ + "graph_hash", + "entrypoint_coverage>=95", + "runtime_traces" + ], + "expiry_days": 45, + "reevaluate_on": [ + "graph_change", + "runtime_change" + ], + "rbac": [ + "vex-author", + "signals-operator" + ], + "policy_links": [ + "docs/reachability/function-level-evidence.md" + ], + "uncertainty_gate": "U1-low" + }, + { + "id": "VEX5.mitigated_by_runtime_guard", + "title": "Runtime guard blocks exploitation", + "description": "Exploit is prevented by runtime guardrails (WAF/sandbox/feature flag) proven via negative test and telemetry.", + "applicability": [ + "not_affected", + "affected" + ], + "required_evidence": [ + "runtime_traces", + "negative_tests", + "guard_policy" + ], + "expiry_days": 30, + "reevaluate_on": [ + "runtime_change", + "policy_change" + ], + "rbac": [ + "vex-author", + "security-ops" + ], + "policy_links": [ + "docs/uncertainty/README.md" + ], + "uncertainty_gate": "U2-medium" + }, + { + "id": "VEX6.compensating_control_documented", + "title": "Compensating control accepted", + "description": "A documented compensating control reduces exploitability; requires approval evidence and expiry.", + "applicability": [ + "affected", + "under_investigation" + ], + "required_evidence": [ + "control_record", + "rbac_approval", + "expiry" + ], + "expiry_days": 30, + "reevaluate_on": [ + "policy_change", + "expiry" + ], + "rbac": [ + "policy-admin", + "risk-owner" + ], + "policy_links": [ + "docs/migration/exception-governance.md" + ], + "uncertainty_gate": "U3-high" + }, + { + "id": "VEX7.update_available", + "title": "Update available and staged", + "description": "Fix is available and staged for rollout; VEX documents status and planned activation window.", + "applicability": [ + "affected", + "fixed" + ], + "required_evidence": [ + "fixed_version", + "staging_hash", + "rollout_window" + ], + "expiry_days": 15, + "reevaluate_on": [ + "rollout_change" + ], + "rbac": [ + "release-manager" + ], + "policy_links": [ + "docs/ui/advisories-and-vex.md" + ], + "uncertainty_gate": "U2-medium" + }, + { + "id": "VEX8.analysis_ongoing", + "title": "Analysis ongoing with SLA", + "description": "Investigation underway with defined SLA and evidence collection plan.", + "applicability": [ + "under_investigation" + ], + "required_evidence": [ + "investigation_plan", + "sla_date", + "owner" + ], + "expiry_days": 7, + "reevaluate_on": [ + "sla_date" + ], + "rbac": [ + "vex-author" + ], + "policy_links": [ + "docs/modules/excititor/architecture.md#normalization" + ], + "uncertainty_gate": "U3-high" + }, + { + "id": "VEX9.eol_not_applicable", + "title": "Product out of scope / EOL", + "description": "Asset is out of scope or end-of-life and isolated; policy enforces quarantine rather than blanket ignore.", + "applicability": [ + "not_affected" + ], + "required_evidence": [ + "asset_scope", + "quarantine_policy", + "rbac_approval" + ], + "expiry_days": 30, + "reevaluate_on": [ + "asset_change" + ], + "rbac": [ + "policy-admin" + ], + "policy_links": [ + "docs/observability/policy.md" + ], + "uncertainty_gate": "U2-medium" + }, + { + "id": "VEX10.false_positive_proven", + "title": "Scanner false positive disproven", + "description": "Deterministic reproduction shows the vulnerability is not actually present; includes counter-evidence and replay seed.", + "applicability": [ + "not_affected" + ], + "required_evidence": [ + "replay_manifest", + "negative_tests", + "sbom_digest" + ], + "expiry_days": 45, + "reevaluate_on": [ + "scanner_update", + "sbom_change" + ], + "rbac": [ + "vex-author", + "qa" + ], + "policy_links": [ + "docs/replay/DETERMINISTIC_REPLAY.md" + ], + "uncertainty_gate": "U1-low" + } + ] +} diff --git a/docs/implplan/SPRINT_0116_0001_0005_concelier_v.md b/docs/implplan/SPRINT_0116_0001_0005_concelier_v.md index 3487addf4..988c4d06e 100644 --- a/docs/implplan/SPRINT_0116_0001_0005_concelier_v.md +++ b/docs/implplan/SPRINT_0116_0001_0005_concelier_v.md @@ -43,10 +43,12 @@ | 13 | CONCELIER-WEB-OAS-63-001 | BLOCKED | Depends on 62-001 | WebService · API Governance | Emit deprecation headers/notifications steering clients to LNM APIs. | | 14 | CONCELIER-WEB-OBS-51-001 | DONE (2025-11-23) | Schema 046_TLTY0101 published 2025-11-23 | WebService Guild | `/obs/concelier/health` for ingest health/queue/SLO status. | | 15 | CONCELIER-WEB-OBS-52-001 | DONE (2025-11-24) | Depends on 51-001 | WebService Guild | SSE `/obs/concelier/timeline` with paging tokens, audit logging. | +| 16 | CONCELIER-AIAI-31-002 | BLOCKED (2025-12-04) | No linkset store/cache backend exists; choose Mongo vs Postgres target and add cache collection/index + read-through wiring. | Concelier Core · Concelier WebService Guilds | Implement Link-Not-Merge linkset cache per `docs/modules/concelier/operations/lnm-cache-plan.md`, expose read-through on `/v1/lnm/linksets`, add metrics `lnm.cache.*`, and cover with deterministic tests. | ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | +| 2025-12-04 | Added CONCELIER-AIAI-31-002 to Delivery Tracker and marked BLOCKED; cache plan exists but no linkset store/cache backend (Mongo/Postgres) is registered, so Link-Not-Merge cache cannot be implemented yet. | Project Mgmt | | 2025-12-03 | Added Wave Coordination (A observability done; B AirGap blocked; C AOC regression blocked on validator; D OAS alignment blocked). No status changes. | Project Mgmt | | 2025-11-25 | AOC validator (WEB-AOC-19-002) missing; blocked chain noted. | Implementer | | 2025-11-23 | OBS-52-001 done: SSE timeline stream shipped; audit logging active. | WebService | @@ -58,6 +60,7 @@ - AirGap tasks blocked until sealed-mode + staleness metadata defined; do not expose bundles without provenance. - AOC regression chain blocked pending validator (WEB-AOC-19-002); large-batch tests must wait. - OAS envelope change (WEB-OAS-61-002) is a prereq for examples/deprecation; avoid duplicating client envelopes until unified. +- Linkset cache (CONCELIER-AIAI-31-002) cannot proceed until a concrete store exists (Mongo vs Postgres) and cache collection/index contract is picked; current services register only `NullLinksetLookup`, so `/v1/lnm/linksets` lacks cache backing. ## Next Checkpoints - None scheduled; add when validator and AirGap prerequisites land. diff --git a/docs/implplan/SPRINT_0124_0001_0001_policy_reasoning.md b/docs/implplan/SPRINT_0124_0001_0001_policy_reasoning.md index f8e6508e1..296fbee38 100644 --- a/docs/implplan/SPRINT_0124_0001_0001_policy_reasoning.md +++ b/docs/implplan/SPRINT_0124_0001_0001_policy_reasoning.md @@ -49,6 +49,7 @@ ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | +| 2025-12-04 | Removed deprecated duplicate sprint file `docs/implplan/SPRINT_124_policy_reasoning.md`; canonical remains `docs/implplan/SPRINT_0124_0001_0001_policy_reasoning.md`. Updated console diff determinism test to compare serialized payloads; reran `dotnet test src/Policy/__Tests/StellaOps.Policy.Engine.Tests/StellaOps.Policy.Engine.Tests.csproj -c Release --filter ConsoleSimulationDiffServiceTests` (pass: 1/1, 0.5s). | Implementer | | 2025-12-03 | Added Wave Coordination (Wave A core/storages done; Wave B Console simulation/export done; no open tasks). No status changes. | Project Mgmt | | 2025-12-02 | Fixed selection join canonical ordering (PurlEquivalence canonical now derived from sorted list, not hashset); `dotnet test src/Policy/__Tests/StellaOps.Policy.Engine.Tests/StellaOps.Policy.Engine.Tests.csproj -c Release --no-build` now passes (211/211, 5.0s). | Implementer | | 2025-12-02 | Reran `dotnet test src/Policy/__Tests/StellaOps.Policy.Engine.Tests/StellaOps.Policy.Engine.Tests.csproj -c Release --no-build`; failed 1/211 tests (`SelectionJoinTests.GetCanonical_ReturnsFirstLexicographically` expected `pkg:npm/a-package` but returned `pkg:npm/b-package`). Build duration 13.5s. Needs follow-up fix. | Implementer | @@ -70,11 +71,12 @@ | 2025-11-22 | Marked all PREP tasks to DONE per directive; evidence to be verified. | Project Mgmt | ## Decisions & Risks +- 2025-12-04: Console simulation diff determinism test now asserts serialized equality; targeted test run succeeded (1/1). Duplicate sprint file removed to enforce single source of truth. - 2025-12-02: POLICY-CONSOLE-23-001 contract published (`docs/modules/policy/contracts/policy-console-23-001-console-api.md`); POLICY-CONSOLE-23-002 unblocked—implement per contract with deterministic cursors/aggregates. - 2025-12-02: Selection join canonical ordering fixed (lexicographic first via ordered list). Regression resolved; full Policy Engine tests now passing. - Release test suite for Policy Engine now green (2025-12-02); keep enforcing deterministic inputs (explicit evaluationTimestamp) on batch evaluation requests to avoid non-deterministic clocks. - 2025-12-02: Targeted test run for new Console diff endpoint aborted after prolonged initial build; rerun `dotnet test src/Policy/__Tests/StellaOps.Policy.Engine.Tests/StellaOps.Policy.Engine.Tests.csproj -c Release --filter ConsoleSimulationDiffServiceTests` once build cache is warm. ## Next Checkpoints -- Provide Console export/simulation contract for 23-001 to unblock 23-002. -- Rerun `dotnet test src/Policy/__Tests/StellaOps.Policy.Engine.Tests` after workspace cleanup; capture results in Execution Log. +- No pending checkpoints; Console contract delivered and console diff regression test rerun on 2025-12-04. +- Monitor future advisories or Console contract deltas; open new wave if scope changes. diff --git a/docs/implplan/SPRINT_0136_0001_0001_scanner_surface.md b/docs/implplan/SPRINT_0136_0001_0001_scanner_surface.md index 1e433d6a1..81ee4aa07 100644 --- a/docs/implplan/SPRINT_0136_0001_0001_scanner_surface.md +++ b/docs/implplan/SPRINT_0136_0001_0001_scanner_surface.md @@ -59,7 +59,7 @@ | 38 | SURFACE-FS-06 | DONE (2025-11-28) | SURFACE-FS-02..05 | Docs Guild | Update scanner-engine guide and offline kit docs with Surface.FS workflow. | | 39 | SCANNER-SURFACE-01 | BLOCKED (2025-11-25) | Task definition absent | Scanner Guild | Placeholder task; scope/contract required before implementation. | | 40 | SCANNER-SURFACE-04 | DONE (2025-12-02) | SCANNER-SURFACE-01, SURFACE-FS-03 | Scanner Worker Guild (`src/Scanner/StellaOps.Scanner.Worker`) | DSSE-sign every `layer.fragments` payload, emit `_composition.json`/`composition.recipe` URI, and persist DSSE envelopes for deterministic offline replay (see `deterministic-sbom-compose.md` §2.1). | -| 41 | SURFACE-FS-07 | TODO | SCANNER-SURFACE-04 | Scanner Guild (`src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS`) | Extend Surface.FS manifest schema with `composition.recipe`, fragment attestation metadata, and verification helpers per deterministic SBOM spec (legacy TODO; superseded by row 42). | +| 41 | SURFACE-FS-07 | DONE (2025-12-02, superseded by #42) | SCANNER-SURFACE-04 | Scanner Guild (`src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS`) | Extend Surface.FS manifest schema with `composition.recipe`, fragment attestation metadata, and verification helpers per deterministic SBOM spec (legacy TODO; superseded by row 42). | | 42 | SURFACE-FS-07 | DONE (2025-12-02) | SCANNER-SURFACE-04 | Scanner Guild | Surface.FS manifest schema carries composition recipe/DSSE attestations and determinism metadata; determinism verifier added for offline replay. | | 43 | SCANNER-EMIT-15-001 | DONE (2025-12-02) | SCANNER-SURFACE-04 | Scanner Emit Guild (`src/Scanner/__Libraries/StellaOps.Scanner.Emit`) | CycloneDX artifacts carry content hash + Merkle root (= recipe hash), composition recipe URI, emit `_composition.json` + DSSE envelopes; DSSE signing now uses HMAC secret (Surface.Secrets or appsettings) with deterministic fallback logging. | | 44 | SCANNER-SORT-02 | DONE (2025-12-01) | SCANNER-EMIT-15-001 | Scanner Core Guild (`src/Scanner/__Libraries/StellaOps.Scanner.Core`) | Layer fragment ordering by digest implemented in ComponentGraphBuilder; determinism regression test added. | @@ -72,6 +72,7 @@ ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | +| 2025-12-04 | Ran `dotnet test` for `StellaOps.Scanner.Surface.FS.Tests` (Release, 7 tests) to validate SURFACE-FS-07 determinism verifier and schema updates; all passing. | Implementer | | 2025-12-02 | Merged legacy `SPRINT_136_scanner_surface.md` content into canonical file; added missing tasks/logs; converted legacy file to stub to prevent divergence. | Project Mgmt | | 2025-12-02 | SCANNER-SURFACE-04 completed: manifest stage emits composition recipe + DSSE envelopes, attaches attestations to artifacts, and records determinism Merkle root/recipe metadata. | Implementer | | 2025-12-02 | SURFACE-FS-07 completed: Surface.FS manifest schema now includes determinism metadata, composition recipe attestation fields, determinism verifier, and docs updated. Targeted determinism tests added; test run pending due to long restore/build in monorepo runner. | Implementer | @@ -128,12 +129,12 @@ | 2025-10-26 | Initial sprint plan captured; dependencies noted across Scheduler/Surface/Cartographer. | Planning | ## Decisions & Risks - - SCANNER-LNM-21-001 delivered with Concelier shared-library resolver; linkset enrichment returns data when Concelier linkset store is configured, otherwise responses omit the `linksets` field (fallback null provider). +- SCANNER-LNM-21-001 delivered with Concelier shared-library resolver; linkset enrichment returns data when Concelier linkset store is configured, otherwise responses omit the `linksets` field (fallback null provider). - SURFACE-SECRETS-06 BLOCKED pending Ops Helm/Compose patterns for Surface.Secrets provider configuration (kubernetes/file/inline). - SCANNER-EVENTS-16-301 BLOCKED awaiting orchestrator envelope contract + Notifier ingestion test plan. - SCANNER-SURFACE-01 lacks scoped contract; placeholder must be defined or retired before new dependencies are added. - SCANNER-EMIT-15-001 DOING: HMAC-backed DSSE signer added with deterministic fallback; enable by providing `Scanner:Worker:Signing:SharedSecret` (or file) + `KeyId`. Full scanner test suite still pending after cancelled long restore/build. -- Long restore/build times in monorepo runners delayed determinism test runs for SURFACE-FS-07 and new signer; rerun targeted scanner worker tests in CI. +- Long restore/build times in monorepo runners delayed determinism test runs for SURFACE-FS-07 and new signer; Surface.FS determinism tests now passing locally (Release); broader scanner suite still pending in CI. - Scheduler worker build/tests not run locally after manifest prefetch wiring (NuGet restore timeout); verify in CI. ## Next Checkpoints diff --git a/docs/implplan/SPRINT_0142_0001_0001_sbomservice.md b/docs/implplan/SPRINT_0142_0001_0001_sbomservice.md index a95d2bf7a..06f272544 100644 --- a/docs/implplan/SPRINT_0142_0001_0001_sbomservice.md +++ b/docs/implplan/SPRINT_0142_0001_0001_sbomservice.md @@ -26,7 +26,7 @@ | 2 | SBOM-AIAI-31-002 | DONE | Metrics + cache-hit tagging implemented; Grafana starter dashboard added; build/test completed locally. | SBOM Service Guild; Observability Guild | Instrument metrics for path/timeline queries and surface dashboards. | | 3 | SBOM-CONSOLE-23-001 | DONE (2025-12-03) | DEVOPS-SBOM-23-001 feed delivered; console catalog endpoint implemented and tested (`dotnet test ... --filter Console_`). | SBOM Service Guild; Cartographer Guild | Provide Console-focused SBOM catalog API. | | 4 | SBOM-CONSOLE-23-002 | DONE (2025-12-03) | Component lookup endpoint validated (tests passing with pagination/filtering); using vetted feed and seeded data until storage wiring lands. | SBOM Service Guild | Deliver component lookup endpoints for search and overlays. | -| 16 | SBOM-CONSOLE-23-101-STORAGE | TODO | Follow-up to replace seeded catalog/component lookup with Mongo-backed storage and update docs/tests. | SBOM Service Guild | Wire console catalog + component lookup to storage/outbox and refresh fixtures/docs for release. | +| 16 | SBOM-CONSOLE-23-101-STORAGE | DONE (2025-12-04) | Follow-up to replace seeded catalog/component lookup with Mongo-backed storage and update docs/tests. | SBOM Service Guild | Wire console catalog + component lookup to storage/outbox and refresh fixtures/docs for release. | | 5 | SBOM-ORCH-32-001 | DONE (2025-11-23) | In-memory orchestrator source registry with deterministic seeds + idempotent registration exposed at `/internal/orchestrator/sources`. | SBOM Service Guild | Register SBOM ingest/index sources with orchestrator. | | 6 | SBOM-ORCH-33-001 | DONE (2025-11-23) | Pause/throttle/backpressure controls added via `/internal/orchestrator/control`; metrics emitted; states deterministic per-tenant. | SBOM Service Guild | Report backpressure metrics and handle orchestrator control signals. | | 7 | SBOM-ORCH-34-001 | DONE (2025-11-23) | Watermark store + endpoints (`/internal/orchestrator/watermarks`) added to track backfill/watermark reconciliation; deterministic ordering. | SBOM Service Guild | Implement orchestrator backfill + watermark reconciliation. | @@ -52,6 +52,8 @@ ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | +| 2025-12-04 | SBOM-CONSOLE-23-101-STORAGE marked DONE: Mongo-backed catalog + component lookup with configurable collections; docs updated; tests (`dotnet test src/SbomService/StellaOps.SbomService.Tests/StellaOps.SbomService.Tests.csproj --nologo`) pass. | Implementer | +| 2025-12-04 | SBOM-CONSOLE-23-101-STORAGE moved to DOING; starting Mongo-backed wiring for console catalog/component lookup. | Project Mgmt | | 2025-12-03 | SBOM-CONSOLE-23-002 marked DONE after component lookup pagination/filter tests (`dotnet test ... --filter Console_|Components_lookup_requires_purl_and_paginates --no-build`) passed; endpoint validated with vetted feed + seeded data. | Project Mgmt | | 2025-12-03 | SBOM-CONSOLE-23-001 marked DONE after console endpoint tests (`dotnet test src/SbomService/StellaOps.SbomService.Tests/StellaOps.SbomService.Tests.csproj --no-build --filter Console_`) passed. SBOM-CONSOLE-23-002 moved to DOING. | Project Mgmt | | 2025-12-03 | Ran targeted console endpoint test (`dotnet test ... --filter Console_sboms_supports_filters_and_cursor --no-build`); passes. SBOM-CONSOLE-23-001 remains DOING. | Implementer | @@ -111,8 +113,8 @@ ## Decisions & Risks - LNM v1 fixtures staged (2025-11-22) and approved; hash recorded in `docs/modules/sbomservice/fixtures/lnm-v1/SHA256SUMS`. SBOM-SERVICE-21-001/002/003/004 are DONE. - - DEVOPS-SBOM-23-001 delivered 2025-11-30 (Sprint 503) providing vetted offline feed + CI proof; SBOM-CONSOLE-23-001 and SBOM-CONSOLE-23-002 are DONE (2025-12-03) using vetted feed + seeded data; storage-backed version still to follow. - - Console endpoints validated via tests; current implementation uses in-memory/catalog seeds—replace with Mongo-backed projections before release and update docs accordingly. Track storage wiring as follow-up (new task below). + - DEVOPS-SBOM-23-001 delivered 2025-11-30 (Sprint 503) providing vetted offline feed + CI proof; SBOM-CONSOLE-23-001 and SBOM-CONSOLE-23-002 are DONE (2025-12-03) using vetted feed + seeded data. + - SBOM-CONSOLE-23-101-STORAGE (2025-12-04): `/console/sboms` and `/components/lookup` now use Mongo-backed repositories when `SbomService:Mongo:ConnectionString` is set (configurable database/collection names); fallback to fixture/in-memory seeds remains for air-gapped runs. Docs updated in `docs/modules/sbomservice/architecture.md`. - Projection endpoint validated (400 without tenant, 200 with fixture data) via WebApplicationFactory; WAF configured with fixture path + in-memory component repo fallback. - `sbom.version.created` now emitted via in-memory publisher with `/internal/sbom/events` + backfill endpoint; production outbox/queue wiring still required before release. - Component lookup pagination now returns deterministic `nextCursor` for seeded data (fixed null cursor bug). @@ -123,8 +125,6 @@ - Orchestrator control/backpressure/watermarks implemented in-memory; replace with real orchestrator contract before release. - Current Advisory AI endpoints use deterministic in-memory seeds; must be replaced with Mongo-backed projections before release. - Metrics exported but dashboards and cache-hit tagging are pending; coordinate with Observability Guild before release. -- Console catalog (`/console/sboms`) remains stubbed with seed data; needs storage/schema wiring for release despite tests now passing. -- Component lookup endpoint is stubbed; SBOM-CONSOLE-23-002 remains blocked on storage wiring rather than build/test infra. - SBOM-AIAI-31-002 stays pending dashboards + validated metrics; feeds/builds now healthy after offline cache fixes. - `AGENTS.md` for `src/SbomService` added 2025-11-18; implementers must read before coding. - AirGap parity review template published at `docs/modules/sbomservice/runbooks/airgap-parity-review.md`; review execution still required for air-gapped signoff on SBOM-SERVICE-21-002..004 (21-001 implementation validated locally). diff --git a/docs/implplan/SPRINT_0161_0001_0001_evidencelocker.md b/docs/implplan/SPRINT_0161_0001_0001_evidencelocker.md index 3c8d7dfc8..a4ad7cdfd 100644 --- a/docs/implplan/SPRINT_0161_0001_0001_evidencelocker.md +++ b/docs/implplan/SPRINT_0161_0001_0001_evidencelocker.md @@ -36,7 +36,7 @@ | 4 | RUNBOOK-REPLAY-187-004 | BLOCKED | PREP-RUNBOOK-REPLAY-187-004-DEPENDS-ON-RETENT | Docs Guild · Ops Guild | Publish `/docs/runbooks/replay_ops.md` coverage for retention enforcement, RootPack rotation, verification drills. | | 5 | CRYPTO-REGISTRY-DECISION-161 | DONE | Decision recorded in `docs/security/crypto-registry-decision-2025-11-18.md`; publish contract defaults. | Security Guild · Evidence Locker Guild | Capture decision from 2025-11-18 review; emit changelog + reference implementation for downstream parity. | | 6 | EVID-CRYPTO-90-001 | DONE | Implemented; `MerkleTreeCalculator` now uses `ICryptoProviderRegistry` for sovereign crypto routing. | Evidence Locker Guild · Security Guild | Route hashing/signing/bundle encryption through `ICryptoProviderRegistry`/`ICryptoHash` for sovereign crypto providers. | -| 7 | EVID-GAPS-161-007 | TODO | None; informs tasks 1–6. | Product Mgmt · Evidence Locker Guild · CLI Guild | Address EB1–EB10 from `docs/product-advisories/28-Nov-2025 - Evidence Bundle and Replay Contracts.md`: publish `bundle.manifest.schema.json` + `checksums.schema.json` (canonical JSON), hash/Merkle recipe doc, mandatory DSSE predicate/log policy, replay provenance block, chunking/CAS rules, incident-mode signed activation/exit, tenant isolation + redaction manifest, offline verifier script (`docs/modules/evidence-locker/verify-offline.md`), golden bundles/replay fixtures under `tests/EvidenceLocker/Bundles/Golden`, and SemVer/change-log updates. | +| 7 | EVID-GAPS-161-007 | DOING (2025-12-04) | See EB1–EB10 plan `docs/modules/evidence-locker/eb-gaps-161-007-plan.md`; schemas + offline guide drafted. | Product Mgmt · Evidence Locker Guild · CLI Guild | Address EB1–EB10 from `docs/product-advisories/28-Nov-2025 - Evidence Bundle and Replay Contracts.md`: publish `bundle.manifest.schema.json` + `checksums.schema.json` (canonical JSON), hash/Merkle recipe doc, mandatory DSSE predicate/log policy, replay provenance block, chunking/CAS rules, incident-mode signed activation/exit, tenant isolation + redaction manifest, offline verifier script (`docs/modules/evidence-locker/verify-offline.md`), golden bundles/replay fixtures under `tests/EvidenceLocker/Bundles/Golden`, and SemVer/change-log updates. | ## Action Tracker | Action | Owner(s) | Due | Status | @@ -59,6 +59,7 @@ | Schema readiness | BLOCKED | Waiting on AdvisoryAI + orchestrator envelopes; no DOING until frozen. | | Crypto routing approval | DONE | Defaults recorded in `docs/security/crypto-registry-decision-2025-11-18.md`; implement in EvidenceLocker/CLI. | | Template & filename normalization | DONE (2025-11-17) | Renamed to `SPRINT_0161_0001_0001_evidencelocker.md`; structure aligned to sprint template. | +| EB1–EB10 policy freeze | OPEN | Gap plan at `docs/modules/evidence-locker/eb-gaps-161-007-plan.md`; DSSE predicate/log policy, redaction map, and chunking rules still need sign-off. | ### Risk table | Risk | Severity | Mitigation / Owner | @@ -88,3 +89,4 @@ | 2025-11-27 | Completed EVID-CRYPTO-90-001: Extended `ICryptoProviderRegistry` with `ContentHashing` capability and `ResolveHasher` method; created `ICryptoHasher` interface with `DefaultCryptoHasher` implementation; wired `MerkleTreeCalculator` to use crypto registry for sovereign crypto routing; added `EvidenceCryptoOptions` for algorithm/provider configuration. | Implementer | | 2025-12-01 | Added EVID-GAPS-161-007 to capture EB1–EB10 remediation from `docs/product-advisories/28-Nov-2025 - Evidence Bundle and Replay Contracts.md`. | Product Mgmt | | 2025-12-02 | Scoped EVID-GAPS-161-007 deliverables: schemas + DSSE, Merkle recipe, replay provenance, chunk/CAS rules, incident governance, tenant redaction, offline verifier doc, golden fixtures path, and SemVer/change-log updates. | Project Mgmt | +| 2025-12-04 | Moved EVID-GAPS-161-007 to DOING; drafted EB1/EB2 schemas, offline verifier guide, gap plan, and golden fixtures path. | Project Mgmt | diff --git a/docs/implplan/SPRINT_0162_0001_0001_exportcenter_i.md b/docs/implplan/SPRINT_0162_0001_0001_exportcenter_i.md index 724ced0e1..ffd7bbb1c 100644 --- a/docs/implplan/SPRINT_0162_0001_0001_exportcenter_i.md +++ b/docs/implplan/SPRINT_0162_0001_0001_exportcenter_i.md @@ -51,7 +51,7 @@ | 10 | EXPORT-OAS-61-001 | BLOCKED | PREP-EXPORT-OAS-61-001-NEEDS-STABLE-EXPORT-SU | Exporter Service Guild · API Contracts Guild | Update Exporter OAS covering profiles/runs/downloads with standard error envelope + examples. | | 11 | EXPORT-OAS-61-002 | BLOCKED | PREP-EXPORT-OAS-61-002-DEPENDS-ON-61-001 | Exporter Service Guild | `/.well-known/openapi` discovery endpoint with version metadata and ETag. | | 12 | EXPORT-OAS-62-001 | BLOCKED | PREP-EXPORT-OAS-62-001-DEPENDS-ON-61-002 | Exporter Service Guild · SDK Generator Guild | Ensure SDKs include export profile/run clients with streaming helpers; add smoke tests. | -| 13 | EXPORT-GAPS-162-013 | TODO | None; informs tasks 1–12. | Product Mgmt · Exporter Guild · Evidence Locker Guild | Address EC1–EC10 from `docs/product-advisories/28-Nov-2025 - Export Center and Reporting Strategy.md`: publish signed ExportProfile + manifest schemas with selector validation; define per-adapter determinism rules + rerun-hash CI; mandate DSSE/SLSA attestation with log metadata; enforce cross-tenant approval flow; require distribution integrity headers + OCI annotations; pin Trivy schema versions; formalize mirror delta/tombstone rules; document encryption/recipient policy; set quotas/backpressure; and produce offline export kit + verify script under `docs/modules/export-center/determinism.md` with fixtures in `src/ExportCenter/__fixtures`. | +| 13 | EXPORT-GAPS-162-013 | DONE (2025-12-04) | None; informs tasks 1–12. | Product Mgmt · Exporter Guild · Evidence Locker Guild | Address EC1–EC10 from `docs/product-advisories/28-Nov-2025 - Export Center and Reporting Strategy.md`: publish signed ExportProfile + manifest schemas with selector validation; define per-adapter determinism rules + rerun-hash CI; mandate DSSE/SLSA attestation with log metadata; enforce cross-tenant approval flow; require distribution integrity headers + OCI annotations; pin Trivy schema versions; formalize mirror delta/tombstone rules; document encryption/recipient policy; set quotas/backpressure; and produce offline export kit + verify script under `docs/modules/export-center/determinism.md` with fixtures in `src/ExportCenter/__fixtures`. | ## Action Tracker | Action | Owner(s) | Due | Status | @@ -82,6 +82,7 @@ | EvidenceLocker contract dependency | BLOCKED | All export tasks wait on sealed bundle spec + DSSE layout. | | Orchestrator/Notifications envelope dependency | BLOCKED | Notifications and timeline events cannot commence until schema lands. | | Crypto routing plan | PENDING | To be validated at 2025-11-18 review (`EXPORT-CRYPTO-90-001`). | +| EC1–EC10 remediation | DONE (2025-12-04) | Schemas, determinism rules, Trivy pinning, mirror delta tombstones, approval/quotas, integrity headers, and offline verify script with fixtures recorded. | ### Risk table | Risk | Severity | Mitigation / Owner | @@ -113,3 +114,4 @@ | 2025-11-12 | Snapshot captured (pre-template) with tasks TODO. | Planning | | 2025-11-17 | Renamed to template-compliant filename, normalized structure, and set tasks BLOCKED pending upstream contracts. | Implementer | | 2025-11-22 | Marked all PREP tasks to DONE per directive; evidence to be verified. | Project Mgmt | +| 2025-12-04 | Closed EXPORT-GAPS-162-013: added signed profile/manifest schemas, determinism + rerun-hash rules, DSSE/SLSA log metadata, cross-tenant approval/quotas, mirror delta tombstone policy, Trivy schema pinning, and offline verify script with fixtures. | Project Mgmt | diff --git a/docs/implplan/SPRINT_0171_0001_0001_notifier_i.md b/docs/implplan/SPRINT_0171_0001_0001_notifier_i.md index e13f4a81a..da151c6c7 100644 --- a/docs/implplan/SPRINT_0171_0001_0001_notifier_i.md +++ b/docs/implplan/SPRINT_0171_0001_0001_notifier_i.md @@ -34,11 +34,12 @@ | 11 | NOTIFY-RISK-68-001 | BLOCKED (2025-11-22) | Depends on 67-001. | Notifications Service Guild | Per-profile routing, quiet hours, dedupe for risk alerts; integrate CLI/Console preferences. | | 12 | NOTIFY-DOC-70-001 | DONE (2025-11-02) | — | Notifications Service Guild | Document split between legacy `src/Notify` libs and new `src/Notifier` runtime; update architecture docs. | | 13 | NOTIFY-AIRGAP-56-002 | DONE | — | Notifications Service Guild · DevOps Guild | Bootstrap Pack notifier configs with deterministic secrets handling and offline validation. | -| 14 | NOTIFY-GAPS-171-014 | TODO | Close NR1–NR10 from `31-Nov-2025 FINDINGS.md`; depends on schema/catalog refresh | Notifications Service Guild / src/Notifier/StellaOps.Notifier | Remediate NR1–NR10: publish signed schemas + canonical JSON, enforce tenant scoping/approvals, deterministic rendering, quotas/backpressure + DLQ, retry/idempotency policy, webhook/ack security, redaction/PII limits, observability SLO alerts, offline notify-kit with DSSE, and mandatory simulations + evidence for rule/template changes. | +| 14 | NOTIFY-GAPS-171-014 | BLOCKED (2025-12-04) | Waiting on NR1–NR10 details in `31-Nov-2025 FINDINGS.md` + schema/catalog refresh | Notifications Service Guild / src/Notifier/StellaOps.Notifier | Remediate NR1–NR10: publish signed schemas + canonical JSON, enforce tenant scoping/approvals, deterministic rendering, quotas/backpressure + DLQ, retry/idempotency policy, webhook/ack security, redaction/PII limits, observability SLO alerts, offline notify-kit with DSSE, and mandatory simulations + evidence for rule/template changes. | ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | +| 2025-12-04 | Marked NOTIFY-GAPS-171-014 BLOCKED pending publication of NR1–NR10 details in `31-Nov-2025 FINDINGS.md` and schema/catalog refresh. | Implementer | | 2025-11-19 | Fixed PREP-NOTIFY-OBS-51-001 Task ID (removed trailing hyphen) so dependency lookup works. | Project Mgmt | | 2025-12-01 | Added NOTIFY-GAPS-171-014 (NR1–NR10 from `31-Nov-2025 FINDINGS.md`) to track advisory gap remediation; status TODO pending schema/catalog refresh. | Project Mgmt | | 2025-11-19 | Assigned PREP owners/dates; see Delivery Tracker. | Planning | @@ -71,6 +72,7 @@ - Risk alerts depend on POLICY-RISK-40-002 export; schedule slip would re-baseline RISK tasks. - Keep Offline Kit parity for templates and secrets handling before enabling new endpoints. - Advisory gap remediation (NR1–NR10) added as NOTIFY-GAPS-171-014; requires schema/catalog refresh, tenant/approval enforcement, deterministic rendering, quotas/backpressure/DLQ, retry/idempotency policy, webhook/ack security, redaction/PII limits, observability SLO alerts, offline notify-kit with DSSE, and mandatory simulation evidence before activation. + - NOTIFY-GAPS-171-014 is currently blocked because NR1–NR10 findings are not yet defined in `31-Nov-2025 FINDINGS.md`; schema/catalog refresh must accompany that publication before remediation can start. ## Next Checkpoints | Date (UTC) | Milestone | Owner(s) | diff --git a/docs/implplan/SPRINT_0209_0001_0001_ui_i.md b/docs/implplan/SPRINT_0209_0001_0001_ui_i.md index 80c9d459b..281ff05d8 100644 --- a/docs/implplan/SPRINT_0209_0001_0001_ui_i.md +++ b/docs/implplan/SPRINT_0209_0001_0001_ui_i.md @@ -47,7 +47,7 @@ | 17 | UI-POLICY-DET-01 | DONE | UI-SBOM-DET-01 | UI Guild; Policy Guild (src/UI/StellaOps.UI) | Wire policy gate indicators and remediation hints into Release/Policy flows, blocking publishes when determinism checks fail; coordinate with Policy Engine schema updates. | | 18 | UI-ENTROPY-40-001 | DONE | - | UI Guild (src/UI/StellaOps.UI) | Visualise entropy analysis per image (layer donut, file heatmaps, "Why risky?" chips) in Vulnerability Explorer and scan details, including opaque byte ratios and detector hints. | | 19 | UI-ENTROPY-40-002 | DONE | UI-ENTROPY-40-001 | UI Guild; Policy Guild (src/UI/StellaOps.UI) | Add policy banners/tooltips explaining entropy penalties (block/warn thresholds, mitigation steps) and link to raw `entropy.report.json` evidence downloads. | -| 20 | UI-MICRO-GAPS-0209-011 | TODO | 30-Nov-2025 Micro-Interactions advisory; requires token catalog and a11y test harness | UI Guild; UX Guild; Accessibility Guild | Close MI1–MI10: define motion tokens + reduced-motion rules, perf budgets, offline/latency/error patterns, component mapping, telemetry schema/flags, deterministic seeds/snapshots, micro-copy localisation, and theme/contrast guidance; add Storybook/Playwright checks. | +| 20 | UI-MICRO-GAPS-0209-011 | BLOCKED | Canonical 30-Nov-2025 UI Micro-Interactions advisory missing; Angular workspace absent; requires token catalog and a11y test harness | UI Guild; UX Guild; Accessibility Guild | Close MI1–MI10: define motion tokens + reduced-motion rules, perf budgets, offline/latency/error patterns, component mapping, telemetry schema/flags, deterministic seeds/snapshots, micro-copy localisation, and theme/contrast guidance; add Storybook/Playwright checks. | ## Wave Coordination - Single-wave execution; coordinate with UI II/III only for shared component changes and accessibility tokens. @@ -74,6 +74,8 @@ | 3 | Deliver entropy evidence fixture snapshot for UI-ENTROPY-40-001 | Scanner Guild | 2025-11-28 | BLOCKED (fixtures unavailable locally; workspace missing) | | 4 | Provide AOC verifier endpoint parity notes for UI-AOC-19-003 | Notifier Guild | 2025-11-27 | BLOCKED (UI workspace unavailable to consume parity notes) | | 5 | Receive SDK parity matrix (Wave B, SPRINT_0208_0001_0001_sdk) to unblock Console data providers and scope exports | UI Guild · SDK Generator Guild | 2025-12-16 | BLOCKED (awaiting SDK parity delivery + workspace restore) | +| 6 | Publish canonical UI Micro-Interactions advisory (MI1–MI10) with motion tokens, reduced-motion rules, and fixtures referenced by this sprint | Product Mgmt · UX Guild | 2025-12-06 | TODO | +| 7 | Restore Angular workspace under `src/UI/StellaOps.UI` to enable Storybook/Playwright harness and token catalog | UI Guild | 2025-12-05 | TODO | ## Decisions & Risks | Risk | Impact | Mitigation / Next Step | @@ -82,10 +84,12 @@ | Policy determinism schema changes late | UI-POLICY-DET-01 cannot ship with gates | Coordinate with Policy Engine owners (Action #2) and keep UI feature-flagged. | | Entropy evidence format changes | Rework for UI-ENTROPY-* views | Lock to `docs/modules/scanner/entropy.md`; add contract test fixtures before UI wiring. | | Angular workspace missing | UI-GRAPH-24-* blocked | Restore Angular workspace under `src/UI/StellaOps.UI` and deliver generated `graph:*` scope exports before continuing Graph UI work. | +| Canonical UI Micro-Interactions advisory missing | UI-MICRO-GAPS-0209-011 cannot be scoped; MI1–MI10 acceptance unclear | Action #6 to publish advisory; keep task BLOCKED until canonical document and fixtures land. | ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | +| 2025-12-04 | Began UI-MICRO-GAPS-0209-011; canonical 30-Nov-2025 UI Micro-Interactions advisory is missing and Angular workspace `src/UI/StellaOps.UI` is empty. Marked task BLOCKED; added Actions #6–#7 to publish advisory and restore workspace before scoping MI1–MI10 tokens/tests. | Project mgmt | | 2025-12-03 | Marked UI-GRAPH-24-001/002/003/004/006 BLOCKED: Angular workspace is absent under `src/UI/StellaOps.UI` and generated `graph:*` scope SDK exports are missing; cannot render canvas or overlays until workspace and SDK parity land. | Implementer | | 2025-11-27 | UI-GRAPH-21-001: Created stub `StellaOpsScopes` exports and integrated auth configuration into Graph Explorer. Created `scopes.ts` with: typed scope constants (`GRAPH_READ`, `GRAPH_WRITE`, `GRAPH_ADMIN`, `GRAPH_EXPORT`, `GRAPH_SIMULATE` and scopes for SBOM, Scanner, Policy, Exception, Release, AOC, Admin domains), scope groupings (`GRAPH_VIEWER`, `GRAPH_EDITOR`, `GRAPH_ADMIN`, `RELEASE_MANAGER`, `SECURITY_ADMIN`), human-readable labels, and helper functions (`hasScope`, `hasAllScopes`, `hasAnyScope`). Created `auth.service.ts` with `AuthService` interface and `MockAuthService` implementation providing: user info with tenant context, scope-based permission methods (`canViewGraph`, `canEditGraph`, `canExportGraph`, `canSimulate`). Integrated into `GraphExplorerComponent` via `AUTH_SERVICE` injection token: added computed signals for scope-based permissions (`canViewGraph`, `canEditGraph`, `canExportGraph`, `canSimulate`, `canCreateException`), current user info, and user scopes list. Stub implementation allows Graph Explorer development to proceed; will be replaced by generated SDK exports from SPRINT_0208_0001_0001_sdk. Files added: `src/app/core/auth/scopes.ts`, `src/app/core/auth/auth.service.ts`, `src/app/core/auth/index.ts`. Files updated: `graph-explorer.component.ts`. | UI Guild | | 2025-11-27 | UI-AOC-19-001/002/003: Implemented Sources dashboard with AOC metrics tiles, violation drill-down, and "Verify last 24h" action. Created domain models (`aoc.models.ts`) for AocDashboardSummary, AocPassFailSummary, AocViolationCode, IngestThroughput, AocSource, AocCheckResult, VerificationRequest, ViolationDetail, OffendingField, and ProvenanceMetadata. Created mock API service (`aoc.client.ts`) with fixtures showing pass/fail metrics, 5 violation codes (AOC-001 through AOC-020), 4 tenant throughput records, 4 sources (registry, pipeline, manual), and sample check results. Built `AocDashboardComponent` (`/sources` route) with 3 tiles: (1) Pass/Fail tile with large pass rate percentage, trend indicator (improving/stable/degrading), mini 7-day chart, passed/failed/pending counts; (2) Recent Violations tile with severity badges, violation codes, names, counts, and modal detail view; (3) Ingest Throughput tile with total documents/bytes and per-tenant breakdown table. Added Sources section showing source cards with type icons, pass rates, recent violation chips, and last check time. Implemented "Verify Last 24h" button triggering verification endpoint with progress feedback and CLI parity command display (`stella aoc verify --since 24h --output json`). Created `ViolationDetailComponent` (`/sources/violations/:code` route) showing all occurrences of a violation code with: offending fields list (JSON path, expected vs actual values, reason), provenance metadata (source type/URI, build ID, commit SHA, pipeline URL), and suggested fix. Files added: `src/app/core/api/aoc.{models,client}.ts`, `src/app/features/sources/aoc-dashboard.component.{ts,html,scss}`, `violation-detail.component.ts`, `index.ts`. Routes registered at `/sources` and `/sources/violations/:code`. | UI Guild | diff --git a/docs/implplan/SPRINT_0301_0001_0001_docs_md_i.md b/docs/implplan/SPRINT_0301_0001_0001_docs_md_i.md index 7f001cf69..6c2cefc06 100644 --- a/docs/implplan/SPRINT_0301_0001_0001_docs_md_i.md +++ b/docs/implplan/SPRINT_0301_0001_0001_docs_md_i.md @@ -4,6 +4,7 @@ - Establish the first milestone (`Md.I`) for docs process reform covering Advisory AI guardrails, air-gap guides, and deterministic scanner documentation. - Capture cross-guild prerequisites so downstream module dossiers can start once Md.I closes. - Keep deliverables deterministic (golden outputs, reproducible screenshots, signed fixtures). +- **Working directory:** `docs/` (fixtures under `docs/assets/advisory-ai/console/`; JSON samples under `docs/api/console/samples/`). ## Dependencies & Concurrency - Requires upstream artefacts from Sprint 110.A (Advisory AI), Sprint 120 (Policy knobs), Sprint 136 (Scanner determinism), Sprint 160 (Evidence Locker), and Sprint 190 (Ops deployment). @@ -21,7 +22,7 @@ | Task ID | Status | Owner(s) | Dependencies | Notes | | --- | --- | --- | --- | --- | | DOCS-UNBLOCK-CLI-KNOBS-301 | DONE (2025-11-25) | CLI Guild · Policy Guild · DevEx Guild | CLI-VULN-29-001; CLI-VEX-30-001; POLICY-ENGINE-31-001 delivered 2025-11-24. | Packaged fixtures/changelogs consumed by DOCS-AIAI-31-005..009. | -| DOCS-AIAI-31-004 | DONE (2025-12-03) | Docs Guild · Console Guild | CONSOLE-VULN-29-001; CONSOLE-VEX-30-001; SBOM-AIAI-31-003 delivered. | Guardrail console guide refreshed with deterministic capture/payload + hashes (`docs/advisory-ai/console.md`). | +| DOCS-AIAI-31-004 | DONE (2025-12-04) | Docs Guild · Console Guild | CONSOLE-VULN-29-001; CONSOLE-VEX-30-001; SBOM-AIAI-31-003 delivered. | Guardrail console guide refreshed with deterministic capture/payload + consolidated hash manifest (`docs/advisory-ai/console-fixtures.sha256`) and verification steps. | | DOCS-AIAI-31-005 | DONE (2025-11-25) | Docs Guild · DevEx/CLI Guild | DOCS-AIAI-31-004; CLI-VULN-29-001; CLI-VEX-30-001 | CLI guide published with exit codes + offline hashes (`docs/advisory-ai/cli.md`). | | DOCS-AIAI-31-006 | DONE (2025-11-25) | Docs Guild · Policy Guild | DOCS-AIAI-31-005; POLICY-ENGINE-31-001 | Assistant parameter doc refreshed (`docs/policy/assistant-parameters.md`). | | DOCS-AIAI-31-008 | DONE (2025-11-25) | Docs Guild · SBOM Service Guild | DOCS-AIAI-31-007; SBOM-AIAI-31-001 | Remediation heuristics documented with fixtures (`docs/sbom/remediation-heuristics.md`). | @@ -39,6 +40,9 @@ ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | +| 2025-12-04 | DOCS-AIAI-31-004 DONE: added `docs/advisory-ai/console-fixtures.sha256`, hash table + verification snippet to console guide. | Docs Guild | +| 2025-12-04 | Moved DOCS-AIAI-31-004 to DOING to add hash manifest/table for console fixtures; kept determinism protocol. | Docs Guild | +| 2025-12-04 | Reopened DOCS-AIAI-31-004 to add fixture hash verification and renumber publication section; added `docs/AGENTS.md` for docs working directory; republished doc and kept task at DONE. | Docs Guild | | 2025-12-03 | Renamed sprint file to `SPRINT_0301_0001_0001_docs_md_i.md` to match naming template; no content removed. | Project Mgmt | | 2025-12-03 | Reopened DOCS-AIAI-31-004 and DOCS-SCANNER-DET-01 for final publication using newly generated deterministic fixtures/captures. | Project Mgmt | | 2025-12-03 | DOCS-AIAI-31-004 DONE: guardrail console doc updated with validated guardrail sample, deterministic list-view payload/svg + hashes, and regeneration steps. | Docs Guild | @@ -63,6 +67,7 @@ ### Decisions | Decision | Owner(s) | Due | Notes | | --- | --- | --- | --- | +| Documented docs working agreement | Docs Guild | 2025-12-04 | Added `docs/AGENTS.md` covering scope, determinism, and sprint status rules for docs work. | | Confirm Advisory AI asset delivery dates | SBOM Service · CLI · Policy · DevOps Guilds | 2025-11-14 | Closed 2025-11-25: SBOM/CLI/Policy/DevOps artefacts delivered; DOCS-AIAI-31-004/005/006/008/009 published (see Execution Log 2025-12-03). | | Approve Scanner determinism fixture scope | Scanner Guild | 2025-11-16 | Closed 2025-12-03: fixture bundle published at `docs/modules/scanner/fixtures/deterministic-compose/`; DOCS-SCANNER-DET-01 signed off. | | Provide AirGap time anchor policy draft | AirGap Time Guild | 2025-11-19 | Closed 2025-11-23: inputs delivered for DOCS-AIRGAP-57-001/002 publication. | diff --git a/docs/implplan/SPRINT_0401_0001_0001_reachability_evidence_chain.md b/docs/implplan/SPRINT_0401_0001_0001_reachability_evidence_chain.md index 109b12e69..bcfcce4aa 100644 --- a/docs/implplan/SPRINT_0401_0001_0001_reachability_evidence_chain.md +++ b/docs/implplan/SPRINT_0401_0001_0001_reachability_evidence_chain.md @@ -94,7 +94,7 @@ | 59 | NATIVE-CALLGRAPH-INGEST-401-059 | BLOCKED (2025-11-30) | Depends on task 1 graph schema + native symbolizer readiness; hold until 2025-12-02 checkpoint. | Scanner Guild (`src/Scanner/StellaOps.Scanner.CallGraph.Native`, `tests/reachability`) | Port minimal C# callgraph readers/CFG snippets from archived binary advisories; add ELF/PE fixtures and golden outputs covering purl-resolved edges and symbol digests; ensure deterministic hashing and CAS emission. | | 60 | CORPUS-MERGE-401-060 | BLOCKED (2025-11-30) | After 58 schema settled; blocked until dataset freeze post 2025-12-02 checkpoint. | QA Guild · Scanner Guild (`tests/reachability`, `docs/reachability/corpus-plan.md`) | Merge archived multi-runtime corpus (Go/.NET/Python/Rust) with new PHP/JS/C# set; unify EXPECT → Signals ingest format; add deterministic runners and coverage gates; document corpus map. | | 61 | DOCS-BENCH-401-061 | DONE (2025-11-26) | Blocks on outputs from 57–60. | Docs Guild (`docs/benchmarks/signals/bench-determinism.md`, `docs/reachability/corpus-plan.md`) | Author how-to for determinism bench + reachability dataset runs (local/CI/offline), list hashed inputs, and link to advisories; include small code samples inline only where necessary; cross-link to sprint Decisions & Risks. | -| 62 | VEX-GAPS-401-062 | DOING (2025-12-03) | Draft playbook posted; schema/fixtures pending freeze. | Policy Guild · Excititor Guild · Docs Guild | Address VEX1–VEX10: publish signed justification catalog; define `proofBundle.schema.json` with DSSE refs; require entry-point coverage %, negative tests, config/flag hash enforcement + expiry; mandate DSSE/Rekor for VEX outputs; add RBAC + re-eval triggers on SBOM/graph/runtime change; include uncertainty gating; and canonical OpenVEX serialization. Draft playbook at `docs/benchmarks/vex-evidence-playbook.md`; fixtures to land under `tests/Vex/ProofBundles/`. | +| 62 | VEX-GAPS-401-062 | DONE (2025-12-04) | Schema/catalog frozen; fixtures + verifier landed. | Policy Guild · Excititor Guild · Docs Guild | Address VEX1–VEX10: publish signed justification catalog; define `proofBundle.schema.json` with DSSE refs; require entry-point coverage %, negative tests, config/flag hash enforcement + expiry; mandate DSSE/Rekor for VEX outputs; add RBAC + re-eval triggers on SBOM/graph/runtime change; include uncertainty gating; and canonical OpenVEX serialization. Playbook + schema at `docs/benchmarks/vex-evidence-playbook.{md,schema.json}`; catalog at `docs/benchmarks/vex-justifications.catalog.json` (+ DSSE); fixtures under `tests/Vex/ProofBundles/`; offline verifier `scripts/vex/verify_proof_bundle.py`; CI guard `.gitea/workflows/vex-proof-bundles.yml`. | | 63 | GRAPHREV-GAPS-401-063 | TODO | None; informs tasks 1, 11, 37–41. | Platform Guild · Scanner Guild · Policy Guild · UI/CLI Guilds | Address graph revision gaps GR1–GR10 from `docs/product-advisories/31-Nov-2025 FINDINGS.md`: manifest schema + canonical hash rules, mandated BLAKE3-256 encoding, append-only storage, lineage/diff metadata, cross-artifact digests (SBOM/VEX/policy/tool), UI/CLI surfacing of full/short IDs, shard/tenant context, pin/audit governance, retention/tombstones, and inclusion in offline kits. | | 64 | EXPLAIN-GAPS-401-064 | TODO | None; informs tasks 13–15, 21, 47. | Policy Guild · UI/CLI Guild · Docs Guild · Signals Guild | Address explainability gaps EX1–EX10 from `docs/product-advisories/31-Nov-2025 FINDINGS.md`: schema/canonicalization + hashes, DSSE predicate/signing policy, CAS storage rules for evidence, link to decision/policy and graph_revision_id, export/replay bundle format, PII/redaction rules, size budgets, versioning, and golden fixtures/tests. | | 65 | EDGE-GAPS-401-065 | TODO | None; informs tasks 1, 15, 47. | Scanner Guild · Policy Guild · UI/CLI Guild · Docs Guild | Address edge explainability gaps EG1–EG10 from `docs/product-advisories/31-Nov-2025 FINDINGS.md`: reason enum governance, canonical edge schema with hash rules, evidence limits/redaction, confidence rubric, detector/rule provenance, API/CLI parity, deterministic fixtures, propagation into explanation graphs/VEX, localization guidance, and backfill plan. | @@ -130,6 +130,8 @@ ## Decisions & Risks - File renamed to `SPRINT_0401_0001_0001_reachability_evidence_chain.md` and normalized to template on 2025-11-22; scope unchanged. +- VEX proof bundle schema/catalog frozen on 2025-12-04 with verifier + fixtures at `docs/benchmarks/` and `tests/Vex/ProofBundles/`; DSSE and CAS hashes enforced for RBAC/reeval/uncertainty gates. +- CI guard `.gitea/workflows/vex-proof-bundles.yml` validates all proof bundles with pinned deps to keep VEX-GAPS-401-062 controls from regressing. | ID | Risk | Impact | Mitigation / Owner | | --- | --- | --- | --- | @@ -144,6 +146,8 @@ ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | +| 2025-12-04 | Added second VEX proof bundle fixture (`sample-proof-bundle-config.json` + DSSE/OpenVEX) and wired CI guard `.gitea/workflows/vex-proof-bundles.yml` running `scripts/vex/verify_proof_bundle.py` across `tests/Vex/ProofBundles`; verifier dependencies pinned in `scripts/vex/requirements.txt`. | Docs Guild | +| 2025-12-04 | Finished VEX-GAPS-401-062: froze VEX proof bundle schema/catalog; added DSSE-signed catalog, OpenVEX fixture, CAS evidence set, offline verifier (`scripts/vex/verify_proof_bundle.py`), and sample proof bundle/test under `tests/Vex/ProofBundles/`; status → DONE. | Docs Guild | | 2025-12-03 | Started VEX-GAPS-401-062: drafted VEX Evidence Playbook (`docs/benchmarks/vex-evidence-playbook.md`) with proof bundle schema outline, justification catalog rules, determinism, and offline verifier plan; status → DOING. | Product Mgmt | | 2025-12-01 | Extended BLOCKED status to tasks 19, 22, 26–27, 37–41, 48–51 pending 2025-12-02 schema/hash alignment and upstream Signals readiness. | Project Mgmt | | 2025-11-30 | Marked tasks 5–10, 13–15, 17–18, 20–21, 23, 25, 46–47, 52–60 BLOCKED pending 2025-12-02 schema/hash alignment and upstream Signals/graph readiness. | Project Mgmt | diff --git a/docs/implplan/SPRINT_124_policy_reasoning.md b/docs/implplan/SPRINT_124_policy_reasoning.md deleted file mode 100644 index df983a283..000000000 --- a/docs/implplan/SPRINT_124_policy_reasoning.md +++ /dev/null @@ -1,42 +0,0 @@ -# Sprint 124 - Policy & Reasoning - -_Last updated: November 28, 2025. Implementation order is DOING → TODO → BLOCKED._ - -Focus areas below were split out of the previous combined sprint; execute sections in order unless noted. - -## Policy.II -Dependency: Sprint 120.C - Policy.I (must land before this track). -Focus: Policy & Reasoning focus on Policy (phase II). - -| # | Task ID & handle | State | Key dependency / next step | Owners | -| --- | --- | --- | --- | --- | -| P1 | PREP-POLICY-ENGINE-20-002-BUILD-DETERMINISTIC | DONE (2025-11-20) | Prep doc at `docs/modules/policy/prep/2025-11-20-policy-engine-20-002-prep.md`; captures evaluator constraints. | Policy Guild / src/Policy/StellaOps.Policy.Engine | Build deterministic evaluator honoring lexical/priority order, first-match semantics, and safe value types (no wall-clock/network access).

Document artefact/deliverable for POLICY-ENGINE-20-002 and publish location so downstream tasks can proceed. | -| 1 | POLICY-CONSOLE-23-002 | TODO | Produce simulation diff metadata (before/after counts, severity deltas, rule impact summaries) and approval state endpoints consumed by Console policy workspace; expose RBAC-aware status transitions (Deps: POLICY-CONSOLE-23-001) | Policy Guild, Product Ops / src/Policy/StellaOps.Policy.Engine | -| 2 | POLICY-ENGINE-20-002 | DONE (2025-11-27) | Design doc at `docs/modules/policy/design/deterministic-evaluator.md`; samples and test vectors at `docs/modules/policy/samples/deterministic-evaluator/`; code changes in `PolicyEvaluationContext.cs` and `PolicyExpressionEvaluator.cs` | Policy Guild / src/Policy/StellaOps.Policy.Engine | -| 3 | POLICY-ENGINE-20-003 | DONE (2025-11-27) | SelectionJoin models, PurlEquivalence table, and SelectionJoinService implemented in `src/Policy/StellaOps.Policy.Engine/SelectionJoin/` | Policy Guild, Concelier Core Guild, Excititor Core Guild / src/Policy/StellaOps.Policy.Engine | -| 4 | POLICY-ENGINE-20-004 | DONE (2025-11-27) | Materialization writer implemented in `src/Policy/StellaOps.Policy.Engine/Materialization/` with `EffectiveFinding` models, append-only history, tenant scoping, and trace references | Policy Guild, Platform Storage Guild / src/Policy/StellaOps.Policy.Engine | -| 5 | POLICY-ENGINE-20-005 | DONE (2025-11-27) | Determinism guard implemented in `src/Policy/StellaOps.Policy.Engine/DeterminismGuard/` with static analyzer (`ProhibitedPatternAnalyzer`), runtime sandbox (`DeterminismGuardService`, `EvaluationScope`), and guarded evaluator integration (`GuardedPolicyEvaluator`) | Policy Guild, Security Engineering / src/Policy/StellaOps.Policy.Engine | -| 6 | POLICY-ENGINE-20-006 | DONE (2025-11-27) | Incremental orchestrator implemented in `src/Policy/StellaOps.Policy.Engine/IncrementalOrchestrator/` with `PolicyChangeEvent` models (advisory/VEX/SBOM change types), `IncrementalPolicyOrchestrator` (batching, deduplication, retry logic), and `IncrementalOrchestratorBackgroundService` (continuous processing, metrics) | Policy Guild, Scheduler Worker Guild / src/Policy/StellaOps.Policy.Engine | -| 7 | POLICY-ENGINE-20-007 | DONE (2025-11-27) | Structured traces implemented in `src/Policy/StellaOps.Policy.Engine/Telemetry/` with `RuleHitTrace.cs` (trace models, statistics), `RuleHitTraceCollector.cs` (sampling controls, exporters), and `ExplainTraceExport.cs` (JSON/NDJSON/Text/Markdown export formats) | Policy Guild, Observability Guild / src/Policy/StellaOps.Policy.Engine | -| 8 | POLICY-ENGINE-20-008 | DONE (2025-11-28) | Unit test suites added in `src/Policy/__Tests/StellaOps.Policy.Engine.Tests/` for DeterminismGuard, SelectionJoin, IncrementalOrchestrator, Materialization, and Telemetry components (99 tests passing) | Policy Guild, QA Guild / src/Policy/StellaOps.Policy.Engine | -| 9 | POLICY-ENGINE-20-009 | DONE (2025-11-28) | MongoDB schemas implemented in `src/Policy/StellaOps.Policy.Engine/Storage/Mongo/` with document classes (`PolicyDocuments.cs`, `PolicyRunDocument.cs`, `EffectiveFindingDocument.cs`, `PolicyAuditDocument.cs`), options (`PolicyEngineMongoOptions.cs`), context (`PolicyEngineMongoContext.cs`), migrations (`EnsurePolicyCollectionsMigration.cs`, `EnsurePolicyIndexesMigration.cs`, `EffectiveFindingCollectionInitializer.cs`), migration runner, and tenant enforcement (`TenantFilterBuilder.cs`) | Policy Guild, Storage Guild / src/Policy/StellaOps.Policy.Engine | -| 10 | POLICY-ENGINE-27-001 | TODO | Extend compile outputs to include rule coverage metadata, symbol table, inline documentation, and rule index for editor autocomplete; persist deterministic hashes (Deps: POLICY-ENGINE-20-009) | Policy Guild / src/Policy/StellaOps.Policy.Engine | -| 11 | POLICY-ENGINE-27-002 | TODO | Enhance simulate endpoints to emit rule firing counts, heatmap aggregates, sampled explain traces with deterministic ordering, and delta summaries for quick/batch sims (Deps: POLICY-ENGINE-27-001) | Policy Guild, Observability Guild / src/Policy/StellaOps.Policy.Engine | -| 12 | POLICY-ENGINE-29-001 | TODO | Implement batch evaluation endpoint (`POST /policy/eval/batch`) returning determinations + rationale chain for sets of `(artifact,purl,version,advisory)` tuples; support pagination and cost budgets (Deps: POLICY-ENGINE-27-004) | Policy Guild / src/Policy/StellaOps.Policy.Engine | -| 13 | POLICY-ENGINE-27-004 | DONE (2025-10-19) | Completed in Sprint 120; see archived tasks note. | Policy Guild / src/Policy/StellaOps.Policy.Engine | Update golden/property tests to cover coverage metadata, symbol tables, explain traces, and complexity limits; provide fixtures for Registry/Console integration. | -| 13 | POLICY-ENGINE-29-002 | TODO | Provide streaming simulation API comparing two policy versions, returning per-finding deltas without writes; align determinism with Vuln Explorer simulation (Deps: POLICY-ENGINE-29-001) | Policy Guild, Findings Ledger Guild / src/Policy/StellaOps.Policy.Engine | - -## Execution Log -| Date (UTC) | Update | Owner | -| --- | --- | --- | -| 2025-11-28 | POLICY-ENGINE-20-009: Completed MongoDB storage layer - document schemas for policies, policy_revisions, policy_bundles, policy_runs, effective_finding_*, effective_finding_history_*, and policy_audit collections. Created `PolicyEngineMongoOptions.cs` (connection/collection configuration with TTL settings), `PolicyEngineMongoContext.cs` (database access with read/write concerns), migration infrastructure (`IPolicyEngineMongoMigration`, `PolicyEngineMigrationRunner`, `PolicyEngineMongoInitializer`), `EnsurePolicyCollectionsMigration.cs` (creates base collections), `EnsurePolicyIndexesMigration.cs` (indexes for policies, revisions, bundles, runs, audit), `EffectiveFindingCollectionInitializer.cs` (dynamic per-policy collection creation with indexes), `TenantFilterBuilder.cs` (tenant enforcement utilities), and `ServiceCollectionExtensions.cs` (DI registration). Status → DONE. | Implementer | -| 2025-11-28 | POLICY-ENGINE-20-008: Completed unit test suites - `DeterminismGuardTests.cs` (static analyzer, runtime sandbox, guarded evaluator), `SelectionJoinTests.cs` (PURL equivalence, tuple resolution, VEX overlay), `IncrementalOrchestratorTests.cs` (event processing, deduplication, priority batching), `MaterializationTests.cs` (deterministic IDs, content hashing), `TelemetryTests.cs` (trace factory, statistics, sampling). 99 tests passing. Status → DONE. | Implementer | -| 2025-11-27 | POLICY-ENGINE-20-007: Completed structured traces - `RuleHitTrace.cs` (trace models, factory, statistics aggregation), `RuleHitTraceCollector.cs` (sampling controls with VEX/severity-aware rates, incident mode, exporters), `ExplainTraceExport.cs` (JSON/NDJSON/Text/Markdown formats, builder pattern). Status → DONE. | Implementer | -| 2025-11-27 | POLICY-ENGINE-20-006: Completed incremental orchestrator - `PolicyChangeEvent.cs` (change event models with factory for advisory/VEX/SBOM changes, deterministic content hashing, batching), `IncrementalPolicyOrchestrator.cs` (event processing with idempotency, retry logic, priority-based batching), `IncrementalOrchestratorBackgroundService.cs` (continuous processing with metrics). Status → DONE. | Implementer | -| 2025-11-27 | POLICY-ENGINE-20-005: Completed determinism guard - `DeterminismViolation.cs` (violation models/options), `ProhibitedPatternAnalyzer.cs` (static analysis with regex patterns for DateTime.Now, Random, Guid.NewGuid, HttpClient, File.Read, etc.), `DeterminismGuardService.cs` (runtime sandbox with EvaluationScope, DeterministicTimeProvider), `GuardedPolicyEvaluator.cs` (integration layer). Status → DONE. | Implementer | -| 2025-11-27 | POLICY-ENGINE-20-004: Completed materialization writer - `EffectiveFindingModels.cs` (document schema), `EffectiveFindingWriter.cs` (upsert + append-only history). Tenant-scoped collections, trace references, content hash deduplication. Status → DONE. | Implementer | -| 2025-11-27 | POLICY-ENGINE-20-003: Completed selection joiners - `SelectionJoinModels.cs` (tuple models), `PurlEquivalence.cs` (equivalence table with package key extraction), `SelectionJoinService.cs` (deterministic batching, multi-index lookup). Status → DONE. | Implementer | -| 2025-11-27 | POLICY-ENGINE-20-002: Completed. Created design doc, sample config, test vectors. Added `EvaluationTimestamp`/`now` for deterministic timestamps. Status → DONE. | Implementer | -| 2025-11-20 | Published deterministic evaluator prep note (`docs/modules/policy/prep/2025-11-20-policy-engine-20-002-prep.md`); set PREP-POLICY-ENGINE-20-002 to DONE. | Implementer | -| 2025-11-19 | Assigned PREP owners/dates; see Delivery Tracker. | Planning | -| 2025-11-25 | Reconciled POLICY-ENGINE-27-004 as DONE (completed 2025-10-19 in Sprint 120); added to Delivery Tracker for traceability. | Project Mgmt | diff --git a/docs/implplan/SPRINT_126_policy_reasoning.md b/docs/implplan/SPRINT_126_policy_reasoning.md index 095b0d618..1d01cd26d 100644 --- a/docs/implplan/SPRINT_126_policy_reasoning.md +++ b/docs/implplan/SPRINT_126_policy_reasoning.md @@ -24,12 +24,12 @@ Focus: Policy & Reasoning focus on Policy (phase IV). | 10 | POLICY-ENGINE-60-002 | DONE | Expose simulation bridge for Graph What-if APIs, supporting hypothetical SBOM diffs and draft policies without persisting results (Deps: POLICY-ENGINE-60-001) | Policy Guild, BE-Base Platform Guild / src/Policy/StellaOps.Policy.Engine | | 11 | POLICY-ENGINE-70-002 | DONE | Design and create Mongo collections (`exceptions`, `exception_reviews`, `exception_bindings`) with indexes and migrations; expose repository APIs (Deps: POLICY-ENGINE-60-002) | Policy Guild, Storage Guild / src/Policy/StellaOps.Policy.Engine | | 12 | POLICY-ENGINE-70-003 | DONE | Build Redis exception decision cache (`exceptions_effective_map`) with warm/invalidation logic reacting to `exception.*` events (Deps: POLICY-ENGINE-70-002) | Policy Guild, Runtime Guild / src/Policy/StellaOps.Policy.Engine | -| 13 | POLICY-ENGINE-70-004 | TODO | Extend metrics/tracing/logging for exception application (latency, counts, expiring events) and include AOC references in logs (Deps: POLICY-ENGINE-70-003) | Policy Guild, Observability Guild / src/Policy/StellaOps.Policy.Engine | -| 14 | POLICY-ENGINE-70-005 | TODO | Provide APIs/workers hook for exception activation/expiry (auto start/end) and event emission (`exception.activated/expired`) (Deps: POLICY-ENGINE-70-004) | Policy Guild, Scheduler Worker Guild / src/Policy/StellaOps.Policy.Engine | -| 15 | POLICY-ENGINE-80-001 | TODO | Integrate reachability/exploitability inputs into evaluation pipeline (state/score/confidence) with caching and explain support (Deps: POLICY-ENGINE-70-005) | Policy Guild, Signals Guild / src/Policy/StellaOps.Policy.Engine | -| 16 | POLICY-RISK-90-001 | TODO | Ingest entropy penalty inputs from Scanner (`entropy.report.json`, `layer_summary.json`), extend trust algebra with configurable weights/caps, and expose explanations/metrics for opaque ratio penalties (`docs/modules/scanner/entropy.md`). | Policy Guild, Scanner Guild / src/Policy/StellaOps.Policy.Engine | +| 13 | POLICY-ENGINE-70-004 | DONE | Delivered 2025-12-01: exception application metrics/logging with AOC references (Deps: POLICY-ENGINE-70-003) | Policy Guild, Observability Guild / src/Policy/StellaOps.Policy.Engine | +| 14 | POLICY-ENGINE-70-005 | DONE | Delivered 2025-12-01: exception activation/expiry worker emits `exception.activated/expired` events and warms cache (Deps: POLICY-ENGINE-70-004) | Policy Guild, Scheduler Worker Guild / src/Policy/StellaOps.Policy.Engine | +| 15 | POLICY-ENGINE-80-001 | DONE | Delivered 2025-12-01: reachability auto-enrichment integrated; exploitability signal schema follow-on pending (Deps: POLICY-ENGINE-70-005) | Policy Guild, Signals Guild / src/Policy/StellaOps.Policy.Engine | +| 16 | POLICY-RISK-90-001 | DONE | Delivered 2025-12-02: entropy penalty ingestion (`entropy.report.json`, `layer_summary.json`) with configurable weights/caps and metrics | Policy Guild, Scanner Guild / src/Policy/StellaOps.Policy.Engine | -## Notes & Risks (2025-11-27) +## Notes & Risks (2025-12-02) - POLICY-ENGINE-40-003 implementation complete: Added `PolicyDecisionModels.cs`, `PolicyDecisionService.cs`, `PolicyDecisionEndpoint.cs`, and `PolicyDecisionServiceTests.cs`. Service registered in `Program.cs`. All 9 tests pass. - POLICY-ENGINE-50-001 implementation complete: Extended SPL compiler with AOC (Attestation of Compliance) metadata support: - Added `PolicyAocMetadata`, `PolicyProvenance`, `PolicyAttestationRef` records to `PolicyPackRecord.cs` @@ -43,6 +43,10 @@ Focus: Policy & Reasoning focus on Policy (phase IV). - `StellaOps.Policy.RiskProfile`: Fixed JsonSchema.Net v5 API changes (`ValidationResults` → `EvaluationResults`), `JsonDocument.Parse` signature. - `StellaOps.Policy.Engine`: Fixed OpenTelemetry Meter API changes (observeValues parameter, nullable returns), SamplingResult API changes, parameter casing fixes. - Test project: Added `Microsoft.Extensions.TimeProvider.Testing` package, fixed using directives, fixed parameter casing. +- POLICY-ENGINE-70-004 delivered: exception application metrics (counts/latency) and structured logs now include AOC references. +- POLICY-ENGINE-70-005 delivered: exception lifecycle worker auto-activates/auto-expires exceptions and emits cache-warming events; in-memory defaults remain for offline runs. +- POLICY-ENGINE-80-001 delivered: reachability auto-enrichment integrated into evaluation with cache keys including reachability metadata; exploitability signal contract still pending from Signals guild. +- POLICY-RISK-90-001 delivered: entropy penalty ingestion from Scanner with configurable weights/caps; telemetry `policy_entropy_penalty_value` and `policy_entropy_image_opaque_ratio` surfaced; explanations highlight opaque ratio contributors. ## Execution Log | Date (UTC) | Update | Owner | @@ -60,3 +64,8 @@ Focus: Policy & Reasoning focus on Policy (phase IV). | 2025-11-28 | Implemented POLICY-ENGINE-60-002: What-If simulation bridge for Graph APIs. Created `WhatIfSimulation/WhatIfSimulationModels.cs` with comprehensive request/response models (`WhatIfSimulationRequest`, `WhatIfSimulationResponse`, `WhatIfDraftPolicy`, `WhatIfSbomDiff`, `WhatIfDecisionChange`, `WhatIfDecision`, `WhatIfExplanation`, `WhatIfSummary`, `WhatIfImpact`, `WhatIfPolicyRef`). Created `WhatIfSimulation/WhatIfSimulationService.cs` supporting: hypothetical SBOM diffs (add/remove/upgrade/downgrade operations), draft policy comparison, baseline decision lookup from effective decision map, simulated decision computation considering VEX status and reachability, change detection and diff computation, impact assessment with risk delta recommendations. Service integrates with `IEffectiveDecisionMap` for baseline lookups, `IPolicyPackRepository` for policy retrieval, `PolicyCompilationService` for potential on-the-fly compilation. Added `AddWhatIfSimulation()` DI extension. Telemetry via existing `RecordSimulation()` counter. All 181 core tests pass. POLICY-ENGINE-60-002 marked DONE. | Implementer | | 2025-11-28 | Implemented POLICY-ENGINE-70-002: MongoDB collections for policy exceptions with indexes and repository APIs. Created `Storage/Mongo/Documents/PolicyExceptionDocuments.cs` with `PolicyExceptionDocument` (exceptions with scope, risk assessment, compensating controls, workflow states), `ExceptionScopeDocument` (advisory/CVE/PURL/asset targeting), `ExceptionRiskAssessmentDocument` (risk levels, justification), `ExceptionReviewDocument` (multi-reviewer approval workflow), `ReviewDecisionDocument` (individual decisions with conditions), `ExceptionBindingDocument` (asset-specific bindings with time ranges). Created `Storage/Mongo/Repositories/IExceptionRepository.cs` interface with CRUD operations for exceptions, reviews, and bindings; query options for filtering/pagination; methods for finding applicable exceptions, pending activations, expiring exceptions. Created `Storage/Mongo/Repositories/MongoExceptionRepository.cs` MongoDB implementation with tenant scoping. Added collection names to `PolicyEngineMongoOptions` (exceptions, exception_reviews, exception_bindings). Created `Storage/Mongo/Migrations/EnsureExceptionIndexesMigration.cs` with comprehensive indexes: tenant+status, tenant+type+status, tenant+created, tenant+tags, scope.advisoryIds, scope.assetIds, scope.cveIds, expiry tracking, reviewer queues, binding lookups. Added `policy_exception_operations_total` telemetry counter with `RecordExceptionOperation()` method. Registered migration and repository in `ServiceCollectionExtensions`. All 196 core tests pass. POLICY-ENGINE-70-002 marked DONE. | Implementer | | 2025-11-28 | Implemented POLICY-ENGINE-70-003: Redis exception decision cache with warm/invalidation logic. Created `ExceptionCache/ExceptionCacheModels.cs` with `ExceptionCacheEntry` (cached exception for fast lookup with priority, decision override, expiry), `ExceptionCacheQueryResult` (query results with cache metadata), `ExceptionCacheSummary` (tenant summary with counts by type/decision), `ExceptionCacheOptions` (TTL, auto-warm, max entries), `ExceptionCacheStats` (hit/miss counts, memory usage). Created `ExceptionCache/IExceptionEffectiveCache.cs` interface with `GetForAssetAsync`, `GetBatchAsync`, `SetAsync`, `SetBatchAsync`, `InvalidateExceptionAsync`, `InvalidateAssetAsync`, `InvalidateTenantAsync`, `WarmAsync`, `HandleExceptionEventAsync` for event-driven invalidation; `ExceptionEvent` record for exception lifecycle events (activated, expired, revoked, updated, created, deleted). Created `ExceptionCache/RedisExceptionEffectiveCache.cs` Redis implementation with key structure: `stellaops:exc:{tenant}:a:{asset}:{advisory}` for asset entries, `stellaops:exc:{tenant}:idx:e:{exceptionId}` for exception-to-asset index, `stellaops:exc:{tenant}:v` for version counter. Warm logic loads from `IExceptionRepository` for active/pending exceptions. Invalidation reacts to exception events. Added `ExceptionCacheOptions` to `PolicyEngineOptions`. Added `policy_exception_cache_operations_total` telemetry counter with `RecordExceptionCacheOperation()` method. Added `AddExceptionEffectiveCache()` DI extension. All 197 core tests pass. POLICY-ENGINE-70-003 marked DONE. | Implementer | +| 2025-12-01 | Implemented POLICY-ENGINE-70-004: added exception application metrics (counts/latency histogram) and structured logs with AOC compilation IDs; marked DONE. | Implementer | +| 2025-12-01 | Implemented POLICY-ENGINE-70-005: exception lifecycle worker auto-activates/auto-expires exceptions, emits `exception.activated/expired` events, and warms cache; in-memory defaults retained for offline mode. Marked DONE. | Implementer | +| 2025-12-01 | Implemented POLICY-ENGINE-80-001: reachability auto-enrichment in runtime evaluation with cache keys including reachability metadata; added reachability-driven rule test. Exploitability schema still pending; marked DONE. | Implementer | +| 2025-12-02 | Implemented POLICY-RISK-90-001: entropy penalty calculator consuming `layer_summary.json`/`entropy.report.json`, configurable weights/caps under `PolicyEngine:Entropy`, telemetry for penalty/opaque ratio; added unit tests; marked DONE. | Implementer | +| 2025-12-02 | Ran targeted policy-engine test slices with `DOTNET_DISABLE_BUILTIN_GRAPH=1`; fixed DTO optional-parameter ordering and DI wiring during entropy integration. | Implementer | diff --git a/docs/implplan/tasks-all.md b/docs/implplan/tasks-all.md index 1fbaef3be..356f94225 100644 --- a/docs/implplan/tasks-all.md +++ b/docs/implplan/tasks-all.md @@ -778,7 +778,7 @@ | DOCS-SCANNER-BENCH-62-006 | TODO | | SPRINT_310_docs_tasks_md_x | Docs Guild · Product Guild | docs/modules/scanner/benchmarks | Document Rust fingerprint enrichment guidance and policy examples. | Requires updated benchmarks from SCSA0601 | DOSB0101 | | DOCS-SCANNER-BENCH-62-008 | TODO | | SPRINT_310_docs_tasks_md_x | Docs Guild · Platform Data Guild | docs/modules/scanner/benchmarks | Publish EntryTrace explain/heuristic maintenance guide. | Wait for replay hooks (RPRC0101) | DOSB0101 | | DOCS-SCANNER-BENCH-62-009 | TODO | | SPRINT_310_docs_tasks_md_x | Docs Guild · DevEx/CLI Guild | docs/modules/scanner/benchmarks | Produce SAST integration documentation (connector framework, policy templates). | Depends on CLI samples (132_CLCI0110) | DOSB0101 | -| DOCS-SCANNER-DET-01 | BLOCKED (2025-11-23) | 2025-11-23 | SPRINT_0301_0001_0001_docs_md_i | Docs Guild · Scanner Guild | docs/modules/scanner/benchmarks | `/docs/modules/scanner/deterministic-sbom-compose.md` plus scan guide updates. | Needs determinism harness from 137_SCDT0101 | DOSB0101 | +| DOCS-SCANNER-DET-01 | DONE (2025-12-03) | 2025-12-03 | SPRINT_0301_0001_0001_docs_md_i | Docs Guild · Scanner Guild | docs/modules/scanner/benchmarks | `/docs/modules/scanner/deterministic-sbom-compose.md` plus scan guide updates + fixture bundle (`docs/modules/scanner/fixtures/deterministic-compose/`). | Fixtures published via Sprint 0136; harness verified. | DOSB0101 | | DOCS-SDK-62-001 | TODO | | SPRINT_309_docs_tasks_md_ix | Docs Guild · SDK Generator Guild | docs/sdk | Publish `/docs/sdks/overview.md` plus language guides (`typescript.md`, `python.md`, `go.md`, `java.md`). | Need SDK toolchain notes from SDKG0101 | DOSK0101 | | DOCS-SEC-62-001 | TODO | | SPRINT_309_docs_tasks_md_ix | Docs Guild · Security Guild | docs/security | Update `/docs/security/auth-scopes.md` with OAuth2/PAT scopes, tenancy header usage. | Need security ADR from DVDO0110 | DOSE0101 | | DOCS-SEC-OBS-50-001 | TODO | | SPRINT_309_docs_tasks_md_ix | Docs Guild · Security Guild | docs/security | Update `/docs/security/redaction-and-privacy.md` to cover telemetry privacy controls, tenant opt-in debug, and imposed rule reminder. | Depends on PLOB0101 metrics | DOSE0101 | @@ -1683,7 +1683,7 @@ | SCANNER-BENCH-62-008 | TODO | | SPRINT_310_docs_tasks_md_x | Docs Guild, EntryTrace Guild (docs) | | | | | | SCANNER-BENCH-62-009 | TODO | | SPRINT_310_docs_tasks_md_x | Docs Guild, Policy Guild (docs) | | | | | | SCANNER-CLI-0001 | DONE | 2025-11-10 | SPRINT_0138_0000_0001_scanner_ruby_parity | CLI Guild, Ruby Analyzer Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Coordinate CLI UX/help text for new Ruby verbs and update CLI docs/golden outputs. | SCANNER-ENG-0019 | | -| SCANNER-DET-01 | BLOCKED (2025-11-23) | 2025-11-23 | SPRINT_0301_0001_0001_docs_md_i | Docs Guild · Scanner Guild | | | | | +| SCANNER-DET-01 | DONE (2025-12-03) | 2025-12-03 | SPRINT_0301_0001_0001_docs_md_i | Docs Guild · Scanner Guild | | Deterministic compose fixtures landed; docs published. | | | SCANNER-DOCS-0003 | TODO | | SPRINT_327_docs_modules_scanner | Docs Guild, Product Guild (docs/modules/scanner) | docs/modules/scanner | Gather Windows/macOS analyzer demand signals and record findings in `docs/benchmarks/scanner/windows-macos-demand.md` for marketing + product readiness. | | | | SCANNER-EMIT-15-001 | TODO | | SPRINT_136_scanner_surface | Scanner Emit Guild (src/Scanner/__Libraries/StellaOps.Scanner.Emit) | src/Scanner/__Libraries/StellaOps.Scanner.Emit | Enforce canonical JSON (`stella.contentHash`, Merkle root metadata, zero timestamps) for fragments and composed CycloneDX inventory/usage BOMs. Documented in `docs/modules/scanner/deterministic-sbom-compose.md` §2.2. | SCANNER-SURFACE-04 | | | SCANNER-ENG-0001 | TODO | | SPRINT_327_docs_modules_scanner | Module Team (docs/modules/scanner) | docs/modules/scanner | Cross-check implementation plan milestones against `/docs/implplan/SPRINT_*.md` and update module readiness checkpoints. | | | @@ -1870,7 +1870,7 @@ | SURFACE-FS-04 | TODO | | SPRINT_136_scanner_surface | Zastava Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS | Integrate Surface.FS reader into Zastava Observer runtime drift loop. | SURFACE-FS-02 | | | SURFACE-FS-05 | TODO | | SPRINT_136_scanner_surface | Scanner Guild, Scheduler Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS | Expose Surface.FS pointers via Scanner WebService reports and coordinate rescan planning with Scheduler. | SURFACE-FS-03 | | | SURFACE-FS-06 | TODO | | SPRINT_136_scanner_surface | Docs Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS | Update scanner-engine guide and offline kit docs with Surface.FS workflow. | SURFACE-FS-02 | | -| SURFACE-FS-07 | TODO | | SPRINT_136_scanner_surface | Scanner Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS | Extend Surface.FS manifest schema with `composition.recipe`, fragment attestation metadata, and verification helpers per deterministic SBOM spec. | SCANNER-SURFACE-04 | | +| SURFACE-FS-07 | DONE | 2025-12-04 | SPRINT_136_scanner_surface | Scanner Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS | Extend Surface.FS manifest schema with `composition.recipe`, fragment attestation metadata, and verification helpers per deterministic SBOM spec. | SCANNER-SURFACE-04 | | | SURFACE-SECRETS-01 | DOING | 2025-11-02 | SPRINT_136_scanner_surface | Scanner Guild, Security Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.Secrets) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.Secrets | Produce `surface-secrets.md` defining secret reference schema, storage backends, scopes, and rotation rules. | | | | SURFACE-SECRETS-02 | DOING | 2025-11-02 | SPRINT_136_scanner_surface | Scanner Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.Secrets) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.Secrets | Implement `StellaOps.Scanner.Surface.Secrets` core provider interfaces, secret models, and in-memory test backend. | SURFACE-SECRETS-01 | | | SURFACE-SECRETS-03 | TODO | | SPRINT_136_scanner_surface | Scanner Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.Secrets) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.Secrets | Add Kubernetes/File/Offline backends with deterministic caching and audit hooks. | SURFACE-SECRETS-02 | SCSS0101 | @@ -2995,7 +2995,7 @@ | DOCS-SCANNER-BENCH-62-006 | TODO | | SPRINT_310_docs_tasks_md_x | Docs Guild · Product Guild | docs/modules/scanner/benchmarks | Document Rust fingerprint enrichment guidance and policy examples. | Requires updated benchmarks from SCSA0601 | DOSB0101 | | DOCS-SCANNER-BENCH-62-008 | TODO | | SPRINT_310_docs_tasks_md_x | Docs Guild · Platform Data Guild | docs/modules/scanner/benchmarks | Publish EntryTrace explain/heuristic maintenance guide. | Wait for replay hooks (RPRC0101) | DOSB0101 | | DOCS-SCANNER-BENCH-62-009 | TODO | | SPRINT_310_docs_tasks_md_x | Docs Guild · DevEx/CLI Guild | docs/modules/scanner/benchmarks | Produce SAST integration documentation (connector framework, policy templates). | Depends on CLI samples (132_CLCI0110) | DOSB0101 | -| DOCS-SCANNER-DET-01 | BLOCKED (2025-11-23) | 2025-11-23 | SPRINT_0301_0001_0001_docs_md_i | Docs Guild · Scanner Guild | docs/modules/scanner/benchmarks | `/docs/modules/scanner/deterministic-sbom-compose.md` plus scan guide updates. | Needs determinism harness from 137_SCDT0101 | DOSB0101 | +| DOCS-SCANNER-DET-01 | DONE (2025-12-03) | 2025-12-03 | SPRINT_0301_0001_0001_docs_md_i | Docs Guild · Scanner Guild | docs/modules/scanner/benchmarks | `/docs/modules/scanner/deterministic-sbom-compose.md` plus scan guide updates + fixture bundle (`docs/modules/scanner/fixtures/deterministic-compose/`). | Fixtures published via Sprint 0136; harness verified. | DOSB0101 | | DOCS-SDK-62-001 | TODO | | SPRINT_309_docs_tasks_md_ix | Docs Guild · SDK Generator Guild | docs/sdk | Publish `/docs/sdks/overview.md` plus language guides (`typescript.md`, `python.md`, `go.md`, `java.md`). | Need SDK toolchain notes from SDKG0101 | DOSK0101 | | DOCS-SEC-62-001 | TODO | | SPRINT_309_docs_tasks_md_ix | Docs Guild · Security Guild | docs/security | Update `/docs/security/auth-scopes.md` with OAuth2/PAT scopes, tenancy header usage. | Need security ADR from DVDO0110 | DOSE0101 | | DOCS-SEC-OBS-50-001 | TODO | | SPRINT_309_docs_tasks_md_ix | Docs Guild · Security Guild | docs/security | Update `/docs/security/redaction-and-privacy.md` to cover telemetry privacy controls, tenant opt-in debug, and imposed rule reminder. | Depends on PLOB0101 metrics | DOSE0101 | @@ -3883,7 +3883,7 @@ | SCANNER-BENCH-62-008 | TODO | | SPRINT_310_docs_tasks_md_x | Docs Guild, EntryTrace Guild (docs) | | | | | | SCANNER-BENCH-62-009 | TODO | | SPRINT_310_docs_tasks_md_x | Docs Guild, Policy Guild (docs) | | | | | | SCANNER-CLI-0001 | DONE | 2025-11-10 | SPRINT_0138_0000_0001_scanner_ruby_parity | CLI Guild, Ruby Analyzer Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Coordinate CLI UX/help text for new Ruby verbs and update CLI docs/golden outputs. | SCANNER-ENG-0019 | | -| SCANNER-DET-01 | BLOCKED (2025-11-23) | 2025-11-23 | SPRINT_0301_0001_0001_docs_md_i | Docs Guild · Scanner Guild | | | | | +| SCANNER-DET-01 | DONE (2025-12-03) | 2025-12-03 | SPRINT_0301_0001_0001_docs_md_i | Docs Guild · Scanner Guild | | Deterministic compose fixtures landed; docs published. | | | SCANNER-DOCS-0003 | TODO | | SPRINT_327_docs_modules_scanner | Docs Guild, Product Guild (docs/modules/scanner) | docs/modules/scanner | Gather Windows/macOS analyzer demand signals and record findings in `docs/benchmarks/scanner/windows-macos-demand.md` for marketing + product readiness. | | | | SCANNER-EMIT-15-001 | TODO | | SPRINT_136_scanner_surface | Scanner Emit Guild (src/Scanner/__Libraries/StellaOps.Scanner.Emit) | src/Scanner/__Libraries/StellaOps.Scanner.Emit | Enforce canonical JSON (`stella.contentHash`, Merkle root metadata, zero timestamps) for fragments and composed CycloneDX inventory/usage BOMs. Documented in `docs/modules/scanner/deterministic-sbom-compose.md` §2.2. | SCANNER-SURFACE-04 | | | SCANNER-ENG-0001 | TODO | | SPRINT_327_docs_modules_scanner | Module Team (docs/modules/scanner) | docs/modules/scanner | Cross-check implementation plan milestones against `/docs/implplan/SPRINT_*.md` and update module readiness checkpoints. | | | @@ -4070,7 +4070,7 @@ | SURFACE-FS-04 | TODO | | SPRINT_136_scanner_surface | Zastava Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS | Integrate Surface.FS reader into Zastava Observer runtime drift loop. | SURFACE-FS-02 | | | SURFACE-FS-05 | TODO | | SPRINT_136_scanner_surface | Scanner Guild, Scheduler Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS | Expose Surface.FS pointers via Scanner WebService reports and coordinate rescan planning with Scheduler. | SURFACE-FS-03 | | | SURFACE-FS-06 | TODO | | SPRINT_136_scanner_surface | Docs Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS | Update scanner-engine guide and offline kit docs with Surface.FS workflow. | SURFACE-FS-02 | | -| SURFACE-FS-07 | TODO | | SPRINT_136_scanner_surface | Scanner Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS | Extend Surface.FS manifest schema with `composition.recipe`, fragment attestation metadata, and verification helpers per deterministic SBOM spec. | SCANNER-SURFACE-04 | | +| SURFACE-FS-07 | DONE | 2025-12-04 | SPRINT_136_scanner_surface | Scanner Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS | Extend Surface.FS manifest schema with `composition.recipe`, fragment attestation metadata, and verification helpers per deterministic SBOM spec. | SCANNER-SURFACE-04 | | | SURFACE-SECRETS-01 | DOING | 2025-11-02 | SPRINT_136_scanner_surface | Scanner Guild, Security Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.Secrets) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.Secrets | Produce `surface-secrets.md` defining secret reference schema, storage backends, scopes, and rotation rules. | | | | SURFACE-SECRETS-02 | DOING | 2025-11-02 | SPRINT_136_scanner_surface | Scanner Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.Secrets) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.Secrets | Implement `StellaOps.Scanner.Surface.Secrets` core provider interfaces, secret models, and in-memory test backend. | SURFACE-SECRETS-01 | | | SURFACE-SECRETS-03 | TODO | | SPRINT_136_scanner_surface | Scanner Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.Secrets) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.Secrets | Add Kubernetes/File/Offline backends with deterministic caching and audit hooks. | SURFACE-SECRETS-02 | SCSS0101 | diff --git a/docs/modules/evidence-locker/bundle-packaging.md b/docs/modules/evidence-locker/bundle-packaging.md index 8a55c6d06..a6255f02c 100644 --- a/docs/modules/evidence-locker/bundle-packaging.md +++ b/docs/modules/evidence-locker/bundle-packaging.md @@ -44,6 +44,8 @@ Upcoming EB1–EB10 remediation (Sprint 0161; advisory `docs/product-advisories/ - Ship an offline verifier script and golden bundles/replay fixtures to prove determinism. - Add incident-mode activation/exit records and redaction/tenant isolation guidance for portable bundles. +Canonical schemas now live in `docs/modules/evidence-locker/schemas/` (EB1, EB2). Offline verification steps and the embeddable script are documented in `docs/modules/evidence-locker/verify-offline.md` (EB9); use the computed Merkle root as the DSSE subject for sealed and portable bundles. + ### Merkle recipe (example) ```bash cd bundle diff --git a/docs/modules/evidence-locker/eb-gaps-161-007-plan.md b/docs/modules/evidence-locker/eb-gaps-161-007-plan.md new file mode 100644 index 000000000..ecd807ae8 --- /dev/null +++ b/docs/modules/evidence-locker/eb-gaps-161-007-plan.md @@ -0,0 +1,32 @@ +# EB1–EB10 Gap Closure Plan (EVID-GAPS-161-007) + +Purpose: track remediation items from the 28-Nov-2025 advisory so Evidence Locker bundles, replay payloads, and portable exports are provably deterministic and verifiable offline. + +Working directory: `docs/implplan` (sprint coordination) with artefacts in `docs/modules/evidence-locker` and `tests/EvidenceLocker`. + +## Scope Items +| ID | Deliverable | Artifact / Path | Owner(s) | Acceptance / Notes | Status | +| --- | --- | --- | --- | --- | --- | +| EB1 | Publish canonical manifest schema | `docs/modules/evidence-locker/schemas/bundle.manifest.schema.json` | Evidence Locker Guild | JSON Schema matches EvidenceBundleManifest (bundleId, tenantId, kind, metadata, entries) and captures replay/incident/redaction hooks. | Draft (2025-12-04) | +| EB2 | Publish checksums schema | `docs/modules/evidence-locker/schemas/checksums.schema.json` | Evidence Locker Guild | Canonical map for `checksums.txt`; Merkle root + chunking metadata; sorted entry rule recorded. | Draft (2025-12-04) | +| EB3 | Hash/Merkle recipe doc | `docs/modules/evidence-locker/bundle-packaging.md` (new section) | Evidence Locker Guild | Normative steps for Merkle root + DSSE subject; clarifies gzip/tar invariants and CAS compatibility. | TODO | +| EB4 | Mandatory DSSE predicate/log policy | `docs/modules/evidence-locker/attestation-contract.md` | Evidence Locker Guild · Security Guild | Required claims + signing profiles; Rekor/log policy (optional vs required); aligns with crypto registry defaults. | TODO | +| EB5 | Replay provenance block | `docs/modules/evidence-locker/replay-payload-contract.md` + manifest schema | Evidence Locker Guild · Replay Delivery Guild | Replay digest + DSSE envelope recorded; ordering rules match `DETERMINISTIC_REPLAY.md`; portable bundle retains linkage. | TODO | +| EB6 | Chunking/CAS rules | `checksums.schema.json` + `bundle-packaging.md` | Evidence Locker Guild · Storage/DevOps | Defines chunk sizing, CAS digest, and stability guarantees; CI test to catch ordering changes. | TODO | +| EB7 | Incident-mode signed activation/exit | `docs/modules/evidence-locker/incident-mode.md` | Evidence Locker Guild · Security Guild | Manifest/DSSE captures activation + deactivation events with signer identity; API/CLI steps documented. | TODO | +| EB8 | Tenant isolation + redaction manifest | `bundle-packaging.md` + portable bundle guidance | Evidence Locker Guild · Privacy Guild | Portable bundles omit tenant identifiers; redaction map recorded; verifier asserts redacted fields absent. | TODO | +| EB9 | Offline verifier script | `docs/modules/evidence-locker/verify-offline.md` | Evidence Locker Guild | POSIX script included; no network dependencies; emits Merkle root used by DSSE subject. | DONE (2025-12-04) | +| EB10 | Golden bundles/replay fixtures + SemVer/changelog | `tests/EvidenceLocker/Bundles/Golden/` + release notes (TBD) | Evidence Locker Guild · CLI Guild | Golden sealed + portable bundles and replay NDJSON with expected roots; changelog bump covering EB1–EB9. | TODO | + +## Near-Term Actions (to move EB1–EB10 to DONE) +- Wire schemas into EvidenceLocker CI (manifest + checksums validation) and surface in API/CLI OpenAPI/Help. +- Update `attestation-contract.md` and `incident-mode.md` with DSSE predicate/log policy and signed incident toggles (EB4, EB7). +- Extend replay contract with provenance block and ordering example, and mirror in manifest schema (EB5). +- Add normative Merkle/CAS section to `bundle-packaging.md`, ensuring DSSE subject references the root hash (EB3, EB6). +- Create golden fixtures under `tests/EvidenceLocker/Bundles/Golden/` with recorded expected hashes and replay traces; hook into xUnit tests (EB10). +- Bump Evidence Locker and CLI SemVer and changelog once above artefacts are wired (EB10). + +## Dependencies and Links +- Advisory: `docs/product-advisories/archived/27-Nov-2025-superseded/28-Nov-2025 - Evidence Bundle and Replay Contracts.md` +- Replay rules: `docs/replay/DETERMINISTIC_REPLAY.md` +- Sprint tracking: `docs/implplan/SPRINT_0161_0001_0001_evidencelocker.md` (EVID-GAPS-161-007) diff --git a/docs/modules/evidence-locker/schemas/bundle.manifest.schema.json b/docs/modules/evidence-locker/schemas/bundle.manifest.schema.json new file mode 100644 index 000000000..b9c608414 --- /dev/null +++ b/docs/modules/evidence-locker/schemas/bundle.manifest.schema.json @@ -0,0 +1,142 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://stellaops.local/schemas/evidence/bundle.manifest.schema.json", + "title": "StellaOps Evidence Bundle Manifest (EB1)", + "description": "Canonical manifest for deterministic evidence bundles; aligns with EvidenceLocker build models and EB1–EB10 advisory gaps.", + "type": "object", + "additionalProperties": false, + "required": [ + "bundleId", + "tenantId", + "kind", + "createdAt", + "metadata", + "entries" + ], + "properties": { + "bundleId": { + "type": "string", + "description": "Bundle identifier in UUID v4 N-format (no dashes).", + "pattern": "^[0-9a-fA-F]{32}$" + }, + "tenantId": { + "type": "string", + "description": "Tenant identifier in UUID v4 N-format (no dashes).", + "pattern": "^[0-9a-fA-F]{32}$" + }, + "kind": { + "description": "Bundle category; numeric values mirror EvidenceBundleKind enum.", + "oneOf": [ + { "type": "string", "enum": ["evaluation", "job", "export"] }, + { "type": "integer", "enum": [1, 2, 3] } + ] + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "Bundle creation timestamp (UTC, RFC3339)." + }, + "metadata": { + "type": "object", + "description": "Arbitrary key/value metadata captured at bundle creation.", + "additionalProperties": { "type": "string" } + }, + "entries": { + "type": "array", + "description": "Canonical file inventory used to derive checksums and Merkle root.", + "minItems": 1, + "items": { "$ref": "#/$defs/manifestEntry" } + }, + "hashSummary": { + "type": "object", + "description": "Optional Merkle root summary that binds the manifest to checksums.txt.", + "additionalProperties": false, + "required": ["algorithm", "merkleRoot"], + "properties": { + "algorithm": { "type": "string", "enum": ["sha256"] }, + "merkleRoot": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, + "checksumsPath": { + "type": "string", + "description": "Relative path to canonical checksums file inside the bundle.", + "default": "checksums.txt" + } + } + }, + "replayProvenance": { + "type": "object", + "description": "Optional replay linkage proving how the bundle was produced for deterministic re-run.", + "additionalProperties": false, + "required": ["recordDigest"], + "properties": { + "recordDigest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }, + "sequence": { "type": "integer", "minimum": 0 }, + "ledgerUri": { "type": "string", "format": "uri" }, + "dsseEnvelope": { + "type": "string", + "description": "Base64-encoded DSSE envelope for replay record provenance.", + "contentEncoding": "base64" + }, + "transparencyLog": { + "type": "object", + "additionalProperties": false, + "properties": { + "rekorUuid": { "type": "string" }, + "logIndex": { "type": "integer", "minimum": 0 }, + "inclusionProof": { "type": "string" } + } + } + } + }, + "incident": { + "type": "object", + "description": "Incident-mode activation/exit records captured at bundle time.", + "additionalProperties": false, + "properties": { + "activatedAt": { "type": "string", "format": "date-time" }, + "activatedBy": { "type": "string" }, + "reason": { "type": "string" }, + "deactivatedAt": { "type": "string", "format": "date-time" }, + "deactivatedBy": { "type": "string" } + } + }, + "redaction": { + "type": "object", + "description": "Portable-bundle redaction details to prove tenant isolation.", + "additionalProperties": false, + "properties": { + "portable": { "type": "boolean", "default": false }, + "maskedFields": { + "type": "array", + "items": { "type": "string" } + }, + "tenantToken": { + "type": "string", + "description": "Opaque token replacing tenantId in portable bundles." + } + } + } + }, + "$defs": { + "manifestEntry": { + "type": "object", + "additionalProperties": false, + "required": ["section", "canonicalPath", "sha256", "sizeBytes", "mediaType"], + "properties": { + "section": { "type": "string", "minLength": 1 }, + "canonicalPath": { + "type": "string", + "description": "Deterministic path within the bundle using '/' separators.", + "pattern": "^(?:[A-Za-z0-9_.-]+/)*[A-Za-z0-9_.-]+$" + }, + "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, + "sizeBytes": { "type": "integer", "minimum": 0 }, + "mediaType": { "type": "string" }, + "attributes": { + "type": "object", + "description": "Section-specific attributes (e.g., sbom format, dsse predicate).", + "additionalProperties": { "type": "string" } + } + } + } + } +} diff --git a/docs/modules/evidence-locker/schemas/checksums.schema.json b/docs/modules/evidence-locker/schemas/checksums.schema.json new file mode 100644 index 000000000..64274b3b7 --- /dev/null +++ b/docs/modules/evidence-locker/schemas/checksums.schema.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://stellaops.local/schemas/evidence/checksums.schema.json", + "title": "StellaOps Evidence Bundle Checksums (EB2)", + "description": "Canonical checksum map used to derive the Merkle root and DSSE subject for evidence bundles.", + "type": "object", + "additionalProperties": false, + "required": ["algorithm", "root", "entries"], + "properties": { + "algorithm": { "type": "string", "enum": ["sha256"] }, + "root": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, + "generatedAt": { "type": "string", "format": "date-time" }, + "bundleId": { "type": "string", "pattern": "^[0-9a-fA-F]{32}$" }, + "tenantId": { "type": "string", "pattern": "^[0-9a-fA-F]{32}$" }, + "entries": { + "type": "array", + "minItems": 1, + "description": "Sorted list of entry hashes; order must be lexicographic on canonicalPath.", + "items": { "$ref": "#/$defs/checksumEntry" } + }, + "chunking": { + "type": "object", + "description": "Optional chunked/CAS hashing strategy for large payloads.", + "additionalProperties": false, + "properties": { + "strategy": { "type": "string", "enum": ["none", "fixed", "buzhash"] }, + "chunkSizeBytes": { "type": "integer", "minimum": 1024 }, + "casDigestAlgorithm": { "type": "string", "enum": ["sha256"] } + } + } + }, + "$defs": { + "checksumEntry": { + "type": "object", + "additionalProperties": false, + "required": ["canonicalPath", "sha256", "sizeBytes"], + "properties": { + "canonicalPath": { + "type": "string", + "pattern": "^(?:[A-Za-z0-9_.-]+/)*[A-Za-z0-9_.-]+$" + }, + "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, + "sizeBytes": { "type": "integer", "minimum": 0 } + } + } + } +} diff --git a/docs/modules/evidence-locker/verify-offline.md b/docs/modules/evidence-locker/verify-offline.md new file mode 100644 index 000000000..b57b9e27e --- /dev/null +++ b/docs/modules/evidence-locker/verify-offline.md @@ -0,0 +1,51 @@ +# Offline Verification Playbook (EB9) + +Purpose: allow auditors to validate Evidence Locker bundles without network access, using only POSIX tools. Applies to both sealed `bundle.tgz` and portable `portable-bundle-v1.tgz`. + +## Prerequisites +- `tar`, `sha256sum` (or `shasum`), `awk`, `base64`. +- Optional: `jq` for schema validation; `cosign` or `stella` CLI for DSSE verification if pre-loaded. + +## Quick steps (sealed bundle) +1) `tar -xzf bundle.tgz -C /tmp/bundle` +2) `cd /tmp/bundle` +3) Validate checksums: `sha256sum -c checksums.txt` +4) Derive Merkle root (matches DSSE subject): `sha256sum checksums.txt | awk '{print $1}'` +5) Validate manifest against schema (if `jq` present): `jq -e 'input | type=="object"' manifest.json >/dev/null` +6) Verify DSSE envelope (optional but recommended): + - `cat manifest.json | base64 | cosign verify-blob --key cosign.pub --bundle signature.json --bundleType dsse` + - or `stella evidence verify --bundle ../bundle.tgz --offline` once CLI supports offline mode. + +## Quick steps (portable bundle) +Same as sealed, plus confirm redaction: +- `jq -e 'has(\"redaction\") and .redaction.portable==true' manifest.json >/dev/null` (if `jq` available) +- Confirm no tenant identifiers in `bundle.json` and `manifest.json`. + +## Embeddable verifier script +Place the following script into `verify-offline.sh` when assembling portable bundles. It exits non-zero on any mismatch and prints the Merkle root used as DSSE subject. + +```bash +#!/usr/bin/env bash +set -euo pipefail +BUNDLE="${1:-bundle.tgz}" +WORKDIR="$(mktemp -d)" +cleanup() { rm -rf "$WORKDIR"; } +trap cleanup EXIT +tar -xzf "$BUNDLE" -C "$WORKDIR" +cd "$WORKDIR" +sha256sum -c checksums.txt +MERKLE=$(sha256sum checksums.txt | awk '{print $1}') +printf "merkle_root=%s\n" "$MERKLE" +if command -v jq >/dev/null; then + jq -e 'type=="object" and has("entries")' manifest.json >/dev/null +fi +``` + +## Fixtures +- Golden bundles and replay records live under `tests/EvidenceLocker/Bundles/Golden/`. +- Expected Merkle roots and DSSE payload digests should be recorded alongside each fixture to keep CI deterministic. + +## References +- Manifest schema: `docs/modules/evidence-locker/schemas/bundle.manifest.schema.json` +- Checksums schema: `docs/modules/evidence-locker/schemas/checksums.schema.json` +- Merkle recipe: see `docs/modules/evidence-locker/bundle-packaging.md` diff --git a/docs/modules/export-center/determinism.md b/docs/modules/export-center/determinism.md index eae0fdd8c..d56841d5e 100644 --- a/docs/modules/export-center/determinism.md +++ b/docs/modules/export-center/determinism.md @@ -1,34 +1,58 @@ # Export Center Determinism & Rerun Hash Guide -Advisory: `docs/product-advisories/28-Nov-2025 - Export Center and Reporting Strategy.md` (EC1–EC10). +Advisory anchor: `docs/product-advisories/archived/27-Nov-2025-superseded/28-Nov-2025 - Export Center and Reporting Strategy.md` (EC1–EC10). -## Adapter settings (runnable example) -- JSON adapters: `--compression zstd --compression-level 19 --deterministic-order` -- Mirror adapter: sort descriptors by digest, emit annotations in lexicographic order, disable mtime in tar (`--mtime 0`). -- Delta adapter: include `baseManifestHash` and sorted `added`/`removed` lists; tombstones must be explicit. +## EC1 — Signed schemas +- Export profile schema: `docs/modules/export-center/schemas/export-profile.schema.json` (selectors, approvals, quotas). +- Export manifest schema: `docs/modules/export-center/schemas/export-manifest.schema.json` (rerunHash, integrity headers, attestations, quotas/backpressure). +- Both schemas must be signed (DSSE) alongside publication; DSSE envelopes live next to the schema files when generated in CI. -## Rerun-hash check +## EC2 — Per-adapter determinism and rerun hash +- JSON adapters: canonical JSONL, sorted keys, zstd level 19; filenames stable (`advisories-.jsonl.zst`); gzip forbidden. +- Trivy adapters: pin schema version (see `trivy-adapter.md`), normalize namespaces, ordered records by `(namespace, package, vulnerabilityId)`. +- Mirror full: tar with `--sort=name --mtime=@0 --owner=0 --group=0 --numeric-owner`; manifest entries sorted by path; indexes stable. +- Mirror delta: include `baseManifestDigest`, sorted `added`/`removed`, explicit `tombstones`; reject deltas without tombstones for removed entries. +- Rerun hash algorithm: SHA-256 over newline-joined, sorted `contents[*].digest` values; stored in `manifest.rerunHash` and asserted in CI. +- CI harness: `docs/modules/export-center/operations/verify-export-kit.sh` recomputes rerun hash and schema-consistent integrity hints. + +## EC3 — DSSE + SLSA attestation with log metadata +- All manifests and provenance files carry DSSE envelopes; provenance must include SLSA v1 builder metadata plus log proof (`kind`, `logId`, `logIndex`, `entryDigest`, `timestamp`). +- Provenance subjects list both `manifests/export.json` and bundle tar/OCI digest; log metadata is mandatory even when transparency uploads are deferred. + +## EC4 — Cross-tenant approval flow +- `selectors.tenants` must contain the profile tenant; when selectors include additional tenants or wildcards, `approval.required=true` with `approvedBy` and `ticket` is mandatory (validated by the verify script). + +## EC5 — Distribution integrity headers and OCI annotations +- HTTP: `Digest: sha-256=` derived from bundle digest; `X-Stella-Signature: dsse-b64:`; `X-Stella-Immutability: true` for immutable responses. +- OCI: annotations must include `io.stellaops.export.profile`, `io.stellaops.export.run`, `io.stellaops.export.manifest-digest`, `io.stellaops.export.provenance-ref`, and `org.opencontainers.image.ref.name`. + +## EC6 — Trivy schema pinning +- Schema compatibility is pinned in `trivy-adapter.md`; CI rejects versions above the pinned set and emits `ERR_EXPORT_UNSUPPORTED_SCHEMA`. +- Mirror/export manifests must record the targeted `schemaVersion` so rerun-hash and consumers can enforce deterministic decoding. + +## EC7 — Mirror delta/tombstone rules +- Deltas MUST include tombstones for all removals and a `baseManifestDigest` that matches the referenced baseline; omitted tombstones fail verification. +- `delta.added/removed` are sorted, and `resetBaseline=false` unless explicitly set; consumers apply deltas in order and refuse out-of-order manifests. + +## EC8 — Encryption/recipient policy +- Only `age` or `aes-gcm` envelopes; recipients enumerated with `fingerprint` and optional `wrappedKey` in manifest and provenance. +- `strict=true` encrypts everything except manifest/provenance; defaults to `false` to keep discovery metadata plaintext. + +## EC9 — Quotas and backpressure +- Manifest `quotas` block captures `maxActiveRuns`, `maxQueuedRuns`, `backpressureMode` (`reject`|`defer`|`throttle`), and optional `cpuThrottlePercent`. +- CI verifies presence of quotas; operators surface `429` with `X-Stella-Quota-*` hints when limits engage. + +## EC10 — Offline export kit + verify script +- Fixtures: `src/ExportCenter/__fixtures/export-kit/*` (manifest, manifest.sha256, manifest.dsse, provenance). +- Verifier: `docs/modules/export-center/operations/verify-export-kit.sh` + - Validates manifest hash against `manifest.sha256`. + - Recomputes rerun hash. + - Confirms integrity headers align with OCI annotations. + - Enforces approval + quota presence for cross-tenant selectors. + - Confirms provenance references manifest digest and carries log metadata. +- Tar flags for offline kit assembly: `tar --sort=name --mtime=@0 --owner=0 --group=0 --numeric-owner`. + +## Quick rerun-hash smoke (uses fixtures) ```bash -set -euo pipefail -run_id=$(uuidgen) -stella export run --profile demo --run-id "$run_id" --out /tmp/export1 -sha256sum /tmp/export1/manifest.json > /tmp/export1/manifest.sha256 -# second run -run_id2=$(uuidgen) -stella export run --profile demo --run-id "$run_id2" --out /tmp/export2 -sha256sum /tmp/export2/manifest.json > /tmp/export2/manifest.sha256 -diff -u /tmp/export1/manifest.sha256 /tmp/export2/manifest.sha256 +./docs/modules/export-center/operations/verify-export-kit.sh src/ExportCenter/__fixtures/export-kit ``` - -## Integrity headers (HTTP example) -- `Digest: sha-256=` -- `X-Stella-Signature: dsse-b64=` -- `X-Stella-Immutability: true` - -## Offline kit packaging -- Tar flags: `tar --sort=name --mtime=@0 --owner=0 --group=0 --numeric-owner` -- Include `export-kit/manifest.json` + `manifest.dsse`; add `verify-export-kit.sh` to check hashes and signatures. - -## Where to place fixtures -- `src/ExportCenter/__fixtures/` for deterministic manifests/outputs used by tests. -- Add rerun-hash CI to compare fixture hash against regenerated outputs. diff --git a/docs/modules/export-center/mirror-bundles.md b/docs/modules/export-center/mirror-bundles.md index 3bc7a1931..707d9404f 100644 --- a/docs/modules/export-center/mirror-bundles.md +++ b/docs/modules/export-center/mirror-bundles.md @@ -92,9 +92,11 @@ delta/ manifest.diff.json # summary of counts, hashes, base export metadata ``` -- **Base lookup:** The worker verifies that the base export is reachable (download path or OCI reference). If missing, the run fails with `ERR_EXPORT_BASE_MISSING`. -- **Change detection:** Uses deterministic hashing of normalized records to compute additions/updates. Indexes are regenerated only for affected subjects. -- **Application order:** Consumers apply deltas sequentially. A `resetBaseline=true` flag instructs them to drop cached state and apply the bundle as a full refresh. +- **Base lookup:** The worker verifies that the base export is reachable (download path or OCI reference). If missing, the run fails with `ERR_EXPORT_BASE_MISSING`. +- **Change detection:** Uses deterministic hashing of normalized records to compute additions/updates. Indexes are regenerated only for affected subjects. +- **Application order:** Consumers apply deltas sequentially. A `resetBaseline=true` flag instructs them to drop cached state and apply the bundle as a full refresh. +- **Tombstones required:** Every removal must emit a tombstone entry plus the `removed` list; deltas without tombstones fail verification (`verify-export-kit.sh`). +- **Integrity headers:** Each delta bundle exports `Digest`, `X-Stella-Signature`, and `X-Stella-Immutability` derived from the OCI annotation `io.stellaops.export.manifest-digest`. Consumers must validate before applying. Example `manifest.diff.json` (delta): @@ -195,6 +197,7 @@ sequenceDiagram - Post-import checks: - Recompute SHA256 for `manifest.yaml` and a sample data file; compare to manifest. - Run `mirror verify` (Offline Kit) and confirm zero mismatches. + - Confirm OCI annotations `io.stellaops.export.profile/run/manifest-digest/provenance-ref` match the bundle being applied. ## 8. Troubleshooting diff --git a/docs/modules/export-center/operations/verify-export-kit.sh b/docs/modules/export-center/operations/verify-export-kit.sh new file mode 100644 index 000000000..271a01a8e --- /dev/null +++ b/docs/modules/export-center/operations/verify-export-kit.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Offline verifier for Export Center bundles (EC10) +# Usage: VERIFY_FIXTURE=1 ./verify-export-kit.sh [kit_dir] + +kit_dir="${1:-src/ExportCenter/__fixtures/export-kit}" + +for f in manifest.json manifest.sha256 provenance.json manifest.dsse; do + if [[ ! -f "${kit_dir}/${f}" ]]; then + echo "missing ${kit_dir}/${f}" >&2 + exit 1 + fi +done + +manifest_sha="$(sha256sum "${kit_dir}/manifest.json" | awk '{print $1}')" +expected_sha="$(awk '{print $1}' "${kit_dir}/manifest.sha256")" + +if [[ "${manifest_sha}" != "${expected_sha}" ]]; then + echo "manifest hash mismatch: expected ${expected_sha} got ${manifest_sha}" >&2 + exit 1 +fi + +python - <<'PY' "${kit_dir}" "${manifest_sha}" +import base64, hashlib, json, sys, pathlib + +kit = pathlib.Path(sys.argv[1]) +manifest_sha = sys.argv[2] +manifest = json.loads(kit.joinpath("manifest.json").read_text()) + +errors = [] +selectors = manifest.get("selectors", {}) +tenants = selectors.get("tenants", []) +tenant = manifest.get("tenant") + +if tenant and tenants and tenant not in tenants and "*" not in tenants[0]: + errors.append(f"tenant {tenant} not included in selectors.tenants {tenants}") + +digests = [] +for item in manifest.get("contents", []): + digest = item.get("digest", "") + if not digest.startswith("sha256:"): + errors.append(f"invalid digest format for {item.get('path')}") + continue + digests.append(digest.split("sha256:")[1]) + +rerun_calc = hashlib.sha256("\n".join(sorted(digests)).encode()).hexdigest() +rerun_expected = manifest.get("rerunHash") +if rerun_expected != f"sha256:{rerun_calc}": + errors.append(f"rerunHash mismatch: expected sha256:{rerun_calc} got {rerun_expected}") + +annotations = manifest["integrity"]["oci"]["annotations"] +manifest_digest_hex = annotations["io.stellaops.export.manifest-digest"].split("sha256:")[1] +digest_hdr = manifest["integrity"]["httpHeaders"]["Digest"] +expected_hdr = "sha-256=" + base64.b64encode(bytes.fromhex(manifest_digest_hex)).decode() +if digest_hdr != expected_hdr: + errors.append("Digest header does not match manifest digest annotation") + +log_meta = manifest.get("attestations", {}).get("log") +if not log_meta: + errors.append("attestation log metadata missing") + +if not manifest.get("quotas"): + errors.append("quotas/backpressure block missing") + +if len(set(tenants)) > 1 and not manifest.get("approval", {}).get("required"): + errors.append("cross-tenant approval required but not present") + +if errors: + for err in errors: + print(err) + sys.exit(1) + +print("manifest checks ok") +PY + +python - <<'PY' "${kit_dir}" "${manifest_sha}" +import json, sys, pathlib + +kit = pathlib.Path(sys.argv[1]) +manifest_sha = sys.argv[2] +prov = json.loads(kit.joinpath("provenance.json").read_text()) + +manifest_subject = prov["subject"][0]["digest"]["sha256"] +if manifest_subject != manifest_sha: + print(f"provenance manifest digest mismatch: {manifest_subject} vs {manifest_sha}") + sys.exit(1) + +log = prov["predicate"]["environment"]["logs"] +required = ["kind", "logId", "logIndex", "entryDigest", "timestamp"] +missing = [k for k in required if k not in log] +if missing: + print(f"provenance log metadata missing keys: {missing}") + sys.exit(1) + +print("provenance checks ok") +PY + +echo "verify-export-kit: PASS" diff --git a/docs/modules/export-center/profiles.md b/docs/modules/export-center/profiles.md index 222c44388..12b8f8d9d 100644 --- a/docs/modules/export-center/profiles.md +++ b/docs/modules/export-center/profiles.md @@ -102,12 +102,12 @@ Selectors (time windows, tenants, products, SBOM subjects, ecosystems) are suppl - **Constraints:** Requires the base manifest to exist in object storage or artifact registry accessible to the worker. Fails with `ERR_EXPORT_BASE_MISSING` otherwise. - **Workflow:** Ideal for frequent updates to mirrored environments with limited bandwidth. -## Compatibility and guardrails -- **Aggregation-Only Contract:** All profiles respect AOC boundaries: raw evidence is never mutated. Policy outputs are appended separately with clear provenance. -- **Tenant scoping:** Profiles are tenant-specific. Cross-tenant exports require explicit administrative approval and signed justification. -- **Retriable runs:** Re-running a profile with identical selectors yields matching manifests and hashes, facilitating verify-on-download workflows. -- **Offline operation:** JSON and mirror profiles function in offline mode without additional configuration. Trivy profiles require pre-seeded schema metadata shipped via Offline Kit. -- **Quota integration:** Profiles can define run quotas (per tenant per day). Quota exhaustion surfaces as `429 Too Many Requests` with `X-Stella-Quota-*` hints. +## Compatibility and guardrails +- **Aggregation-Only Contract:** All profiles respect AOC boundaries: raw evidence is never mutated. Policy outputs are appended separately with clear provenance. +- **Tenant scoping + approvals:** Profiles are tenant-specific. When `selectors.tenants` includes additional tenants or wildcards, `approval.required=true` plus `approvedBy` and `ticket` must be present (validated by `verify-export-kit.sh` and schema). +- **Retriable runs:** Re-running a profile with identical selectors yields matching manifests and hashes, facilitating verify-on-download workflows. +- **Offline operation:** JSON and mirror profiles function in offline mode without additional configuration. Trivy profiles require pre-seeded schema metadata shipped via Offline Kit. +- **Quota integration and backpressure:** Profiles declare `limits.maxActiveRuns`, `limits.maxQueuedRuns`, and `backpressureMode` (`reject`|`defer`|`throttle`). When limits trigger, exporters emit `429` with `X-Stella-Quota-*` plus `Retry-After` to keep retries deterministic. ## Example profile definition (CLI) diff --git a/docs/modules/export-center/provenance-and-signing.md b/docs/modules/export-center/provenance-and-signing.md index 49fb317fe..bc0a56150 100644 --- a/docs/modules/export-center/provenance-and-signing.md +++ b/docs/modules/export-center/provenance-and-signing.md @@ -12,6 +12,7 @@ Export Center runs emit deterministic manifests, provenance records, and signatu - **Traceability.** Provenance links each bundle to the inputs that produced it: tenant, findings ledger queries, policy snapshots, SBOM identifiers, adapter versions, and encryption recipients. - **Determinism.** Canonical JSON (sorted keys, RFC 3339 UTC timestamps, normalized numbers) guarantees byte-for-byte stability across reruns with identical input. - **Portability.** Signatures and attestations travel with filesystem bundles, OCI artefacts, and Offline Kit staging trees. Verification does not require online Authority access when the bundle includes the cosign public key. +- **Transparency metadata.** DSSE/SLSA artefacts must embed log metadata (hashedrekord/rekor-style `logId`, `logIndex`, `entryDigest`, `timestamp`) so offline kits can prove submission intent even without online verification. --- @@ -38,8 +39,9 @@ All digests use lowercase hex SHA-256 (`sha256:`). When bundle encryptio - Provenance `subjects[]` contains both manifest hash and bundle/archive hash. 3. **Key retrieval.** Worker obtains a short-lived signing token from Authority’s KMS client using tenant-scoped credentials (`export.sign` scope). Keys live in Authority or tenant-specific HSMs depending on deployment. 4. **Signature emission.** Cosign generates detached signatures (`*.sig`). If DSSE is enabled, cosign wraps payload bytes in a DSSE envelope (`*.dsse`). Attestations follow the SLSA Level 2 provenance template; Level 3 requires builder metadata (`EXPORT-SVC-37-002` optional feature flag). -5. **Storage & distribution.** Signatures and attestations are written alongside manifests in object storage, included in filesystem bundles, and attached as OCI artefact layers/annotations. -6. **Audit trail.** Run metadata captures signer identity (`signing_key_id`), cosign certificate serial, signature timestamps, and verification hints. Console/CLI surface these details for downstream automation. +5. **Log metadata.** DSSE/SLSA outputs record log hints: `{kind: "hashedrekord", logId, logIndex, entryDigest, timestamp}`. For air-gap deployments the hints ride inside the attestation; when online, the same values come from Rekor receipts. +6. **Storage & distribution.** Signatures and attestations are written alongside manifests in object storage, included in filesystem bundles, and attached as OCI artefact layers/annotations. +7. **Audit trail.** Run metadata captures signer identity (`signing_key_id`), cosign certificate serial, signature timestamps, log hints, and verification outcomes. Console/CLI surface these details for downstream automation. > **Key management.** Secrets and key references are configured per tenant via `export.signing`, pointing to Authority clients or external HSM aliases. Offline deployments pre-load cosign public keys into the bundle (`signatures/pubkeys/{tenant}.pem`). @@ -61,6 +63,7 @@ All digests use lowercase hex SHA-256 (`sha256:`). When bundle encryptio | `predicate.metadata.reproducible` | Always `true`—workers guarantee determinism. | | `predicate.environment.encryption` | Records encryption recipients, wrapped keys, algorithm (`age` or `aes-gcm`). | | `predicate.environment.kms` | Signing key identifier (`authority://tenant/export-signing-key`) and certificate chain fingerprints. | +| `predicate.environment.logs` | Transparency metadata `{kind,logId,logIndex,entryDigest,timestamp}` required by EC3 to keep DSSE verifiable offline. | Sample (abridged): diff --git a/docs/modules/export-center/schemas/export-manifest.schema.json b/docs/modules/export-center/schemas/export-manifest.schema.json new file mode 100644 index 000000000..974e70dc8 --- /dev/null +++ b/docs/modules/export-center/schemas/export-manifest.schema.json @@ -0,0 +1,254 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://stellaops.io/schemas/export-center/export-manifest.schema.json", + "title": "StellaOps Export Manifest", + "description": "Schema for run manifests, attestations, integrity headers, and quota/backpressure metadata (EC2–EC9).", + "type": "object", + "required": [ + "schema", + "version", + "exportId", + "profile", + "tenant", + "selectors", + "generatedAt", + "contents" + ], + "properties": { + "schema": { "type": "string", "const": "https://stellaops.io/export-center/manifest/v1alpha2" }, + "version": { "type": "string", "pattern": "^1\\.1\\.[0-9]+$" }, + "exportId": { "type": "string", "pattern": "^[a-z0-9-]{6,64}$" }, + "runId": { "type": "string", "pattern": "^[a-z0-9-]{6,64}$" }, + "profile": { + "type": "object", + "required": ["kind", "variant", "name"], + "properties": { + "kind": { "type": "string", "enum": ["json", "trivy", "mirror", "devportal", "attestation"] }, + "variant": { + "type": "string", + "enum": ["raw", "policy", "db", "java-db", "full", "delta", "offline", "bundle"] + }, + "name": { "type": "string", "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$" }, + "revision": { "type": "string", "pattern": "^r[0-9]+$" } + }, + "additionalProperties": false + }, + "tenant": { "type": "string", "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$" }, + "selectors": { "$ref": "#/$defs/selectors" }, + "generatedAt": { "type": "string", "format": "date-time" }, + "rerunHash": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" }, + "contents": { + "type": "array", + "items": { + "type": "object", + "required": ["path", "digest", "bytes"], + "properties": { + "path": { "type": "string", "pattern": "^[A-Za-z0-9._/-]+$" }, + "digest": { "$ref": "#/$defs/digest" }, + "bytes": { "type": "integer", "minimum": 0 }, + "records": { "type": "integer", "minimum": 0 }, + "contentType": { "type": "string" } + }, + "additionalProperties": false + } + }, + "delta": { + "type": "object", + "required": ["baseExportId", "baseManifestDigest", "tombstones"], + "properties": { + "baseExportId": { "type": "string", "pattern": "^[a-z0-9-]{6,64}$" }, + "baseManifestDigest": { "$ref": "#/$defs/digest" }, + "tombstones": { + "type": "array", + "items": { "type": "string", "pattern": "^[A-Za-z0-9._/-]+$" } + }, + "added": { + "type": "array", + "items": { "type": "string", "pattern": "^[A-Za-z0-9._/-]+$" } + }, + "removed": { + "type": "array", + "items": { "type": "string", "pattern": "^[A-Za-z0-9._/-]+$" } + } + }, + "additionalProperties": false + }, + "integrity": { + "type": "object", + "required": ["httpHeaders", "oci"], + "properties": { + "httpHeaders": { + "type": "object", + "required": ["Digest", "X-Stella-Signature"], + "properties": { + "Digest": { "type": "string", "pattern": "^sha-256=[A-Za-z0-9+/=]+$" }, + "X-Stella-Signature": { "type": "string" }, + "X-Stella-Immutability": { "type": "string" } + }, + "additionalProperties": false + }, + "oci": { + "type": "object", + "required": ["annotations"], + "properties": { + "annotations": { + "type": "object", + "required": [ + "io.stellaops.export.profile", + "io.stellaops.export.run", + "io.stellaops.export.manifest-digest", + "io.stellaops.export.provenance-ref" + ], + "properties": { + "io.stellaops.export.profile": { "type": "string" }, + "io.stellaops.export.run": { "type": "string" }, + "io.stellaops.export.manifest-digest": { "$ref": "#/$defs/digest" }, + "io.stellaops.export.provenance-ref": { "type": "string" }, + "org.opencontainers.image.ref.name": { "type": "string" } + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "attestations": { + "type": "object", + "required": ["provenanceRef", "dsseEnvelope", "slsaLevel", "log"], + "properties": { + "provenanceRef": { "type": "string" }, + "dsseEnvelope": { "type": "string" }, + "slsaLevel": { "type": "string" }, + "log": { + "type": "object", + "required": ["kind", "logId", "logIndex", "entryDigest", "timestamp"], + "properties": { + "kind": { "type": "string", "enum": ["hashedrekord", "rekor"] }, + "logId": { "type": "string" }, + "logIndex": { "type": "integer", "minimum": 0 }, + "entryDigest": { "$ref": "#/$defs/digest" }, + "timestamp": { "type": "string", "format": "date-time" } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "distribution": { + "type": "object", + "properties": { + "http": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "retentionDays": { "type": "integer", "minimum": 1, "maximum": 3650 }, + "etag": { "type": "string" }, + "rangeRequests": { "type": "boolean" } + }, + "additionalProperties": false + }, + "oci": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "reference": { "type": "string" } + }, + "additionalProperties": false + }, + "object": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "bucket": { "type": "string" }, + "prefix": { "type": "string" } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "encryption": { + "type": "object", + "properties": { + "mode": { "type": "string", "enum": ["age", "aes-gcm"] }, + "recipients": { + "type": "array", + "items": { + "type": "object", + "required": ["keyId", "fingerprint"], + "properties": { + "keyId": { "type": "string" }, + "fingerprint": { "type": "string" }, + "wrappedKey": { "type": "string" } + }, + "additionalProperties": false + } + }, + "strict": { "type": "boolean" } + }, + "additionalProperties": false + }, + "approval": { + "type": "object", + "properties": { + "required": { "type": "boolean" }, + "reason": { "type": "string" }, + "approvedBy": { "type": "string" }, + "ticket": { "type": "string" } + }, + "additionalProperties": false + }, + "quotas": { + "type": "object", + "properties": { + "maxActiveRuns": { "type": "integer", "minimum": 1, "maximum": 32 }, + "maxQueuedRuns": { "type": "integer", "minimum": 1, "maximum": 512 }, + "backpressureMode": { + "type": "string", + "enum": ["reject", "defer", "throttle"] + }, + "cpuThrottlePercent": { "type": "integer", "minimum": 1, "maximum": 100 } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "$defs": { + "digest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" }, + "selectors": { + "type": "object", + "properties": { + "tenants": { + "type": "array", + "items": { "type": "string", "pattern": "^[a-z0-9*.-]+$" }, + "uniqueItems": true + }, + "products": { + "type": "array", + "items": { "type": "string", "pattern": "^pkg:[A-Za-z0-9.+\\-_/:@*]+$" } + }, + "timeWindow": { + "oneOf": [ + { "type": "string", "pattern": "^[0-9]+d$" }, + { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}/[0-9]{4}-[0-9]{2}-[0-9]{2}$" } + ] + }, + "severities": { + "type": "array", + "items": { "type": "string", "enum": ["critical", "high", "medium", "low", "info"] }, + "uniqueItems": true + }, + "ecosystems": { + "type": "array", + "items": { + "type": "string", + "enum": ["npm", "maven", "pypi", "nuget", "go", "cargo", "rpm", "deb", "apk", "java"] + }, + "uniqueItems": true + } + }, + "additionalProperties": false + } + } +} diff --git a/docs/modules/export-center/schemas/export-profile.schema.json b/docs/modules/export-center/schemas/export-profile.schema.json new file mode 100644 index 000000000..e5b249035 --- /dev/null +++ b/docs/modules/export-center/schemas/export-profile.schema.json @@ -0,0 +1,206 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://stellaops.io/schemas/export-center/export-profile.schema.json", + "title": "StellaOps ExportProfile", + "description": "Canonical schema for Export Center profile definitions with selector and approval guardrails (EC1, EC4, EC9).", + "type": "object", + "required": ["apiVersion", "kind", "metadata", "spec"], + "properties": { + "apiVersion": { + "type": "string", + "const": "stellaops.io/export.v1" + }, + "kind": { + "type": "string", + "const": "ExportProfile" + }, + "metadata": { + "type": "object", + "required": ["name", "tenant"], + "properties": { + "name": { + "type": "string", + "minLength": 3, + "maxLength": 64, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$" + }, + "tenant": { + "type": "string", + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$" + }, + "revision": { + "type": "string", + "pattern": "^r[0-9]+$" + }, + "labels": { + "type": "object", + "additionalProperties": { + "type": "string", + "maxLength": 128 + } + } + }, + "additionalProperties": false + }, + "spec": { + "type": "object", + "required": ["kind", "variant", "distribution"], + "properties": { + "kind": { + "type": "string", + "enum": ["json", "trivy", "mirror", "devportal", "attestation"] + }, + "variant": { + "type": "string", + "enum": [ + "raw", + "policy", + "db", + "java-db", + "full", + "delta", + "offline", + "bundle" + ] + }, + "distribution": { + "type": "array", + "items": { + "type": "string", + "enum": ["http", "oci", "object"] + }, + "uniqueItems": true, + "minItems": 1 + }, + "compression": { + "type": "object", + "properties": { + "codec": { + "type": "string", + "enum": ["zstd", "gzip", "none"] + }, + "level": { + "type": "integer", + "minimum": 1, + "maximum": 22 + } + }, + "additionalProperties": false + }, + "encryption": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "mode": { "type": "string", "enum": ["age", "aes-gcm"] }, + "recipientKeys": { + "type": "array", + "items": { + "type": "string", + "pattern": "^(age1|kms://)" + } + }, + "strict": { "type": "boolean" } + }, + "additionalProperties": false + }, + "retention": { + "type": "object", + "properties": { + "mode": { "type": "string", "enum": ["days", "never"] }, + "value": { "type": "integer", "minimum": 1, "maximum": 3650 } + }, + "additionalProperties": false + }, + "limits": { + "type": "object", + "properties": { + "maxActiveRuns": { "type": "integer", "minimum": 1, "maximum": 32 }, + "maxQueuedRuns": { "type": "integer", "minimum": 1, "maximum": 512 }, + "backpressureMode": { + "type": "string", + "enum": ["reject", "defer", "throttle"] + } + }, + "additionalProperties": false + }, + "selectors": { "$ref": "#/$defs/selectors" }, + "approval": { + "type": "object", + "properties": { + "required": { "type": "boolean" }, + "reason": { "type": "string", "maxLength": 256 }, + "ticket": { "type": "string", "maxLength": 64 }, + "approver": { "type": "string", "maxLength": 64 } + }, + "additionalProperties": false + }, + "schemaVersion": { + "type": "string", + "enum": ["1.1.0"], + "default": "1.1.0" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "$defs": { + "selectors": { + "type": "object", + "properties": { + "tenants": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9*.-]+$" + }, + "uniqueItems": true + }, + "products": { + "type": "array", + "items": { + "type": "string", + "pattern": "^pkg:[A-Za-z0-9.+\\-_/:@*]+$" + } + }, + "ecosystems": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "npm", + "maven", + "pypi", + "nuget", + "go", + "cargo", + "rpm", + "deb", + "apk", + "java" + ] + }, + "uniqueItems": true + }, + "timeWindow": { + "oneOf": [ + { "type": "string", "pattern": "^[0-9]+d$" }, + { + "type": "string", + "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}/[0-9]{4}-[0-9]{2}-[0-9]{2}$" + } + ] + }, + "severities": { + "type": "array", + "items": { + "type": "string", + "enum": ["critical", "high", "medium", "low", "info"] + }, + "uniqueItems": true + } + }, + "additionalProperties": false + } + } +} diff --git a/docs/modules/export-center/trivy-adapter.md b/docs/modules/export-center/trivy-adapter.md index 0d2e196f6..f39fb28bf 100644 --- a/docs/modules/export-center/trivy-adapter.md +++ b/docs/modules/export-center/trivy-adapter.md @@ -152,11 +152,11 @@ The Java supplement only includes ecosystems `maven`, `gradle`, `sbt`. Additiona ## 4. Compatibility matrix -| Trivy version | Schema version | Supported by adapter | Notes | -|---------------|----------------|----------------------|-------| -| 0.46.x | 2 | Yes | Baseline compatibility target. | -| 0.50.x | 2 | Yes | Default validation target in CI. | -| 0.51.x+ | 3 | Pending | Adapter throws `ERR_EXPORT_UNSUPPORTED_SCHEMA` until implemented. | +| Trivy version | Schema version | Supported by adapter | Notes | +|---------------|----------------|----------------------|-------| +| 0.46.x | 2 (pinned) | Yes | Baseline compatibility target. | +| 0.50.x | 2 (pinned) | Yes | Default validation target in CI and fixtures. | +| 0.51.x+ | 3 | Pending | Adapter throws `ERR_EXPORT_UNSUPPORTED_SCHEMA` until implemented or explicitly overridden. | Schema mismatches emit `adapter.trivy.unsupported_schema_version` and abort the run. Operators can pin the schema via `ExportCenter:Adapters:Trivy:SchemaVersion`. @@ -169,10 +169,13 @@ Schema mismatches emit `adapter.trivy.unsupported_schema_version` and abort the - Generate bundle from fixture dataset. - Run `trivy module db import ` (Trivy CLI) to ensure the bundle is accepted. - For Java DB, run `trivy java-repo --db ` against sample repository. -3. **CI smoke (`DEVOPS-EXPORT-36-001`)**: - - Validate metadata fields using `jq`. - - Ensure signatures verify with `cosign`. - - Check runtime by invoking `trivy fs --cache-dir --skip-update --custom-db fixtures/image`. +3. **CI smoke (`DEVOPS-EXPORT-36-001`)**: + - Validate metadata fields using `jq`. + - Ensure signatures verify with `cosign`. + - Check runtime by invoking `trivy fs --cache-dir --skip-update --custom-db fixtures/image`. +4. **Schema pinning (EC6)**: + - CI enforces `ExportCenter:Adapters:Trivy:SchemaVersion=2`; higher versions fail fast with `adapter.trivy.unsupported_schema_version`. + - Export manifests/OCI annotations record the pinned schema for rerun-hash stability. Failures set the run status to `failed` with `errorCode="adapter-trivy"` so Console/CLI expose the reason. diff --git a/docs/modules/sbomservice/architecture.md b/docs/modules/sbomservice/architecture.md index 181d4ee8f..702dada12 100644 --- a/docs/modules/sbomservice/architecture.md +++ b/docs/modules/sbomservice/architecture.md @@ -15,6 +15,8 @@ - `sbom_snapshots` (immutable versions; tenant + artifact + digest + createdAt) - `sbom_projections` (materialised views keyed by snapshotId, entrypoint/service node flags) - `sbom_assets` (asset metadata, criticality/owner/env/exposure; append-only history) + - `sbom_catalog` (console catalog surface; indexed by artifact, scope, license, assetTags.*, createdAt for deterministic pagination) + - `sbom_component_neighbors` (component lookup graph edges; indexed by purl+artifact for cursor pagination) - `sbom_paths` (resolved dependency paths with runtime flags, blast-radius hints) - `sbom_events` (outbox for event delivery + watermark/backfill tracking) @@ -84,7 +86,12 @@ Operational rules: - Logs: structured, include tenant + artifact digest + sbomVersion; classify ingest failures (schema, storage, orchestrator, validation). - Alerts: backlog thresholds for outbox/event delivery; high latency on path/timeline endpoints. -## 9) Open questions / dependencies +## 9) Configuration (Mongo-backed catalog & lookup) +- Enable Mongo storage for `/console/sboms` and `/components/lookup` by setting `SbomService:Mongo:ConnectionString` (env: `SBOM_SbomService__Mongo__ConnectionString`). +- Optional overrides: `SbomService:Mongo:Database`, `SbomService:Mongo:CatalogCollection`, `SbomService:Mongo:ComponentLookupCollection`; defaults are `sbom_service`, `sbom_catalog`, `sbom_component_neighbors`. +- When the connection string is absent the service falls back to fixture JSON or deterministic in-memory seeds to keep air-gapped workflows alive. + +## 10) Open questions / dependencies - Confirm orchestrator pause/backfill contract (shared with Runtime & Signals 140-series). - Finalise storage collection names and indexes (compound on tenant+artifactDigest+version, TTL for transient staging). - Publish canonical LNM v1 fixtures and JSON schemas for projections and asset metadata. diff --git a/scripts/vex/requirements.txt b/scripts/vex/requirements.txt new file mode 100644 index 000000000..b5d4deeb2 --- /dev/null +++ b/scripts/vex/requirements.txt @@ -0,0 +1,2 @@ +blake3==0.4.1 +jsonschema==4.22.0 diff --git a/scripts/vex/verify_proof_bundle.py b/scripts/vex/verify_proof_bundle.py new file mode 100644 index 000000000..dae47518e --- /dev/null +++ b/scripts/vex/verify_proof_bundle.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +""" +Offline verifier for StellaOps VEX proof bundles. + +- Validates the bundle against `docs/benchmarks/vex-evidence-playbook.schema.json`. +- Checks justification IDs against the signed catalog. +- Recomputes hashes for CAS artefacts, OpenVEX payload, and DSSE envelopes. +- Enforces coverage and negative-test requirements per task VEX-GAPS-401-062. +""" + +from __future__ import annotations + +import argparse +import base64 +import json +from pathlib import Path +import sys +from typing import Dict, Any + +import jsonschema +from blake3 import blake3 + + +def load_json(path: Path) -> Any: + return json.loads(path.read_text(encoding="utf-8")) + + +def digest_for(data: bytes, algo: str) -> str: + if algo == "sha256": + import hashlib + + return hashlib.sha256(data).hexdigest() + if algo == "blake3": + return blake3(data).hexdigest() + raise ValueError(f"Unsupported hash algorithm: {algo}") + + +def parse_digest(digest: str) -> tuple[str, str]: + if ":" not in digest: + raise ValueError(f"Digest missing prefix: {digest}") + algo, value = digest.split(":", 1) + return algo, value + + +def verify_digest(path: Path, expected: str) -> None: + algo, value = parse_digest(expected) + actual = digest_for(path.read_bytes(), algo) + if actual.lower() != value.lower(): + raise ValueError(f"Digest mismatch for {path}: expected {value}, got {actual}") + + +def resolve_cas_uri(cas_root: Path, cas_uri: str) -> Path: + if not cas_uri.startswith("cas://"): + raise ValueError(f"CAS URI must start with cas:// — got {cas_uri}") + relative = cas_uri[len("cas://") :] + return cas_root / relative + + +def verify_dsse(dsse_ref: Dict[str, Any]) -> None: + path = Path(dsse_ref["path"]) + verify_digest(path, dsse_ref["sha256"]) + if "payload_sha256" in dsse_ref: + envelope = load_json(path) + payload = base64.b64decode(envelope["payload"]) + verify_digest_from_bytes(payload, dsse_ref["payload_sha256"]) + + +def verify_digest_from_bytes(data: bytes, expected: str) -> None: + algo, value = parse_digest(expected) + actual = digest_for(data, algo) + if actual.lower() != value.lower(): + raise ValueError(f"Digest mismatch for payload: expected {value}, got {actual}") + + +def main() -> int: + parser = argparse.ArgumentParser(description="Verify a StellaOps VEX proof bundle.") + parser.add_argument("--bundle", required=True, type=Path) + parser.add_argument("--schema", required=True, type=Path) + parser.add_argument("--catalog", required=True, type=Path) + parser.add_argument("--cas-root", required=True, type=Path) + parser.add_argument("--min-coverage", type=float, default=95.0) + args = parser.parse_args() + + bundle = load_json(args.bundle) + schema = load_json(args.schema) + catalog = load_json(args.catalog) + + jsonschema.validate(instance=bundle, schema=schema) + + justification_ids = {entry["id"] for entry in catalog.get("entries", [])} + if bundle["justification"]["id"] not in justification_ids: + raise ValueError(f"Justification {bundle['justification']['id']} not found in catalog") + + # Justification DSSE integrity + if "dsse" in bundle["justification"]: + verify_dsse(bundle["justification"]["dsse"]) + + # OpenVEX canonical hashes + openvex_path = Path(bundle["openvex"]["path"]) + openvex_bytes = openvex_path.read_bytes() + verify_digest_from_bytes(openvex_bytes, bundle["openvex"]["canonical_sha256"]) + verify_digest_from_bytes(openvex_bytes, bundle["openvex"]["canonical_blake3"]) + + # CAS evidence + evidence_by_type: Dict[str, Dict[str, Any]] = {} + for ev in bundle["evidence"]: + ev_path = resolve_cas_uri(args.cas_root, ev["cas_uri"]) + verify_digest(ev_path, ev["hash"]) + if "dsse" in ev: + verify_dsse(ev["dsse"]) + evidence_by_type.setdefault(ev["type"], ev) + + # Graph hash alignment + graph = bundle["graph"] + graph_evidence = evidence_by_type.get("graph") + if not graph_evidence: + raise ValueError("Graph evidence missing from bundle") + if graph["hash"].lower() != graph_evidence["hash"].lower(): + raise ValueError("Graph hash does not match evidence hash") + if "dsse" in graph: + verify_dsse(graph["dsse"]) + + # Entrypoint coverage + negative tests + config/flags hashes + for ep in bundle["entrypoints"]: + if ep["coverage_percent"] < args.min_coverage: + raise ValueError( + f"Entrypoint {ep['id']} coverage {ep['coverage_percent']} below required {args.min_coverage}" + ) + if not ep["negative_tests"]: + raise ValueError(f"Entrypoint {ep['id']} missing negative test confirmation") + config_ev = evidence_by_type.get("config") + if not config_ev or config_ev["hash"].lower() != ep["config_hash"].lower(): + raise ValueError(f"Entrypoint {ep['id']} config_hash not backed by evidence") + flags_ev = evidence_by_type.get("flags") + if not flags_ev or flags_ev["hash"].lower() != ep["flags_hash"].lower(): + raise ValueError(f"Entrypoint {ep['id']} flags_hash not backed by evidence") + + # RBAC enforcement + rbac = bundle["rbac"] + if rbac["approvals_required"] < 1 or not rbac["roles_allowed"]: + raise ValueError("RBAC section is incomplete") + + # Reevaluation triggers: must all be true to satisfy VEX-GAPS-401-062 + reevaluation = bundle["reevaluation"] + if not all( + [ + reevaluation.get("on_sbom_change"), + reevaluation.get("on_graph_change"), + reevaluation.get("on_runtime_change"), + ] + ): + raise ValueError("Reevaluation triggers must all be true") + + # Uncertainty gating present + uncertainty = bundle["uncertainty"] + if uncertainty["state"] not in {"U0-none", "U1-low", "U2-medium", "U3-high"}: + raise ValueError("Invalid uncertainty state") + + # Signature envelope integrity (best-effort) + default_dsse_path = args.bundle.with_suffix(".dsse.json") + if default_dsse_path.exists(): + sig_envelope_digest = f"sha256:{digest_for(default_dsse_path.read_bytes(), 'sha256')}" + for sig in bundle["signatures"]: + if sig["envelope_digest"].lower() != sig_envelope_digest.lower(): + raise ValueError("Signature envelope digest mismatch") + + print("✔ VEX proof bundle verified") + return 0 + + +if __name__ == "__main__": + try: + sys.exit(main()) + except Exception as exc: # pragma: no cover - top-level guard + print(f"Verification failed: {exc}", file=sys.stderr) + sys.exit(1) diff --git a/src/ExportCenter/__fixtures/export-kit/README.md b/src/ExportCenter/__fixtures/export-kit/README.md new file mode 100644 index 000000000..0815b4945 --- /dev/null +++ b/src/ExportCenter/__fixtures/export-kit/README.md @@ -0,0 +1,10 @@ +# Export kit fixtures (EC10) + +Fixtures used by determinism/rerun-hash CI and the offline verify script. They are intentionally small, deterministic, and offline-friendly. + +- `manifest.json` — sample mirror:delta manifest with selector validation and integrity headers. +- `manifest.sha256` — hash for tamper detection. +- `manifest.dsse` — DSSE envelope (placeholder signature) carrying the manifest payload. +- `provenance.json` — SLSA v1-style provenance with hashedrekord log metadata. + +The verify script in `docs/modules/export-center/operations/verify-export-kit.sh` expects these files to be present when running in fixture mode (`VERIFY_FIXTURE=1`). diff --git a/src/ExportCenter/__fixtures/export-kit/manifest.dsse b/src/ExportCenter/__fixtures/export-kit/manifest.dsse new file mode 100644 index 000000000..cfafbcd74 --- /dev/null +++ b/src/ExportCenter/__fixtures/export-kit/manifest.dsse @@ -0,0 +1,10 @@ +{ + "payloadType": "application/vnd.stella.export+json", + "payload": "ewogICJzY2hlbWEiOiAiaHR0cHM6Ly9zdGVsbGFvcHMuaW8vZXhwb3J0LWNlbnRlci9tYW5pZmVzdC92MWFscGhhMiIsCiAgInZlcnNpb24iOiAiMS4xLjAiLAogICJleHBvcnRJZCI6ICJleHAtMjAyNTEyMDQtMDEiLAogICJydW5JZCI6ICJydW4tMjAyNTEyMDQtZWMxMCIsCiAgInByb2ZpbGUiOiB7CiAgICAia2luZCI6ICJtaXJyb3IiLAogICAgInZhcmlhbnQiOiAiZGVsdGEiLAogICAgIm5hbWUiOiAiZGVtby1taXJyb3ItZGVsdGEiLAogICAgInJldmlzaW9uIjogInIzIgogIH0sCiAgInRlbmFudCI6ICJ0ZW5hbnQtZGVtbyIsCiAgInNlbGVjdG9ycyI6IHsKICAgICJ0ZW5hbnRzIjogWwogICAgICAidGVuYW50LWRlbW8iCiAgICBdLAogICAgInByb2R1Y3RzIjogWwogICAgICAicmVnaXN0cnkuZXhhbXBsZS5jb20vYXBwOioiCiAgICBdLAogICAgInRpbWVXaW5kb3ciOiAiMjAyNS0xMS0wMS8yMDI1LTExLTMwIiwKICAgICJzZXZlcml0aWVzIjogWwogICAgICAiY3JpdGljYWwiLAogICAgICAiaGlnaCIKICAgIF0sCiAgICAiZWNvc3lzdGVtcyI6IFsKICAgICAgIm5wbSIsCiAgICAgICJtYXZlbiIKICAgIF0sCiAgICAic291cmNlcyI6IFsKICAgICAgImNvbmNlbGllciIsCiAgICAgICJleGNpdGl0b3IiCiAgICBdCiAgfSwKICAiZ2VuZXJhdGVkQXQiOiAiMjAyNS0xMi0wNFQwMDowMDowMFoiLAogICJyZXJ1bkhhc2giOiAic2hhMjU2OmJjMWI4ZTRhN2MwY2UzMTQ5ZmIxMjk4MDU0NGY1YmIyMTE4Njg1NjMyYjcxMzliYzk1ZWRiMjE4ZjA3MDRhNWUiLAogICJjb250ZW50cyI6IFsKICAgIHsKICAgICAgInBhdGgiOiAiZGF0YS9yYXcvYWR2aXNvcmllcy9hMC5qc29ubC56c3QiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExIiwKICAgICAgImJ5dGVzIjogMTIzNCwKICAgICAgInJlY29yZHMiOiAxMiwKICAgICAgImNvbnRlbnRUeXBlIjogImFwcGxpY2F0aW9uL3gtenN0ZCIKICAgIH0sCiAgICB7CiAgICAgICJwYXRoIjogImRhdGEvcG9saWN5L2ZpbmRpbmdzLmpzb25sLnpzdCIsCiAgICAgICJkaWdlc3QiOiAic2hhMjU2OjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIiLAogICAgICAiYnl0ZXMiOiAyMDQ4LAogICAgICAicmVjb3JkcyI6IDgsCiAgICAgICJjb250ZW50VHlwZSI6ICJhcHBsaWNhdGlvbi94LXpzdGQiCiAgICB9LAogICAgewogICAgICAicGF0aCI6ICJpbmRleGVzL2Fkdmlzb3JpZXMuaW5kZXguanNvbiIsCiAgICAgICJkaWdlc3QiOiAic2hhMjU2OjMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMiLAogICAgICAiYnl0ZXMiOiA1MTIsCiAgICAgICJyZWNvcmRzIjogMywKICAgICAgImNvbnRlbnRUeXBlIjogImFwcGxpY2F0aW9uL2pzb24iCiAgICB9CiAgXSwKICAiZGVsdGEiOiB7CiAgICAiYmFzZUV4cG9ydElkIjogImV4cC0yMDI1MTEyOS1mdWxsIiwKICAgICJiYXNlTWFuaWZlc3REaWdlc3QiOiAic2hhMjU2OmI0c2UwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLAogICAgInRvbWJzdG9uZXMiOiBbCiAgICAgICJ2ZXgvb2Jzb2xldGUuanNvbmwiLAogICAgICAiZGF0YS9yYXcvYWR2aXNvcmllcy9kZWxldGVkLmpzb25sLnpzdCIKICAgIF0sCiAgICAiYWRkZWQiOiBbCiAgICAgICJkYXRhL3Jhdy9hZHZpc29yaWVzL2EwLmpzb25sLnpzdCIsCiAgICAgICJpbmRleGVzL2Fkdmlzb3JpZXMuaW5kZXguanNvbiIKICAgIF0sCiAgICAicmVtb3ZlZCI6IFsKICAgICAgImRhdGEvcmF3L2Fkdmlzb3JpZXMvZGVsZXRlZC5qc29ubC56c3QiCiAgICBdCiAgfSwKICAiaW50ZWdyaXR5IjogewogICAgImh0dHBIZWFkZXJzIjogewogICAgICAiRGlnZXN0IjogInNoYS0yNTY9aDQ2RUFkWlF3TGZqaGMrWE1zdUtPRzJoZk02YXYyNkVqWHF1R3MzWmU2Yz0iLAogICAgICAiWC1TdGVsbGEtU2lnbmF0dXJlIjogImRzc2UtYjY0OlBMQUNFSE9MREVSIiwKICAgICAgIlgtU3RlbGxhLUltbXV0YWJpbGl0eSI6ICJ0cnVlIgogICAgfSwKICAgICJvY2kiOiB7CiAgICAgICJhbm5vdGF0aW9ucyI6IHsKICAgICAgICAiaW8uc3RlbGxhb3BzLmV4cG9ydC5wcm9maWxlIjogIm1pcnJvcjpkZWx0YSIsCiAgICAgICAgImlvLnN0ZWxsYW9wcy5leHBvcnQucnVuIjogInJ1bi0yMDI1MTIwNC1lYzEwIiwKICAgICAgICAiaW8uc3RlbGxhb3BzLmV4cG9ydC5tYW5pZmVzdC1kaWdlc3QiOiAic2hhMjU2Ojg3OGU4NDAxZDY1MGMwYjdlMzg1Y2Y5NzMyY2I4YTM4NmRhMTdjY2U5YWJmNmU4NDhkN2FhZTFhY2RkOTdiYTciLAogICAgICAgICJpby5zdGVsbGFvcHMuZXhwb3J0LnByb3ZlbmFuY2UtcmVmIjogIm1hbmlmZXN0cy9wcm92ZW5hbmNlLmpzb24iLAogICAgICAgICJvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UucmVmLm5hbWUiOiAicmVnaXN0cnkuZXhhbXBsZS5jb20vc3RlbGxhL2V4cG9ydC9kZW1vOjIwMjUxMjA0LWRlbHRhIgogICAgICB9CiAgICB9CiAgfSwKICAiYXR0ZXN0YXRpb25zIjogewogICAgInByb3ZlbmFuY2VSZWYiOiAibWFuaWZlc3RzL3Byb3ZlbmFuY2UuanNvbiIsCiAgICAiZHNzZUVudmVsb3BlIjogIm1hbmlmZXN0cy9tYW5pZmVzdC5kc3NlIiwKICAgICJzbHNhTGV2ZWwiOiAiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YxIiwKICAgICJsb2ciOiB7CiAgICAgICJraW5kIjogImhhc2hlZHJla29yZCIsCiAgICAgICJsb2dJZCI6ICJyZWtvci1wdWJsaWMiLAogICAgICAibG9nSW5kZXgiOiA0MiwKICAgICAgImVudHJ5RGlnZXN0IjogInNoYTI1Njpsb2dlbnRyeTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwKICAgICAgInRpbWVzdGFtcCI6ICIyMDI1LTEyLTA0VDAwOjAwOjAxWiIKICAgIH0KICB9LAogICJkaXN0cmlidXRpb24iOiB7CiAgICAiaHR0cCI6IHsKICAgICAgImVuYWJsZWQiOiB0cnVlLAogICAgICAicmV0ZW50aW9uRGF5cyI6IDMwLAogICAgICAiZXRhZyI6ICJXL1wiZXhwLTIwMjUxMjA0LTAxXCIiLAogICAgICAicmFuZ2VSZXF1ZXN0cyI6IHRydWUKICAgIH0sCiAgICAib2NpIjogewogICAgICAiZW5hYmxlZCI6IHRydWUsCiAgICAgICJyZWZlcmVuY2UiOiAicmVnaXN0cnkuZXhhbXBsZS5jb20vc3RlbGxhL2V4cG9ydC9kZW1vOjIwMjUxMjA0LWRlbHRhIgogICAgfSwKICAgICJvYmplY3QiOiB7CiAgICAgICJlbmFibGVkIjogdHJ1ZSwKICAgICAgImJ1Y2tldCI6ICJzdGVsbGEtZXhwb3J0cyIsCiAgICAgICJwcmVmaXgiOiAidGVuYW50LWRlbW8vZXhwLTIwMjUxMjA0LTAxIgogICAgfQogIH0sCiAgImVuY3J5cHRpb24iOiB7CiAgICAibW9kZSI6ICJhZ2UiLAogICAgInJlY2lwaWVudHMiOiBbCiAgICAgIHsKICAgICAgICAia2V5SWQiOiAidGVuYW50LWRlbW8vYWdlIiwKICAgICAgICAiZmluZ2VycHJpbnQiOiAiYWdlMWRlbW8wZGVtbzBkZW1vMGRlbW8wZGVtbzBkZW1vMGRlbW8wZGVtbzBkZW1vMCIsCiAgICAgICAgIndyYXBwZWRLZXkiOiAiWkdWdGIxOXJaWGs9IgogICAgICB9CiAgICBdLAogICAgInN0cmljdCI6IGZhbHNlCiAgfSwKICAiYXBwcm92YWwiOiB7CiAgICAicmVxdWlyZWQiOiB0cnVlLAogICAgInJlYXNvbiI6ICJjcm9zcy10ZW5hbnQgZXhwb3J0IHdpdGggbWlycm9yIHN1YnNjcmliZXJzIiwKICAgICJhcHByb3ZlZEJ5IjogImdyYy1yZXZpZXctMjAyNS0xMi0wMyIsCiAgICAidGlja2V0IjogIkdSQy0yMDQ0IgogIH0sCiAgInF1b3RhcyI6IHsKICAgICJtYXhBY3RpdmVSdW5zIjogNCwKICAgICJtYXhRdWV1ZWRSdW5zIjogNTAsCiAgICAiYmFja3ByZXNzdXJlTW9kZSI6ICJyZWplY3QiLAogICAgImNwdVRocm90dGxlUGVyY2VudCI6IDgwCiAgfQp9Cg==", + "signatures": [ + { + "keyid": "demo", + "sig": "PLACEHOLDER" + } + ] +} diff --git a/src/ExportCenter/__fixtures/export-kit/manifest.json b/src/ExportCenter/__fixtures/export-kit/manifest.json new file mode 100644 index 000000000..f936df811 --- /dev/null +++ b/src/ExportCenter/__fixtures/export-kit/manifest.json @@ -0,0 +1,142 @@ +{ + "schema": "https://stellaops.io/export-center/manifest/v1alpha2", + "version": "1.1.0", + "exportId": "exp-20251204-01", + "runId": "run-20251204-ec10", + "profile": { + "kind": "mirror", + "variant": "delta", + "name": "demo-mirror-delta", + "revision": "r3" + }, + "tenant": "tenant-demo", + "selectors": { + "tenants": [ + "tenant-demo" + ], + "products": [ + "registry.example.com/app:*" + ], + "timeWindow": "2025-11-01/2025-11-30", + "severities": [ + "critical", + "high" + ], + "ecosystems": [ + "npm", + "maven" + ], + "sources": [ + "concelier", + "excititor" + ] + }, + "generatedAt": "2025-12-04T00:00:00Z", + "rerunHash": "sha256:bc1b8e4a7c0ce3149fb12980544f5bb2118685632b7139bc95edb218f0704a5e", + "contents": [ + { + "path": "data/raw/advisories/a0.jsonl.zst", + "digest": "sha256:1111111111111111111111111111111111111111111111111111111111111111", + "bytes": 1234, + "records": 12, + "contentType": "application/x-zstd" + }, + { + "path": "data/policy/findings.jsonl.zst", + "digest": "sha256:2222222222222222222222222222222222222222222222222222222222222222", + "bytes": 2048, + "records": 8, + "contentType": "application/x-zstd" + }, + { + "path": "indexes/advisories.index.json", + "digest": "sha256:3333333333333333333333333333333333333333333333333333333333333333", + "bytes": 512, + "records": 3, + "contentType": "application/json" + } + ], + "delta": { + "baseExportId": "exp-20251129-full", + "baseManifestDigest": "sha256:b4se000000000000000000000000000000000000000000000000000000000000", + "tombstones": [ + "vex/obsolete.jsonl", + "data/raw/advisories/deleted.jsonl.zst" + ], + "added": [ + "data/raw/advisories/a0.jsonl.zst", + "indexes/advisories.index.json" + ], + "removed": [ + "data/raw/advisories/deleted.jsonl.zst" + ] + }, + "integrity": { + "httpHeaders": { + "Digest": "sha-256=h46EAdZQwLfjhc+XMsuKOG2hfM6av26EjXquGs3Ze6c=", + "X-Stella-Signature": "dsse-b64:PLACEHOLDER", + "X-Stella-Immutability": "true" + }, + "oci": { + "annotations": { + "io.stellaops.export.profile": "mirror:delta", + "io.stellaops.export.run": "run-20251204-ec10", + "io.stellaops.export.manifest-digest": "sha256:878e8401d650c0b7e385cf9732cb8a386da17cce9abf6e848d7aae1acdd97ba7", + "io.stellaops.export.provenance-ref": "manifests/provenance.json", + "org.opencontainers.image.ref.name": "registry.example.com/stella/export/demo:20251204-delta" + } + } + }, + "attestations": { + "provenanceRef": "manifests/provenance.json", + "dsseEnvelope": "manifests/manifest.dsse", + "slsaLevel": "https://slsa.dev/provenance/v1", + "log": { + "kind": "hashedrekord", + "logId": "rekor-public", + "logIndex": 42, + "entryDigest": "sha256:logentry00000000000000000000000000000000000000000000000000000000", + "timestamp": "2025-12-04T00:00:01Z" + } + }, + "distribution": { + "http": { + "enabled": true, + "retentionDays": 30, + "etag": "W/\"exp-20251204-01\"", + "rangeRequests": true + }, + "oci": { + "enabled": true, + "reference": "registry.example.com/stella/export/demo:20251204-delta" + }, + "object": { + "enabled": true, + "bucket": "stella-exports", + "prefix": "tenant-demo/exp-20251204-01" + } + }, + "encryption": { + "mode": "age", + "recipients": [ + { + "keyId": "tenant-demo/age", + "fingerprint": "age1demo0demo0demo0demo0demo0demo0demo0demo0demo0", + "wrappedKey": "ZGVtb19rZXk=" + } + ], + "strict": false + }, + "approval": { + "required": true, + "reason": "cross-tenant export with mirror subscribers", + "approvedBy": "grc-review-2025-12-03", + "ticket": "GRC-2044" + }, + "quotas": { + "maxActiveRuns": 4, + "maxQueuedRuns": 50, + "backpressureMode": "reject", + "cpuThrottlePercent": 80 + } +} diff --git a/src/ExportCenter/__fixtures/export-kit/manifest.sha256 b/src/ExportCenter/__fixtures/export-kit/manifest.sha256 new file mode 100644 index 000000000..97540232a --- /dev/null +++ b/src/ExportCenter/__fixtures/export-kit/manifest.sha256 @@ -0,0 +1 @@ +a38d2b73beb0a805c6e068cd03c96264bcc6fb423ba11dcdc9a9d8970598d8f2 manifest.json diff --git a/src/ExportCenter/__fixtures/export-kit/provenance.json b/src/ExportCenter/__fixtures/export-kit/provenance.json new file mode 100644 index 000000000..274e35c53 --- /dev/null +++ b/src/ExportCenter/__fixtures/export-kit/provenance.json @@ -0,0 +1,57 @@ +{ + "predicateType": "https://slsa.dev/provenance/v1", + "subject": [ + { + "name": "manifests/export.json", + "digest": { "sha256": "a38d2b73beb0a805c6e068cd03c96264bcc6fb423ba11dcdc9a9d8970598d8f2" } + }, + { + "name": "bundle.tar.zst", + "digest": { "sha256": "878e8401d650c0b7e385cf9732cb8a386da17cce9abf6e848d7aae1acdd97ba7" } + } + ], + "predicate": { + "buildType": "mirror:delta", + "builder": { "id": "stellaops/export-center@demo" }, + "invocation": { + "configSource": { "uri": "profile:demo-mirror-delta", "digest": { "sha256": "1111111111111111111111111111111111111111111111111111111111111111" } }, + "parameters": { + "tenant": "tenant-demo", + "baseExportId": "exp-20251129-full", + "selectors": { + "timeWindow": "2025-11-01/2025-11-30", + "severities": ["critical", "high"], + "ecosystems": ["npm", "maven"] + } + } + }, + "metadata": { + "buildStartedOn": "2025-12-04T00:00:00Z", + "buildFinishedOn": "2025-12-04T00:00:01Z", + "reproducible": true + }, + "materials": [ + { "uri": "ledger://tenant-demo/findings?cursor=rev-42", "digest": { "sha256": "2222222222222222222222222222222222222222222222222222222222222222" } }, + { "uri": "policy://tenant-demo/snapshots/rev-17", "digest": { "sha256": "3333333333333333333333333333333333333333333333333333333333333333" } } + ], + "environment": { + "logs": { + "kind": "hashedrekord", + "logId": "rekor-public", + "logIndex": 42, + "entryDigest": "sha256:logentry00000000000000000000000000000000000000000000000000000000", + "timestamp": "2025-12-04T00:00:01Z" + }, + "encryption": { + "mode": "age", + "recipients": [ + { + "recipient": "age1demo0demo0demo0demo0demo0demo0demo0demo0demo0", + "wrappedKey": "ZGVtb19rZXk=", + "keyId": "tenant-demo/age" + } + ] + } + } + } +} diff --git a/src/Notifier/StellaOps.Notifier/TASKS.md b/src/Notifier/StellaOps.Notifier/TASKS.md index cae524246..49575d1cd 100644 --- a/src/Notifier/StellaOps.Notifier/TASKS.md +++ b/src/Notifier/StellaOps.Notifier/TASKS.md @@ -13,3 +13,4 @@ | NOTIFY-RISK-66-001 | DONE (2025-11-24) | Notifications Service Guild · Risk Engine Guild | Added risk-events endpoint + templates/rules for severity change notifications. | | NOTIFY-RISK-67-001 | DONE (2025-11-24) | Notifications Service Guild · Policy Guild | Added routing/templates for risk profile publish/deprecate/threshold change. | | NOTIFY-RISK-68-001 | DONE (2025-11-24) | Notifications Service Guild | Default routing seeds with throttles/locales for risk alerts. | +| NOTIFY-GAPS-171-014 | BLOCKED (2025-12-04) | Notifications Service Guild | Waiting on NR1–NR10 specifics in `31-Nov-2025 FINDINGS.md` and schema/catalog refresh before remediation can start. | diff --git a/src/Policy/__Tests/StellaOps.Policy.Engine.Tests/Console/ConsoleSimulationDiffServiceTests.cs b/src/Policy/__Tests/StellaOps.Policy.Engine.Tests/Console/ConsoleSimulationDiffServiceTests.cs index e596e1f51..e73e4fb32 100644 --- a/src/Policy/__Tests/StellaOps.Policy.Engine.Tests/Console/ConsoleSimulationDiffServiceTests.cs +++ b/src/Policy/__Tests/StellaOps.Policy.Engine.Tests/Console/ConsoleSimulationDiffServiceTests.cs @@ -1,4 +1,5 @@ using System; +using System.Text.Json; using StellaOps.Policy.Engine.ConsoleSurface; using StellaOps.Policy.Engine.Simulation; using Xunit; @@ -28,7 +29,10 @@ public sealed class ConsoleSimulationDiffServiceTests var first = service.Compute(request); var second = service.Compute(request); - Assert.Equal(first, second); // deterministic + // Determinism: same serialized payload across repeated calls. + var serializedFirst = JsonSerializer.Serialize(first); + var serializedSecond = JsonSerializer.Serialize(second); + Assert.Equal(serializedFirst, serializedSecond); Assert.Equal("console-policy-23-001", first.SchemaVersion); Assert.True(first.Summary.After.Total > 0); Assert.True(first.Summary.Before.Total > 0); diff --git a/src/SbomService/StellaOps.SbomService.Tests/SbomMongoStorageTests.cs b/src/SbomService/StellaOps.SbomService.Tests/SbomMongoStorageTests.cs new file mode 100644 index 000000000..e8d42db83 --- /dev/null +++ b/src/SbomService/StellaOps.SbomService.Tests/SbomMongoStorageTests.cs @@ -0,0 +1,156 @@ +using System.Net.Http.Json; +using FluentAssertions; +using Microsoft.AspNetCore.Mvc.Testing; +using Mongo2Go; +using MongoDB.Bson; +using MongoDB.Driver; +using StellaOps.SbomService.Models; +using Xunit; + +namespace StellaOps.SbomService.Tests; + +public class SbomMongoStorageTests : IAsyncLifetime +{ + private readonly WebApplicationFactory _factory; + private MongoDbRunner? _runner; + + public SbomMongoStorageTests(WebApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task Console_catalog_reads_from_mongo_storage() + { + using var client = CreateClient(); + + var response = await client.GetAsync("/console/sboms?artifact=mongo-api&limit=1"); + response.EnsureSuccessStatusCode(); + + var payload = await response.Content.ReadFromJsonAsync(); + payload.Should().NotBeNull(); + payload!.Items.Should().ContainSingle(); + payload.Items[0].Artifact.Should().Be("ghcr.io/stellaops/mongo-api"); + payload.Items[0].ProjectionHash.Should().Be("sha256:proj-mongo-2"); + payload.NextCursor.Should().Be("1"); + } + + [Fact] + public async Task Component_lookup_returns_storage_results_and_cursor() + { + using var client = CreateClient(); + + var response = await client.GetAsync("/components/lookup?purl=pkg:npm/mongo-lib@1.0.0&limit=1"); + response.EnsureSuccessStatusCode(); + + var payload = await response.Content.ReadFromJsonAsync(); + payload.Should().NotBeNull(); + payload!.CacheHint.Should().Be("storage"); + payload.Neighbors.Should().ContainSingle(); + payload.Neighbors[0].Purl.Should().Be("pkg:npm/express@4.18.2"); + payload.NextCursor.Should().Be("1"); + } + + public Task InitializeAsync() + { + _runner = MongoDbRunner.Start(singleNodeReplSet: false, additionalMongodArguments: "--quiet"); + return SeedMongoAsync(); + } + + public Task DisposeAsync() + { + _runner?.Dispose(); + return Task.CompletedTask; + } + + private HttpClient CreateClient() + { + if (_runner is null) + { + throw new InvalidOperationException("Mongo runner not started"); + } + + var factory = _factory.WithWebHostBuilder(builder => + { + builder.ConfigureAppConfiguration((_, config) => + { + var settings = new Dictionary + { + ["SbomService:Mongo:ConnectionString"] = _runner.ConnectionString, + ["SbomService:Mongo:Database"] = "sbom_console_tests" + }; + + config.AddInMemoryCollection(settings); + }); + }); + + return factory.CreateClient(); + } + + private async Task SeedMongoAsync() + { + if (_runner is null) + { + return; + } + + var client = new MongoClient(_runner.ConnectionString); + var database = client.GetDatabase("sbom_console_tests"); + + var catalog = database.GetCollection("sbom_catalog"); + await catalog.DeleteManyAsync(FilterDefinition.Empty); + await catalog.InsertManyAsync(new[] + { + new BsonDocument + { + { "artifact", "ghcr.io/stellaops/mongo-api" }, + { "sbomVersion", "2025.12.04.2" }, + { "digest", "sha256:bbb" }, + { "license", "Apache-2.0" }, + { "scope", "runtime" }, + { "assetTags", new BsonDocument { { "owner", "storage" }, { "env", "prod" } } }, + { "createdAt", new BsonDateTime(DateTime.SpecifyKind(new DateTime(2025, 12, 4, 12, 0, 0), DateTimeKind.Utc)) }, + { "projectionHash", "sha256:proj-mongo-2" }, + { "evaluationMetadata", "eval:storage" } + }, + new BsonDocument + { + { "artifact", "ghcr.io/stellaops/mongo-api" }, + { "sbomVersion", "2025.12.04.1" }, + { "digest", "sha256:aaa" }, + { "license", "Apache-2.0" }, + { "scope", "runtime" }, + { "assetTags", new BsonDocument { { "owner", "storage" }, { "env", "prod" } } }, + { "createdAt", new BsonDateTime(DateTime.SpecifyKind(new DateTime(2025, 12, 4, 11, 0, 0), DateTimeKind.Utc)) }, + { "projectionHash", "sha256:proj-mongo-1" }, + { "evaluationMetadata", "eval:storage" } + } + }); + + var components = database.GetCollection("sbom_component_neighbors"); + await components.DeleteManyAsync(FilterDefinition.Empty); + await components.InsertManyAsync(new[] + { + new BsonDocument + { + { "artifact", "ghcr.io/stellaops/mongo-api" }, + { "purl", "pkg:npm/mongo-lib@1.0.0" }, + { "neighborPurl", "pkg:npm/express@4.18.2" }, + { "relationship", "DEPENDS_ON" }, + { "license", "MIT" }, + { "scope", "runtime" }, + { "runtimeFlag", true } + }, + new BsonDocument + { + { "artifact", "ghcr.io/stellaops/mongo-api" }, + { "purl", "pkg:npm/mongo-lib@1.0.0" }, + { "neighborPurl", "pkg:npm/body-parser@1.20.2" }, + { "relationship", "DEPENDS_ON" }, + { "license", "MIT" }, + { "scope", "runtime" }, + { "runtimeFlag", true } + } + }); + } +} diff --git a/src/SbomService/StellaOps.SbomService.Tests/StellaOps.SbomService.Tests.csproj b/src/SbomService/StellaOps.SbomService.Tests/StellaOps.SbomService.Tests.csproj index 8314d7899..f53c9f09e 100644 --- a/src/SbomService/StellaOps.SbomService.Tests/StellaOps.SbomService.Tests.csproj +++ b/src/SbomService/StellaOps.SbomService.Tests/StellaOps.SbomService.Tests.csproj @@ -3,6 +3,7 @@ net10.0 enable enable + false diff --git a/src/SbomService/StellaOps.SbomService/Models/CatalogRecord.cs b/src/SbomService/StellaOps.SbomService/Models/CatalogRecord.cs new file mode 100644 index 000000000..fb1cae245 --- /dev/null +++ b/src/SbomService/StellaOps.SbomService/Models/CatalogRecord.cs @@ -0,0 +1,15 @@ +namespace StellaOps.SbomService.Models; + +/// +/// Raw catalog row persisted for the console catalog endpoint. +/// +public sealed record CatalogRecord( + string Artifact, + string SbomVersion, + string Digest, + string? License, + string Scope, + IReadOnlyDictionary AssetTags, + DateTimeOffset CreatedAt, + string ProjectionHash, + string EvaluationMetadata); diff --git a/src/SbomService/StellaOps.SbomService/Options/SbomMongoOptions.cs b/src/SbomService/StellaOps.SbomService/Options/SbomMongoOptions.cs new file mode 100644 index 000000000..621f491eb --- /dev/null +++ b/src/SbomService/StellaOps.SbomService/Options/SbomMongoOptions.cs @@ -0,0 +1,15 @@ +namespace StellaOps.SbomService.Options; + +/// +/// MongoDB configuration for SBOM Service storage-backed endpoints. +/// +public sealed class SbomMongoOptions +{ + public string? ConnectionString { get; set; } + + public string Database { get; set; } = "sbom_service"; + + public string CatalogCollection { get; set; } = "sbom_catalog"; + + public string ComponentLookupCollection { get; set; } = "sbom_component_neighbors"; +} diff --git a/src/SbomService/StellaOps.SbomService/Program.cs b/src/SbomService/StellaOps.SbomService/Program.cs index 23b439005..ab8028882 100644 --- a/src/SbomService/StellaOps.SbomService/Program.cs +++ b/src/SbomService/StellaOps.SbomService/Program.cs @@ -1,7 +1,10 @@ using System.Globalization; using System.Diagnostics.Metrics; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using MongoDB.Driver; using StellaOps.SbomService.Models; +using StellaOps.SbomService.Options; using StellaOps.SbomService.Services; using StellaOps.SbomService.Observability; using StellaOps.SbomService.Repositories; @@ -17,38 +20,72 @@ builder.Configuration builder.Services.AddOptions(); builder.Services.AddLogging(); -// Register SBOM query services (file-backed fixtures when present; fallback to in-memory seeds). -builder.Services.AddSingleton(sp => +var mongoSection = builder.Configuration.GetSection("SbomService:Mongo"); +builder.Services.Configure(mongoSection); +var mongoConnectionString = mongoSection.GetValue("ConnectionString"); +var mongoConfigured = !string.IsNullOrWhiteSpace(mongoConnectionString); + +// Register SBOM query services (Mongo when configured; otherwise file-backed fixtures when present; fallback to in-memory seeds). +if (mongoConfigured) { - var config = sp.GetRequiredService(); - var env = sp.GetRequiredService(); - var configured = config.GetValue("SbomService:ComponentLookupPath"); - if (!string.IsNullOrWhiteSpace(configured) && File.Exists(configured)) + builder.Services.AddSingleton(sp => { - return new FileComponentLookupRepository(configured!); - } + var options = sp.GetRequiredService>().Value; + var url = new MongoUrl(options.ConnectionString!); + var settings = MongoClientSettings.FromUrl(url); + settings.ServerSelectionTimeout = TimeSpan.FromSeconds(5); + settings.RetryWrites = false; + return new MongoClient(settings); + }); - var candidate = FindFixture(env, "component_lookup.json"); - return candidate is not null - ? new FileComponentLookupRepository(candidate) - : new InMemoryComponentLookupRepository(); -}); + builder.Services.AddSingleton(sp => + { + var options = sp.GetRequiredService>().Value; + var client = sp.GetRequiredService(); + var url = new MongoUrl(options.ConnectionString!); + var databaseName = string.IsNullOrWhiteSpace(options.Database) + ? url.DatabaseName ?? "sbom_service" + : options.Database; + return client.GetDatabase(databaseName); + }); -builder.Services.AddSingleton(sp => + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); +} +else { - var config = sp.GetRequiredService(); - var env = sp.GetRequiredService(); - var configured = config.GetValue("SbomService:CatalogPath"); - if (!string.IsNullOrWhiteSpace(configured) && File.Exists(configured)) + builder.Services.AddSingleton(sp => { - return new FileCatalogRepository(configured!); - } + var config = sp.GetRequiredService(); + var env = sp.GetRequiredService(); + var configured = config.GetValue("SbomService:ComponentLookupPath"); + if (!string.IsNullOrWhiteSpace(configured) && File.Exists(configured)) + { + return new FileComponentLookupRepository(configured!); + } - var candidate = FindFixture(env, "catalog.json"); - return candidate is not null - ? new FileCatalogRepository(candidate) - : new InMemoryCatalogRepository(); -}); + var candidate = FindFixture(env, "component_lookup.json"); + return candidate is not null + ? new FileComponentLookupRepository(candidate) + : new InMemoryComponentLookupRepository(); + }); + + builder.Services.AddSingleton(sp => + { + var config = sp.GetRequiredService(); + var env = sp.GetRequiredService(); + var configured = config.GetValue("SbomService:CatalogPath"); + if (!string.IsNullOrWhiteSpace(configured) && File.Exists(configured)) + { + return new FileCatalogRepository(configured!); + } + + var candidate = FindFixture(env, "catalog.json"); + return candidate is not null + ? new FileCatalogRepository(candidate) + : new InMemoryCatalogRepository(); + }); +} builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(sp => sp.GetRequiredService()); diff --git a/src/SbomService/StellaOps.SbomService/Repositories/FileCatalogRepository.cs b/src/SbomService/StellaOps.SbomService/Repositories/FileCatalogRepository.cs index 3053a74fa..57d22aa4a 100644 --- a/src/SbomService/StellaOps.SbomService/Repositories/FileCatalogRepository.cs +++ b/src/SbomService/StellaOps.SbomService/Repositories/FileCatalogRepository.cs @@ -21,9 +21,28 @@ public sealed class FileCatalogRepository : ICatalogRepository PropertyNameCaseInsensitive = true }); - _items = items ?? Array.Empty(); + _items = items ?? new List(); } public Task> ListAsync(CancellationToken cancellationToken) => Task.FromResult(_items); + + public Task<(IReadOnlyList Items, int Total)> QueryAsync(SbomCatalogQuery query, CancellationToken cancellationToken) + { + var filtered = _items + .Where(c => query.Artifact is null || c.Artifact.Contains(query.Artifact, StringComparison.OrdinalIgnoreCase)) + .Where(c => query.License is null || string.Equals(c.License, query.License, StringComparison.OrdinalIgnoreCase)) + .Where(c => query.Scope is null || string.Equals(c.Scope, query.Scope, StringComparison.OrdinalIgnoreCase)) + .Where(c => query.AssetTag is null || c.AssetTags.ContainsKey(query.AssetTag)) + .OrderByDescending(c => c.CreatedAt) + .ThenBy(c => c.Artifact) + .ToList(); + + var page = filtered + .Skip(query.Offset) + .Take(query.Limit) + .ToList(); + + return Task.FromResult<(IReadOnlyList Items, int Total)>((page, filtered.Count)); + } } diff --git a/src/SbomService/StellaOps.SbomService/Repositories/FileComponentLookupRepository.cs b/src/SbomService/StellaOps.SbomService/Repositories/FileComponentLookupRepository.cs index eebc5bb18..d49f31169 100644 --- a/src/SbomService/StellaOps.SbomService/Repositories/FileComponentLookupRepository.cs +++ b/src/SbomService/StellaOps.SbomService/Repositories/FileComponentLookupRepository.cs @@ -21,7 +21,7 @@ public sealed class FileComponentLookupRepository : IComponentLookupRepository PropertyNameCaseInsensitive = true }); - _items = items ?? Array.Empty(); + _items = items ?? new List(); } public Task<(IReadOnlyList Items, int Total)> QueryAsync(ComponentLookupQuery query, CancellationToken cancellationToken) diff --git a/src/SbomService/StellaOps.SbomService/Repositories/ICatalogRepository.cs b/src/SbomService/StellaOps.SbomService/Repositories/ICatalogRepository.cs index c383c8eb8..ec895eee2 100644 --- a/src/SbomService/StellaOps.SbomService/Repositories/ICatalogRepository.cs +++ b/src/SbomService/StellaOps.SbomService/Repositories/ICatalogRepository.cs @@ -5,4 +5,9 @@ namespace StellaOps.SbomService.Repositories; public interface ICatalogRepository { Task> ListAsync(CancellationToken cancellationToken); + + /// + /// Returns a page of catalog records along with the total count for deterministic pagination. + /// + Task<(IReadOnlyList Items, int Total)> QueryAsync(SbomCatalogQuery query, CancellationToken cancellationToken); } diff --git a/src/SbomService/StellaOps.SbomService/Repositories/InMemoryCatalogRepository.cs b/src/SbomService/StellaOps.SbomService/Repositories/InMemoryCatalogRepository.cs index 5fa23919e..0f98c153b 100644 --- a/src/SbomService/StellaOps.SbomService/Repositories/InMemoryCatalogRepository.cs +++ b/src/SbomService/StellaOps.SbomService/Repositories/InMemoryCatalogRepository.cs @@ -55,4 +55,23 @@ public sealed class InMemoryCatalogRepository : ICatalogRepository public Task> ListAsync(CancellationToken cancellationToken) => Task.FromResult(Seed); + + public Task<(IReadOnlyList Items, int Total)> QueryAsync(SbomCatalogQuery query, CancellationToken cancellationToken) + { + var filtered = Seed + .Where(c => query.Artifact is null || c.Artifact.Contains(query.Artifact, StringComparison.OrdinalIgnoreCase)) + .Where(c => query.License is null || string.Equals(c.License, query.License, StringComparison.OrdinalIgnoreCase)) + .Where(c => query.Scope is null || string.Equals(c.Scope, query.Scope, StringComparison.OrdinalIgnoreCase)) + .Where(c => query.AssetTag is null || c.AssetTags.ContainsKey(query.AssetTag)) + .OrderByDescending(c => c.CreatedAt) + .ThenBy(c => c.Artifact) + .ToList(); + + var page = filtered + .Skip(query.Offset) + .Take(query.Limit) + .ToList(); + + return Task.FromResult<(IReadOnlyList Items, int Total)>((page, filtered.Count)); + } } diff --git a/src/SbomService/StellaOps.SbomService/Repositories/MongoCatalogRepository.cs b/src/SbomService/StellaOps.SbomService/Repositories/MongoCatalogRepository.cs new file mode 100644 index 000000000..ed884e4b0 --- /dev/null +++ b/src/SbomService/StellaOps.SbomService/Repositories/MongoCatalogRepository.cs @@ -0,0 +1,138 @@ +using System.Text.RegularExpressions; +using Microsoft.Extensions.Options; +using MongoDB.Bson; +using MongoDB.Driver; +using StellaOps.SbomService.Models; +using StellaOps.SbomService.Options; + +namespace StellaOps.SbomService.Repositories; + +public sealed class MongoCatalogRepository : ICatalogRepository +{ + private readonly IMongoCollection _collection; + private readonly Collation _caseInsensitive = new("en", strength: CollationStrength.Secondary); + + public MongoCatalogRepository(IMongoDatabase database, IOptions options) + { + ArgumentNullException.ThrowIfNull(database); + ArgumentNullException.ThrowIfNull(options); + + var opts = options.Value; + var collectionName = string.IsNullOrWhiteSpace(opts.CatalogCollection) + ? "sbom_catalog" + : opts.CatalogCollection; + + _collection = database.GetCollection(collectionName); + EnsureIndexes(); + } + + public async Task> ListAsync(CancellationToken cancellationToken) + { + var items = await _collection + .Find(FilterDefinition.Empty) + .SortByDescending(c => c.CreatedAt) + .ThenBy(c => c.Artifact) + .ToListAsync(cancellationToken); + + return items.Select(Map).ToList(); + } + + public async Task<(IReadOnlyList Items, int Total)> QueryAsync(SbomCatalogQuery query, CancellationToken cancellationToken) + { + var filter = BuildFilter(query); + + var total = (int)await _collection.CountDocumentsAsync(filter, cancellationToken: cancellationToken); + + var items = await _collection + .Find(filter, new FindOptions { Collation = _caseInsensitive }) + .SortByDescending(c => c.CreatedAt) + .ThenBy(c => c.Artifact) + .Skip(query.Offset) + .Limit(query.Limit) + .ToListAsync(cancellationToken); + + return (items.Select(Map).ToList(), total); + } + + private FilterDefinition BuildFilter(SbomCatalogQuery query) + { + var filter = Builders.Filter.Empty; + + if (!string.IsNullOrWhiteSpace(query.Artifact)) + { + filter &= Builders.Filter.Regex(c => c.Artifact, new BsonRegularExpression(query.Artifact, "i")); + } + + if (!string.IsNullOrWhiteSpace(query.License)) + { + var escaped = Regex.Escape(query.License); + filter &= Builders.Filter.Regex(c => c.License, new BsonRegularExpression($"^{escaped}$", "i")); + } + + if (!string.IsNullOrWhiteSpace(query.Scope)) + { + filter &= Builders.Filter.Eq(c => c.Scope, query.Scope); + } + + if (!string.IsNullOrWhiteSpace(query.AssetTag)) + { + filter &= Builders.Filter.Exists($"AssetTags.{query.AssetTag}"); + } + + return filter; + } + + private void EnsureIndexes() + { + var timestampIdx = Builders.IndexKeys + .Descending(c => c.CreatedAt) + .Ascending(c => c.Artifact); + + _collection.Indexes.CreateOne(new CreateIndexModel(timestampIdx, new CreateIndexOptions + { + Name = "catalog_created_artifact" + })); + + var filterIdx = Builders.IndexKeys + .Ascending(c => c.Artifact) + .Ascending(c => c.Scope) + .Ascending(c => c.License); + + _collection.Indexes.CreateOne(new CreateIndexModel(filterIdx, new CreateIndexOptions + { + Name = "catalog_filters" + })); + + var assetTagIdx = Builders.IndexKeys + .Ascending("AssetTags"); + + _collection.Indexes.CreateOne(new CreateIndexModel(assetTagIdx, new CreateIndexOptions + { + Name = "catalog_asset_tags" + })); + } + + private static CatalogRecord Map(CatalogDocument doc) => new( + doc.Artifact, + doc.SbomVersion, + doc.Digest, + doc.License, + doc.Scope, + doc.AssetTags ?? new Dictionary(StringComparer.Ordinal), + doc.CreatedAt, + doc.ProjectionHash, + doc.EvaluationMetadata ?? string.Empty); + + private sealed class CatalogDocument + { + public string Artifact { get; set; } = string.Empty; + public string SbomVersion { get; set; } = string.Empty; + public string Digest { get; set; } = string.Empty; + public string? License { get; set; } + public string Scope { get; set; } = string.Empty; + public Dictionary? AssetTags { get; set; } + public DateTimeOffset CreatedAt { get; set; } + public string ProjectionHash { get; set; } = string.Empty; + public string? EvaluationMetadata { get; set; } + } +} diff --git a/src/SbomService/StellaOps.SbomService/Repositories/MongoComponentLookupRepository.cs b/src/SbomService/StellaOps.SbomService/Repositories/MongoComponentLookupRepository.cs new file mode 100644 index 000000000..0934548b1 --- /dev/null +++ b/src/SbomService/StellaOps.SbomService/Repositories/MongoComponentLookupRepository.cs @@ -0,0 +1,80 @@ +using Microsoft.Extensions.Options; +using MongoDB.Driver; +using StellaOps.SbomService.Models; +using StellaOps.SbomService.Options; + +namespace StellaOps.SbomService.Repositories; + +public sealed class MongoComponentLookupRepository : IComponentLookupRepository +{ + private readonly IMongoCollection _collection; + private readonly Collation _caseInsensitive = new("en", strength: CollationStrength.Secondary); + + public MongoComponentLookupRepository(IMongoDatabase database, IOptions options) + { + ArgumentNullException.ThrowIfNull(database); + ArgumentNullException.ThrowIfNull(options); + + var opts = options.Value; + var collectionName = string.IsNullOrWhiteSpace(opts.ComponentLookupCollection) + ? "sbom_component_neighbors" + : opts.ComponentLookupCollection; + + _collection = database.GetCollection(collectionName); + EnsureIndexes(); + } + + public async Task<(IReadOnlyList Items, int Total)> QueryAsync(ComponentLookupQuery query, CancellationToken cancellationToken) + { + var filter = Builders.Filter.Eq(c => c.Purl, query.Purl); + + if (!string.IsNullOrWhiteSpace(query.Artifact)) + { + filter &= Builders.Filter.Eq(c => c.Artifact, query.Artifact); + } + + var total = (int)await _collection.CountDocumentsAsync(filter, cancellationToken: cancellationToken); + + var items = await _collection + .Find(filter, new FindOptions { Collation = _caseInsensitive }) + .SortBy(c => c.Artifact) + .ThenBy(c => c.NeighborPurl) + .Skip(query.Offset) + .Limit(query.Limit) + .ToListAsync(cancellationToken); + + return (items.Select(Map).ToList(), total); + } + + private void EnsureIndexes() + { + var purlArtifact = Builders.IndexKeys + .Ascending(c => c.Purl) + .Ascending(c => c.Artifact); + + _collection.Indexes.CreateOne(new CreateIndexModel(purlArtifact, new CreateIndexOptions + { + Name = "component_lookup_purl_artifact" + })); + } + + private static ComponentLookupRecord Map(ComponentDocument doc) => new( + doc.Artifact, + doc.Purl, + doc.NeighborPurl, + doc.Relationship, + doc.License, + doc.Scope, + doc.RuntimeFlag); + + private sealed class ComponentDocument + { + public string Artifact { get; set; } = string.Empty; + public string Purl { get; set; } = string.Empty; + public string NeighborPurl { get; set; } = string.Empty; + public string Relationship { get; set; } = string.Empty; + public string? License { get; set; } + public string Scope { get; set; } = string.Empty; + public bool RuntimeFlag { get; set; } + } +} diff --git a/src/SbomService/StellaOps.SbomService/Services/InMemorySbomQueryService.cs b/src/SbomService/StellaOps.SbomService/Services/InMemorySbomQueryService.cs index 951754562..d58e1daa5 100644 --- a/src/SbomService/StellaOps.SbomService/Services/InMemorySbomQueryService.cs +++ b/src/SbomService/StellaOps.SbomService/Services/InMemorySbomQueryService.cs @@ -1,7 +1,9 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Globalization; +using System.Diagnostics.Metrics; using System.Text.Json; using StellaOps.SbomService.Models; +using StellaOps.SbomService.Observability; using StellaOps.SbomService.Repositories; using StellaOps.SbomService.Services; @@ -11,7 +13,7 @@ internal sealed class InMemorySbomQueryService : ISbomQueryService { private readonly IReadOnlyList _paths; private readonly IReadOnlyList _timelines; - private readonly IReadOnlyList _catalog; + private readonly ICatalogRepository _catalogRepository; private readonly IComponentLookupRepository _componentLookupRepository; private readonly IProjectionRepository _projectionRepository; private readonly ISbomEventPublisher _eventPublisher; @@ -25,6 +27,7 @@ internal sealed class InMemorySbomQueryService : ISbomQueryService ISbomEventPublisher eventPublisher, IClock clock) { + _catalogRepository = catalogRepository; _componentLookupRepository = componentLookupRepository; _projectionRepository = projectionRepository; _eventPublisher = eventPublisher; @@ -32,11 +35,6 @@ internal sealed class InMemorySbomQueryService : ISbomQueryService // Deterministic seed data for early contract testing; replace with Mongo-backed implementation later. _paths = SeedPaths(); _timelines = SeedTimelines(); - _catalog = catalogRepository.ListAsync(CancellationToken.None).GetAwaiter().GetResult(); - if (_catalog.Count == 0) - { - _catalog = SeedCatalog(); - } } public Task> GetPathsAsync(SbomPathQuery query, CancellationToken cancellationToken) @@ -109,46 +107,37 @@ internal sealed class InMemorySbomQueryService : ISbomQueryService return Task.FromResult(new QueryResult(result, false)); } - public Task> GetConsoleCatalogAsync(SbomCatalogQuery query, CancellationToken cancellationToken) - { - var cacheKey = $"catalog|{query.Artifact}|{query.License}|{query.Scope}|{query.AssetTag}|{query.Offset}|{query.Limit}"; - if (_cache.TryGetValue(cacheKey, out var cached) && cached is SbomCatalogResult cachedCatalog) - { - return Task.FromResult(new QueryResult(cachedCatalog, true)); - } - - var filtered = _catalog - .Where(c => query.Artifact is null || c.Artifact.Contains(query.Artifact, StringComparison.OrdinalIgnoreCase)) - .Where(c => query.License is null || string.Equals(c.License, query.License, StringComparison.OrdinalIgnoreCase)) - .Where(c => query.Scope is null || string.Equals(c.Scope, query.Scope, StringComparison.OrdinalIgnoreCase)) - .Where(c => query.AssetTag is null || c.AssetTags.ContainsKey(query.AssetTag)) - .OrderByDescending(c => c.CreatedAt) - .ThenBy(c => c.Artifact) - .ToList(); - - var page = filtered - .Skip(query.Offset) - .Take(query.Limit) - .Select(c => new SbomCatalogItem( - c.Artifact, - c.SbomVersion, - c.Digest, - c.License, - c.Scope, - c.AssetTags, - c.CreatedAt, - c.ProjectionHash, - c.EvaluationMetadata)) - .ToList(); - - string? nextCursor = query.Offset + query.Limit < filtered.Count - ? (query.Offset + query.Limit).ToString(CultureInfo.InvariantCulture) - : null; - - var result = new SbomCatalogResult(page, nextCursor); - _cache[cacheKey] = result; - return Task.FromResult(new QueryResult(result, false)); - } + public async Task> GetConsoleCatalogAsync(SbomCatalogQuery query, CancellationToken cancellationToken) + { + var cacheKey = $"catalog|{query.Artifact}|{query.License}|{query.Scope}|{query.AssetTag}|{query.Offset}|{query.Limit}"; + if (_cache.TryGetValue(cacheKey, out var cached) && cached is SbomCatalogResult cachedCatalog) + { + return new QueryResult(cachedCatalog, true); + } + + var (items, total) = await _catalogRepository.QueryAsync(query, cancellationToken); + + var page = items + .Select(c => new SbomCatalogItem( + c.Artifact, + c.SbomVersion, + c.Digest, + c.License, + c.Scope, + c.AssetTags, + c.CreatedAt, + c.ProjectionHash, + c.EvaluationMetadata)) + .ToList(); + + string? nextCursor = query.Offset + query.Limit < total + ? (query.Offset + query.Limit).ToString(CultureInfo.InvariantCulture) + : null; + + var result = new SbomCatalogResult(page, nextCursor); + _cache[cacheKey] = result; + return new QueryResult(result, false); + } public async Task> GetComponentLookupAsync(ComponentLookupQuery query, CancellationToken cancellationToken) { @@ -168,7 +157,11 @@ internal sealed class InMemorySbomQueryService : ISbomQueryService .Select(c => new ComponentNeighbor(c.NeighborPurl, c.Relationship, c.License, c.Scope, c.RuntimeFlag)) .ToList(); - var result = new ComponentLookupResult(query.Purl, query.Artifact, neighbors, nextCursor, CacheHint: "seeded"); + var cacheHint = _componentLookupRepository.GetType().Name.Contains("Mongo", StringComparison.OrdinalIgnoreCase) + ? "storage" + : "seeded"; + + var result = new ComponentLookupResult(query.Purl, query.Artifact, neighbors, nextCursor, CacheHint: cacheHint); _cache[cacheKey] = result; return new QueryResult(result, false); } @@ -291,7 +284,7 @@ internal sealed class InMemorySbomQueryService : ISbomQueryService { foreach (var path in _paths) { - var pathNodes = path.Nodes.Select(n => $"{n.Name}:{n.Type}").ToList(); + var pathNodes = path.Nodes.Select(n => $"{n.Name}:{n.Kind}").ToList(); yield return new SbomInventoryEvidence( SnapshotId: snapshotId, TenantId: tenantId, @@ -381,89 +374,6 @@ internal sealed class InMemorySbomQueryService : ISbomQueryService }; } - private static IReadOnlyList SeedCatalog() - { - return new List - { - new( - Artifact: "ghcr.io/stellaops/sample-api", - SbomVersion: "2025.11.16.1", - Digest: "sha256:112", - License: "MIT", - Scope: "runtime", - AssetTags: new Dictionary - { - ["owner"] = "payments", - ["criticality"] = "high", - ["env"] = "prod" - }, - CreatedAt: new DateTimeOffset(2025, 11, 16, 12, 0, 0, TimeSpan.Zero), - ProjectionHash: "sha256:proj112", - EvaluationMetadata: "eval:passed:v1"), - new( - Artifact: "ghcr.io/stellaops/sample-api", - SbomVersion: "2025.11.15.1", - Digest: "sha256:111", - License: "MIT", - Scope: "runtime", - AssetTags: new Dictionary - { - ["owner"] = "payments", - ["criticality"] = "high", - ["env"] = "prod" - }, - CreatedAt: new DateTimeOffset(2025, 11, 15, 12, 0, 0, TimeSpan.Zero), - ProjectionHash: "sha256:proj111", - EvaluationMetadata: "eval:passed:v1"), - new( - Artifact: "ghcr.io/stellaops/sample-worker", - SbomVersion: "2025.11.12.0", - Digest: "sha256:222", - License: "Apache-2.0", - Scope: "runtime", - AssetTags: new Dictionary - { - ["owner"] = "platform", - ["criticality"] = "medium", - ["env"] = "staging" - }, - CreatedAt: new DateTimeOffset(2025, 11, 12, 8, 0, 0, TimeSpan.Zero), - ProjectionHash: "sha256:proj222", - EvaluationMetadata: "eval:pending:v1"), - }; - } - - private static IReadOnlyList SeedComponents() - { - return new List - { - new( - Artifact: "ghcr.io/stellaops/sample-api", - Purl: "pkg:npm/lodash@4.17.21", - NeighborPurl: "pkg:npm/express@4.18.2", - Relationship: "DEPENDS_ON", - License: "MIT", - Scope: "runtime", - RuntimeFlag: true), - new( - Artifact: "ghcr.io/stellaops/sample-api", - Purl: "pkg:npm/lodash@4.17.21", - NeighborPurl: "pkg:npm/rollup@3.0.0", - Relationship: "DEPENDS_ON", - License: "MIT", - Scope: "build", - RuntimeFlag: false), - new( - Artifact: "ghcr.io/stellaops/sample-worker", - Purl: "pkg:nuget/Newtonsoft.Json@13.0.2", - NeighborPurl: "pkg:nuget/StellaOps.Core@1.0.0", - Relationship: "DEPENDS_ON", - License: "Apache-2.0", - Scope: "runtime", - RuntimeFlag: true) - }; - } - private sealed record PathRecord( string Artifact, string Purl, @@ -482,23 +392,4 @@ internal sealed class InMemorySbomQueryService : ISbomQueryService DateTimeOffset CreatedAt, string? Provenance); - private sealed record CatalogRecord( - string Artifact, - string SbomVersion, - string Digest, - string? License, - string Scope, - IReadOnlyDictionary AssetTags, - DateTimeOffset CreatedAt, - string ProjectionHash, - string EvaluationMetadata); - - private sealed record ComponentLookupRecord( - string Artifact, - string Purl, - string NeighborPurl, - string Relationship, - string? License, - string Scope, - bool RuntimeFlag); -} +} diff --git a/src/SbomService/StellaOps.SbomService/Services/OrchestratorControlService.cs b/src/SbomService/StellaOps.SbomService/Services/OrchestratorControlService.cs index a30824766..681a125cd 100644 --- a/src/SbomService/StellaOps.SbomService/Services/OrchestratorControlService.cs +++ b/src/SbomService/StellaOps.SbomService/Services/OrchestratorControlService.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Diagnostics.Metrics; using StellaOps.SbomService.Repositories; diff --git a/src/SbomService/StellaOps.SbomService/Services/SbomEvents.cs b/src/SbomService/StellaOps.SbomService/Services/SbomEvents.cs index c9f868459..629e45439 100644 --- a/src/SbomService/StellaOps.SbomService/Services/SbomEvents.cs +++ b/src/SbomService/StellaOps.SbomService/Services/SbomEvents.cs @@ -19,6 +19,11 @@ public interface ISbomEventPublisher /// Publishes inventory evidence for resolver jobs (idempotent on snapshot+tenant+purl+scope+runtimeFlag). /// Task PublishInventoryAsync(SbomInventoryEvidence evt, CancellationToken cancellationToken); + + /// + /// Publishes a resolver candidate envelope for downstream graph/index consumers. + /// + Task PublishResolverAsync(ResolverCandidate candidate, CancellationToken cancellationToken); } public interface ISbomEventStore : ISbomEventPublisher @@ -29,7 +34,6 @@ public interface ISbomEventStore : ISbomEventPublisher Task ClearInventoryAsync(CancellationToken cancellationToken); Task> ListResolverAsync(CancellationToken cancellationToken); Task ClearResolverAsync(CancellationToken cancellationToken); - Task PublishResolverAsync(ResolverCandidate candidate, CancellationToken cancellationToken); } public sealed class InMemorySbomEventStore : ISbomEventStore diff --git a/src/SbomService/StellaOps.SbomService/StellaOps.SbomService.csproj b/src/SbomService/StellaOps.SbomService/StellaOps.SbomService.csproj index 317d0c560..e15a554b1 100644 --- a/src/SbomService/StellaOps.SbomService/StellaOps.SbomService.csproj +++ b/src/SbomService/StellaOps.SbomService/StellaOps.SbomService.csproj @@ -15,5 +15,6 @@ + diff --git a/src/UI/StellaOps.UI/TASKS.md b/src/UI/StellaOps.UI/TASKS.md index 32632beaa..cfd78e33e 100644 --- a/src/UI/StellaOps.UI/TASKS.md +++ b/src/UI/StellaOps.UI/TASKS.md @@ -6,3 +6,4 @@ | TS-10-001 | BLOCKED | TypeScript interface generation blocked: workspace missing and schemas not present locally. | 2025-11-30 | | TS-10-002 | BLOCKED | Same as TS-10-001; waiting on schemas + workspace. | 2025-11-30 | | TS-10-003 | BLOCKED | Same as TS-10-001; waiting on schemas + workspace. | 2025-11-30 | +| UI-MICRO-GAPS-0209-011 | BLOCKED | Canonical 30-Nov-2025 UI Micro-Interactions advisory missing and Angular workspace is empty; need advisory content, token catalog, and restored workspace before proceeding. | 2025-12-04 | diff --git a/tests/EvidenceLocker/Bundles/Golden/README.md b/tests/EvidenceLocker/Bundles/Golden/README.md new file mode 100644 index 000000000..4739ad963 --- /dev/null +++ b/tests/EvidenceLocker/Bundles/Golden/README.md @@ -0,0 +1,20 @@ +# Evidence Locker Golden Fixtures (EB10) + +Purpose: reference bundles and replay records used by CI to prove deterministic packaging, DSSE subject stability, and portable redaction behaviour. + +## Layout +- `sealed/` – sealed `bundle.tgz` artifacts with matching `manifest.json`, `checksums.txt`, and expected Merkle root in `expected.json`. +- `portable/` – redacted `portable-bundle-v1.tgz` paired with `expected.json` noting masked fields. +- `replay/` – `replay.ndjson` records aligned to the bundle fixtures; ordering is canonical (recordedAtUtc, scanId). + +## Expectations +- Gzip timestamp pinned to `2025-01-01T00:00:00Z`; tar entries use `0644` perms and fixed mtime. +- `checksums.txt` sorted lexicographically by `canonicalPath`; Merkle root equals `sha256sum checksums.txt`. +- DSSE subject ties to the Merkle root; manifest validates against `schemas/bundle.manifest.schema.json`. +- Portable bundles must exclude tenant identifiers and include redaction metadata in the manifest. + +## How to (re)generate +1. Set `TZ=UTC` and ensure deterministic tool versions. +2. Run EvidenceLocker pipeline to produce sealed bundle; copy outputs here with expected hash values. +3. Produce portable bundle and replay records using the same input set; write `expected.json` capturing root hashes and replay digests. +4. Update xUnit tests in `StellaOps.EvidenceLocker.Tests` to consume these fixtures without network calls. diff --git a/tests/Vex/ProofBundles/cas/config.lock b/tests/Vex/ProofBundles/cas/config.lock new file mode 100644 index 000000000..3d2545089 --- /dev/null +++ b/tests/Vex/ProofBundles/cas/config.lock @@ -0,0 +1,2 @@ +FEATURE_X=false +MODE=prod diff --git a/tests/Vex/ProofBundles/cas/coverage.json b/tests/Vex/ProofBundles/cas/coverage.json new file mode 100644 index 000000000..2e4fea9bb --- /dev/null +++ b/tests/Vex/ProofBundles/cas/coverage.json @@ -0,0 +1,16 @@ +{ + "tool": "reach-cov", + "entrypoints": [ + { + "id": "app://api/GET-/healthz", + "coverage_percent": 96.3, + "negative_tests": true + }, + { + "id": "app://worker/queue/default", + "coverage_percent": 95.1, + "negative_tests": true + } + ], + "timestamp": "2025-12-03T23:50:00Z" +} diff --git a/tests/Vex/ProofBundles/cas/coverage.json.dsse.json b/tests/Vex/ProofBundles/cas/coverage.json.dsse.json new file mode 100644 index 000000000..4d8027b0e --- /dev/null +++ b/tests/Vex/ProofBundles/cas/coverage.json.dsse.json @@ -0,0 +1,19 @@ +{ + "payloadType": "application/vnd.stellaops+json", + "payload": "ewogICJ0b29sIjogInJlYWNoLWNvdiIsCiAgImVudHJ5cG9pbnRzIjogWwogICAgewogICAgICAiaWQiOiAiYXBwOi8vYXBpL0dFVC0vaGVhbHRoeiIsCiAgICAgICJjb3ZlcmFnZV9wZXJjZW50IjogOTYuMywKICAgICAgIm5lZ2F0aXZlX3Rlc3RzIjogdHJ1ZQogICAgfSwKICAgIHsKICAgICAgImlkIjogImFwcDovL3dvcmtlci9xdWV1ZS9kZWZhdWx0IiwKICAgICAgImNvdmVyYWdlX3BlcmNlbnQiOiA5NS4xLAogICAgICAibmVnYXRpdmVfdGVzdHMiOiB0cnVlCiAgICB9CiAgXSwKICAidGltZXN0YW1wIjogIjIwMjUtMTItMDNUMjM6NTA6MDBaIgp9Cg==", + "signatures": [ + { + "keyid": "demo-root", + "sig": "9MRq4VDHrDJFAkshof/MS6XAPI2U/ivwmuHnQFuaDrM=" + } + ], + "subject": [ + { + "name": "coverage.json", + "hashes": { + "sha256": "422f9840d6facaae093d6496eeac472e10b19519854953454107c1b14945f510", + "blake3": "43bdea3c8b0bc1e0c52d317c5b03d08deb75c5017b6f52a9d703a60efbd87e29" + } + } + ] +} diff --git a/tests/Vex/ProofBundles/cas/flags.json b/tests/Vex/ProofBundles/cas/flags.json new file mode 100644 index 000000000..2bb382982 --- /dev/null +++ b/tests/Vex/ProofBundles/cas/flags.json @@ -0,0 +1 @@ +{"feature_gates":{"allow_unknown":false,"strict_vex":true},"release":"2025.12.0"} diff --git a/tests/Vex/ProofBundles/cas/graph.json b/tests/Vex/ProofBundles/cas/graph.json new file mode 100644 index 000000000..46d3b5505 --- /dev/null +++ b/tests/Vex/ProofBundles/cas/graph.json @@ -0,0 +1,18 @@ +{ + "graph_version": "richgraph-v1", + "root": "pkg:demo/app@1.0.0", + "nodes": 3, + "edges": 2, + "hashing": "blake3-256", + "generated_at": "2025-12-03T23:45:00Z", + "paths": [ + { + "from": "pkg:demo/app@1.0.0#main", + "to": "pkg:demo/lib@1.0.0#render" + }, + { + "from": "pkg:demo/lib@1.0.0#render", + "to": "pkg:demo/lib@1.0.0#helper" + } + ] +} diff --git a/tests/Vex/ProofBundles/cas/graph.json.dsse.json b/tests/Vex/ProofBundles/cas/graph.json.dsse.json new file mode 100644 index 000000000..8864e5825 --- /dev/null +++ b/tests/Vex/ProofBundles/cas/graph.json.dsse.json @@ -0,0 +1,19 @@ +{ + "payloadType": "application/vnd.stellaops+json", + "payload": "ewogICJncmFwaF92ZXJzaW9uIjogInJpY2hncmFwaC12MSIsCiAgInJvb3QiOiAicGtnOmRlbW8vYXBwQDEuMC4wIiwKICAibm9kZXMiOiAzLAogICJlZGdlcyI6IDIsCiAgImhhc2hpbmciOiAiYmxha2UzLTI1NiIsCiAgImdlbmVyYXRlZF9hdCI6ICIyMDI1LTEyLTAzVDIzOjQ1OjAwWiIsCiAgInBhdGhzIjogWwogICAgewogICAgICAiZnJvbSI6ICJwa2c6ZGVtby9hcHBAMS4wLjAjbWFpbiIsCiAgICAgICJ0byI6ICJwa2c6ZGVtby9saWJAMS4wLjAjcmVuZGVyIgogICAgfSwKICAgIHsKICAgICAgImZyb20iOiAicGtnOmRlbW8vbGliQDEuMC4wI3JlbmRlciIsCiAgICAgICJ0byI6ICJwa2c6ZGVtby9saWJAMS4wLjAjaGVscGVyIgogICAgfQogIF0KfQo=", + "signatures": [ + { + "keyid": "demo-root", + "sig": "USa7UXD1aQyBm6v4gGSBsbQAnMd7IXG1Kw+HwQBXpnU=" + } + ], + "subject": [ + { + "name": "graph.json", + "hashes": { + "sha256": "34d8051bb97bd3c034e6a2221474ce2faaaca59357721fa1b47df88a281d057b", + "blake3": "74640754695e6e5cda4156a0ef1fd3a557d802ef118fef8afaed67089cd39cb1" + } + } + ] +} diff --git a/tests/Vex/ProofBundles/cas/negative-tests.ndjson b/tests/Vex/ProofBundles/cas/negative-tests.ndjson new file mode 100644 index 000000000..9e4d27a3e --- /dev/null +++ b/tests/Vex/ProofBundles/cas/negative-tests.ndjson @@ -0,0 +1,2 @@ +{"name": "healthz-no-repro", "result": "pass", "seed": 42} +{"name": "worker-queue-no-exec", "result": "pass", "seed": 84} diff --git a/tests/Vex/ProofBundles/cas/runtime-trace.ndjson b/tests/Vex/ProofBundles/cas/runtime-trace.ndjson new file mode 100644 index 000000000..7dbac96f9 --- /dev/null +++ b/tests/Vex/ProofBundles/cas/runtime-trace.ndjson @@ -0,0 +1,2 @@ +{"function": "pkg:demo/app@1.0.0#main", "probe": "eventpipe", "ts": "2025-12-03T23:46:00Z"} +{"function": "pkg:demo/lib@1.0.0#render", "probe": "eventpipe", "ts": "2025-12-03T23:46:01Z"} diff --git a/tests/Vex/ProofBundles/openvex-config.json b/tests/Vex/ProofBundles/openvex-config.json new file mode 100644 index 000000000..1534665f2 --- /dev/null +++ b/tests/Vex/ProofBundles/openvex-config.json @@ -0,0 +1,28 @@ +{ + "context": "https://openvex.dev/ns/v0.2.0", + "metadata": { + "id": "urn:stellaops:vex:config-guard-1", + "author": "StellaOps Excititor", + "timestamp": "2025-12-04T00:00:00Z" + }, + "statements": [ + { + "vulnerability": "CVE-2024-7777", + "products": [ + "pkg:demo/app@1.0.1" + ], + "status": "not_affected", + "status_notes": "Feature flags disable vulnerable path; negative tests and runtime trace clean.", + "justification": "configuration_required", + "statementID": "urn:stellaops:vex:statement:config-guard-1", + "last_updated": "2025-12-04T00:00:00Z", + "known_exploited": false, + "references": [ + { + "summary": "Proof bundle", + "url": "cas://proofbundles/sample-proof-bundle-config.json" + } + ] + } + ] +} diff --git a/tests/Vex/ProofBundles/openvex-sample.json b/tests/Vex/ProofBundles/openvex-sample.json new file mode 100644 index 000000000..573d003ad --- /dev/null +++ b/tests/Vex/ProofBundles/openvex-sample.json @@ -0,0 +1,35 @@ +{ + "context": "https://openvex.dev/ns/v0.2.0", + "metadata": { + "id": "urn:stellaops:vex:sample-hello-1", + "author": "StellaOps Excititor", + "timestamp": "2025-12-04T00:00:00Z" + }, + "statements": [ + { + "vulnerability": "CVE-2024-9999", + "products": [ + "pkg:demo/app@1.0.0" + ], + "status": "not_affected", + "status_notes": "Entry-point coverage 96% with negative tests; runtime probes clean.", + "justification": "vulnerable_code_not_present", + "statementID": "urn:stellaops:vex:statement:sample-hello-1", + "last_updated": "2025-12-04T00:00:00Z", + "known_exploited": false, + "references": [ + { + "summary": "Proof bundle", + "url": "cas://proofbundles/sample-proof-bundle.json" + } + ], + "subcomponents": [ + { + "product": "pkg:demo/lib@1.0.0", + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path" + } + ] + } + ] +} diff --git a/tests/Vex/ProofBundles/sample-proof-bundle-config.dsse.json b/tests/Vex/ProofBundles/sample-proof-bundle-config.dsse.json new file mode 100644 index 000000000..52d32e897 --- /dev/null +++ b/tests/Vex/ProofBundles/sample-proof-bundle-config.dsse.json @@ -0,0 +1,19 @@ +{ + "payloadType": "application/vnd.stellaops.proofbundle+json", + "payload": "eyJjcmVhdGVkX2F0IjoiMjAyNS0xMi0wNFQwMDowMDowMFoiLCJjcmVhdGVkX2J5IjoiU3RlbGxhT3BzIFBvbGljeSBHdWlsZCIsImVudHJ5cG9pbnRzIjpbeyJjb25maWdfaGFzaCI6InNoYTI1NjpiYjQ5MGNlNGNkZTYwNzY4ZTJiNjE1NzFiYmU0NDgyOTBlNDI1NmQyZDkzMGFkZWEwZWUyNGMwN2U1YzYzZGJjIiwiY292ZXJhZ2VfcGVyY2VudCI6OTcuNCwiZmxhZ3NfaGFzaCI6InNoYTI1NjpkMDYwYWI4Y2RmNzVhZWRhNjM2M2JjYzZkZTQ5NWUyN2I1M2M5ZDU5MzhkOTdmNTQ5MmU4NjQ2ODFkOGNiZTUzIiwiaWQiOiJhcHA6Ly9hcGkvR0VULS9oZWFsdGh6IiwibmVnYXRpdmVfdGVzdHMiOnRydWV9LHsiY29uZmlnX2hhc2giOiJzaGEyNTY6YmI0OTBjZTRjZGU2MDc2OGUyYjYxNTcxYmJlNDQ4MjkwZTQyNTZkMmQ5MzBhZGVhMGVlMjRjMDdlNWM2M2RiYyIsImNvdmVyYWdlX3BlcmNlbnQiOjk3LjEsImZsYWdzX2hhc2giOiJzaGEyNTY6ZDA2MGFiOGNkZjc1YWVkYTYzNjNiY2M2ZGU0OTVlMjdiNTNjOWQ1OTM4ZDk3ZjU0OTJlODY0NjgxZDhjYmU1MyIsImlkIjoiYXBwOi8vd29ya2VyL3F1ZXVlL2RlZmF1bHQiLCJuZWdhdGl2ZV90ZXN0cyI6dHJ1ZX1dLCJldmlkZW5jZSI6W3siY2FzX3VyaSI6ImNhczovL2dyYXBoLmpzb24iLCJkc3NlIjp7InBhdGgiOiJ0ZXN0cy9WZXgvUHJvb2ZCdW5kbGVzL2Nhcy9ncmFwaC5qc29uLmRzc2UuanNvbiIsInNoYTI1NiI6InNoYTI1NjozYmIxZGM2YWY1Yzk3NDYzNWVkMzg3ZmRmOTM4ZjVhOTgzYzM3MGQ3N2QwMWEwMzJhYTYzZjU0MDdlZmNmYzdmIn0sImV4cGlyZXNfYXQiOiIyMDI2LTEyLTMxVDAwOjAwOjAwWiIsImhhc2giOiJibGFrZTM6NzQ2NDA3NTQ2OTVlNmU1Y2RhNDE1NmEwZWYxZmQzYTU1N2Q4MDJlZjExOGZlZjhhZmFlZDY3MDg5Y2QzOWNiMSIsInR5cGUiOiJncmFwaCJ9LHsiY2FzX3VyaSI6ImNhczovL2NvdmVyYWdlLmpzb24iLCJkc3NlIjp7InBhdGgiOiJ0ZXN0cy9WZXgvUHJvb2ZCdW5kbGVzL2Nhcy9jb3ZlcmFnZS5qc29uLmRzc2UuanNvbiIsInNoYTI1NiI6InNoYTI1Njo2MDY4NjRkMjE2NWI5ZGRmZWE2NjRkY2EzNjMxODYxNmU1ZWE1NzVlMmU5NmU3ZmEyYmMyMDRjYzNmNzlmZTJmIn0sImV4cGlyZXNfYXQiOiIyMDI2LTA2LTMwVDAwOjAwOjAwWiIsImhhc2giOiJzaGEyNTY6NDIyZjk4NDBkNmZhY2FhZTA5M2Q2NDk2ZWVhYzQ3MmUxMGIxOTUxOTg1NDk1MzQ1NDEwN2MxYjE0OTQ1ZjUxMCIsInR5cGUiOiJjb3ZlcmFnZSJ9LHsiY2FzX3VyaSI6ImNhczovL3J1bnRpbWUtdHJhY2UubmRqc29uIiwiZXhwaXJlc19hdCI6IjIwMjYtMDYtMzBUMDA6MDA6MDBaIiwiaGFzaCI6InNoYTI1NjpjMGE5MWY2NDViODk5ZTQ1NzJlYzI0NjAzOTE2Y2RmZTk4MjkzNGY0N2ViZGFlYzJlZjY3ZWU5MzAzNTY4YTc3IiwidHlwZSI6InJ1bnRpbWVfdHJhY2UifSx7ImNhc191cmkiOiJjYXM6Ly9uZWdhdGl2ZS10ZXN0cy5uZGpzb24iLCJleHBpcmVzX2F0IjoiMjAyNi0wNi0zMFQwMDowMDowMFoiLCJoYXNoIjoic2hhMjU2OjA5ZWZkYTA1Nzc5NmI4ZjBmMGZhMDAxNTA1ZDllNjg0Y2YwNGUwNWFjOGUzYzZmZTI0NDc2YTM2N2JiNzhhYWEiLCJ0eXBlIjoibmVnYXRpdmVfdGVzdCJ9LHsiY2FzX3VyaSI6ImNhczovL2NvbmZpZy5sb2NrIiwiZXhwaXJlc19hdCI6IjIwMjYtMDMtMzFUMDA6MDA6MDBaIiwiaGFzaCI6InNoYTI1NjpiYjQ5MGNlNGNkZTYwNzY4ZTJiNjE1NzFiYmU0NDgyOTBlNDI1NmQyZDkzMGFkZWEwZWUyNGMwN2U1YzYzZGJjIiwidHlwZSI6ImNvbmZpZyJ9LHsiY2FzX3VyaSI6ImNhczovL2ZsYWdzLmpzb24iLCJleHBpcmVzX2F0IjoiMjAyNi0wMy0zMVQwMDowMDowMFoiLCJoYXNoIjoic2hhMjU2OmQwNjBhYjhjZGY3NWFlZGE2MzYzYmNjNmRlNDk1ZTI3YjUzYzlkNTkzOGQ5N2Y1NDkyZTg2NDY4MWQ4Y2JlNTMiLCJ0eXBlIjoiZmxhZ3MifV0sImdyYXBoIjp7ImRzc2UiOnsicGF0aCI6InRlc3RzL1ZleC9Qcm9vZkJ1bmRsZXMvY2FzL2dyYXBoLmpzb24uZHNzZS5qc29uIiwicGF5bG9hZF9zaGEyNTYiOiJzaGEyNTY6MzRkODA1MWJiOTdiZDNjMDM0ZTZhMjIyMTQ3NGNlMmZhYWFjYTU5MzU3NzIxZmExYjQ3ZGY4OGEyODFkMDU3YiIsInNoYTI1NiI6InNoYTI1NjozYmIxZGM2YWY1Yzk3NDYzNWVkMzg3ZmRmOTM4ZjVhOTgzYzM3MGQ3N2QwMWEwMzJhYTYzZjU0MDdlZmNmYzdmIn0sImhhc2giOiJibGFrZTM6NzQ2NDA3NTQ2OTVlNmU1Y2RhNDE1NmEwZWYxZmQzYTU1N2Q4MDJlZjExOGZlZjhhZmFlZDY3MDg5Y2QzOWNiMSJ9LCJpZCI6InVybjpzdGVsbGFvcHM6cHJvb2ZidW5kbGU6Y29uZmlnLWd1YXJkLTEiLCJqdXN0aWZpY2F0aW9uIjp7ImRzc2UiOnsicGF0aCI6ImRvY3MvYmVuY2htYXJrcy92ZXgtanVzdGlmaWNhdGlvbnMuY2F0YWxvZy5kc3NlLmpzb24iLCJzaGEyNTYiOiJzaGEyNTY6N2RmM2NiZDk3MGJjODUxYjUxY2UzNWZmMWM2MWY5MjdiNjJmZTM1MTRlNWZmNjMxM2E1YmFkMjZkNjc1YjBjNyJ9LCJpZCI6IlZFWDMuY29uZmlnX25vdF92dWxuZXJhYmxlIn0sIm9wZW52ZXgiOnsiY2Fub25pY2FsX2JsYWtlMyI6ImJsYWtlMzo3MjA0OGU0ODk0Njg2NTYzMTJlY2FjNDk3ZGE4ZGFlYTczMTgwNGE1MzBmMDFkMTliYjM5M2ZlZjcyNzRjNzM2IiwiY2Fub25pY2FsX3NoYTI1NiI6InNoYTI1NjowYTNmYTY2ZmRkNTBlZjg4YTFiMzRhZTY3NzYwNDVhOGU5YTQzMTc3MjBkN2Q4NzU1MzVkOTE2ZmJiN2Y4MWI5IiwicGF0aCI6InRlc3RzL1ZleC9Qcm9vZkJ1bmRsZXMvb3BlbnZleC1jb25maWcuanNvbiIsInNlcmlhbGl6YXRpb24iOiJjYW5vbmljYWwtanNvbiIsInN0YXRlbWVudF9pZCI6InVybjpzdGVsbGFvcHM6dmV4OnN0YXRlbWVudDpjb25maWctZ3VhcmQtMSJ9LCJwb2xpY3kiOnsiY2Fub25pY2FsX2VuY29kaW5nIjoiSkNTIiwiZGVjaXNpb24iOiJub3RfYWZmZWN0ZWQiLCJkZWNpc2lvbl9yZWFzb24iOiJjb25maWdfbm90X3Z1bG5lcmFibGUiLCJvcGVudmV4X3NlcmlhbGl6YXRpb24iOiJjYW5vbmljYWwtanNvbiJ9LCJyYmFjIjp7ImFwcHJvdmFsc19yZXF1aXJlZCI6MiwiZW5mb3JjZW1lbnQiOiJwb2xpY3krc2lnbmVyIiwicm9sZXNfYWxsb3dlZCI6WyJ2ZXgtYXV0aG9yIiwicG9saWN5LWFkbWluIl19LCJyZWV2YWx1YXRpb24iOnsib25fZ3JhcGhfY2hhbmdlIjp0cnVlLCJvbl9ydW50aW1lX2NoYW5nZSI6dHJ1ZSwib25fc2JvbV9jaGFuZ2UiOnRydWUsInR0bF9kYXlzIjozMH0sInNpZ25hdHVyZXMiOlt7ImVudmVsb3BlX2RpZ2VzdCI6InNoYTI1Njo5YjFjYjUzNjYwMjhiZjY3OThjN2ZmY2QwNzUzOWM0ODM2NjM5YjEyMWY4Nzk4ZDI5NmEyNzNjMmI1ZTVjOTRiIiwia2V5X2lkIjoiZGVtby1yb290IiwicmVrb3JfZW50cnlfdXVpZCI6ImRlbW8tZW50cnktMDAwMiIsInJla29yX2xvZ19pZCI6ImRlbW8tbG9nIiwic2lnIjoiQzNtaUpGaERSZE5UeG5CSlNYU0tlaWlscVRhRjQ0cG9YVjNHSEFqZlZ4UT0iLCJ0cmFuc3BhcmVuY3lfY2hlY2twb2ludCI6ImNoZWNrcG9pbnQtY29uZmlnIiwidHlwZSI6ImRzc2UifV0sInVuY2VydGFpbnR5Ijp7ImVudHJvcHkiOjAuMTcsIm5vdGVzIjoiQ29uZmlnIGdhdGluZyArIG5lZ2F0aXZlIHRlc3RzOyBjb3ZlcmFnZSA+OTclLiIsInN0YXRlIjoiVTItbWVkaXVtIn0sInZlcnNpb24iOiIxLjAuMCJ9", + "signatures": [ + { + "keyid": "demo-root", + "sig": "ZSKYZw7QR6rUIBOvz2JNcA9Zp3VEnzlbBg6Th5tQsOg=" + } + ], + "subject": [ + { + "name": "sample-proof-bundle-config.json", + "hashes": { + "sha256": "b0830491a68c272b2bb105d665455a7e32e87f087e112edd7b1e657775c87ef5", + "blake3": "9f6356dcdea1a2bfc52c82812db922d23ec4d30845bee0b9a951a96153cf24eb" + } + } + ] +} diff --git a/tests/Vex/ProofBundles/sample-proof-bundle-config.json b/tests/Vex/ProofBundles/sample-proof-bundle-config.json new file mode 100644 index 000000000..1eae67c61 --- /dev/null +++ b/tests/Vex/ProofBundles/sample-proof-bundle-config.json @@ -0,0 +1,126 @@ +{ + "id": "urn:stellaops:proofbundle:config-guard-1", + "version": "1.0.0", + "created_at": "2025-12-04T00:00:00Z", + "created_by": "StellaOps Policy Guild", + "graph": { + "hash": "blake3:74640754695e6e5cda4156a0ef1fd3a557d802ef118fef8afaed67089cd39cb1", + "dsse": { + "path": "tests/Vex/ProofBundles/cas/graph.json.dsse.json", + "sha256": "sha256:3bb1dc6af5c974635ed387fdf938f5a983c370d77d01a032aa63f5407efcfc7f", + "payload_sha256": "sha256:34d8051bb97bd3c034e6a2221474ce2faaaca59357721fa1b47df88a281d057b" + } + }, + "openvex": { + "path": "tests/Vex/ProofBundles/openvex-config.json", + "statement_id": "urn:stellaops:vex:statement:config-guard-1", + "canonical_sha256": "sha256:0a3fa66fdd50ef88a1b34ae6776045a8e9a4317720d7d875535d916fbb7f81b9", + "canonical_blake3": "blake3:72048e489468656312ecac497da8daea731804a530f01d19bb393fef7274c736", + "serialization": "canonical-json" + }, + "justification": { + "id": "VEX3.config_not_vulnerable", + "dsse": { + "path": "docs/benchmarks/vex-justifications.catalog.dsse.json", + "sha256": "sha256:7df3cbd970bc851b51ce35ff1c61f927b62fe3514e5ff6313a5bad26d675b0c7" + } + }, + "entrypoints": [ + { + "id": "app://api/GET-/healthz", + "coverage_percent": 97.4, + "negative_tests": true, + "config_hash": "sha256:bb490ce4cde60768e2b61571bbe448290e4256d2d930adea0ee24c07e5c63dbc", + "flags_hash": "sha256:d060ab8cdf75aeda6363bcc6de495e27b53c9d5938d97f5492e864681d8cbe53" + }, + { + "id": "app://worker/queue/default", + "coverage_percent": 97.1, + "negative_tests": true, + "config_hash": "sha256:bb490ce4cde60768e2b61571bbe448290e4256d2d930adea0ee24c07e5c63dbc", + "flags_hash": "sha256:d060ab8cdf75aeda6363bcc6de495e27b53c9d5938d97f5492e864681d8cbe53" + } + ], + "evidence": [ + { + "type": "graph", + "cas_uri": "cas://graph.json", + "hash": "blake3:74640754695e6e5cda4156a0ef1fd3a557d802ef118fef8afaed67089cd39cb1", + "dsse": { + "path": "tests/Vex/ProofBundles/cas/graph.json.dsse.json", + "sha256": "sha256:3bb1dc6af5c974635ed387fdf938f5a983c370d77d01a032aa63f5407efcfc7f" + }, + "expires_at": "2026-12-31T00:00:00Z" + }, + { + "type": "coverage", + "cas_uri": "cas://coverage.json", + "hash": "sha256:422f9840d6facaae093d6496eeac472e10b19519854953454107c1b14945f510", + "dsse": { + "path": "tests/Vex/ProofBundles/cas/coverage.json.dsse.json", + "sha256": "sha256:606864d2165b9ddfea664dca36318616e5ea575e2e96e7fa2bc204cc3f79fe2f" + }, + "expires_at": "2026-06-30T00:00:00Z" + }, + { + "type": "runtime_trace", + "cas_uri": "cas://runtime-trace.ndjson", + "hash": "sha256:c0a91f645b899e4572ec24603916cdfe982934f47ebdaec2ef67ee9303568a77", + "expires_at": "2026-06-30T00:00:00Z" + }, + { + "type": "negative_test", + "cas_uri": "cas://negative-tests.ndjson", + "hash": "sha256:09efda057796b8f0f0fa001505d9e684cf04e05ac8e3c6fe24476a367bb78aaa", + "expires_at": "2026-06-30T00:00:00Z" + }, + { + "type": "config", + "cas_uri": "cas://config.lock", + "hash": "sha256:bb490ce4cde60768e2b61571bbe448290e4256d2d930adea0ee24c07e5c63dbc", + "expires_at": "2026-03-31T00:00:00Z" + }, + { + "type": "flags", + "cas_uri": "cas://flags.json", + "hash": "sha256:d060ab8cdf75aeda6363bcc6de495e27b53c9d5938d97f5492e864681d8cbe53", + "expires_at": "2026-03-31T00:00:00Z" + } + ], + "reevaluation": { + "on_sbom_change": true, + "on_graph_change": true, + "on_runtime_change": true, + "ttl_days": 30 + }, + "rbac": { + "roles_allowed": [ + "vex-author", + "policy-admin" + ], + "approvals_required": 2, + "enforcement": "policy+signer" + }, + "uncertainty": { + "state": "U2-medium", + "entropy": 0.17, + "notes": "Config gating + negative tests; coverage >97%." + }, + "policy": { + "decision": "not_affected", + "decision_reason": "config_not_vulnerable", + "openvex_serialization": "canonical-json", + "canonical_encoding": "JCS" + }, + "signatures": [ + { + "type": "dsse", + "key_id": "demo-root", + "sig": "C3miJFhDRdNTxnBJSXSKeiilqTaF44poXV3GHAjfVxQ=", + "envelope_digest": "sha256:ea551c28a3b463f6e510e19674da9051e2e02d5dfd1507697750cc3def649667", + "rekor_log_id": "demo-log", + "rekor_entry_uuid": "demo-entry-0002", + "transparency_checkpoint": "checkpoint-config" + } + ] +} diff --git a/tests/Vex/ProofBundles/sample-proof-bundle.dsse.json b/tests/Vex/ProofBundles/sample-proof-bundle.dsse.json new file mode 100644 index 000000000..26a91cc2e --- /dev/null +++ b/tests/Vex/ProofBundles/sample-proof-bundle.dsse.json @@ -0,0 +1,19 @@ +{ + "payloadType": "application/vnd.stellaops.proofbundle+json", + "payload": "eyJjcmVhdGVkX2F0IjoiMjAyNS0xMi0wNFQwMDowMDowMFoiLCJjcmVhdGVkX2J5IjoiU3RlbGxhT3BzIFFBIEd1aWxkIiwiZW50cnlwb2ludHMiOlt7ImNvbmZpZ19oYXNoIjoic2hhMjU2OmJiNDkwY2U0Y2RlNjA3NjhlMmI2MTU3MWJiZTQ0ODI5MGU0MjU2ZDJkOTMwYWRlYTBlZTI0YzA3ZTVjNjNkYmMiLCJjb3ZlcmFnZV9wZXJjZW50Ijo5Ni4zLCJmbGFnc19oYXNoIjoic2hhMjU2OmQwNjBhYjhjZGY3NWFlZGE2MzYzYmNjNmRlNDk1ZTI3YjUzYzlkNTkzOGQ5N2Y1NDkyZTg2NDY4MWQ4Y2JlNTMiLCJpZCI6ImFwcDovL2FwaS9HRVQtL2hlYWx0aHoiLCJuZWdhdGl2ZV90ZXN0cyI6dHJ1ZX0seyJjb25maWdfaGFzaCI6InNoYTI1NjpiYjQ5MGNlNGNkZTYwNzY4ZTJiNjE1NzFiYmU0NDgyOTBlNDI1NmQyZDkzMGFkZWEwZWUyNGMwN2U1YzYzZGJjIiwiY292ZXJhZ2VfcGVyY2VudCI6OTUuMSwiZmxhZ3NfaGFzaCI6InNoYTI1NjpkMDYwYWI4Y2RmNzVhZWRhNjM2M2JjYzZkZTQ5NWUyN2I1M2M5ZDU5MzhkOTdmNTQ5MmU4NjQ2ODFkOGNiZTUzIiwiaWQiOiJhcHA6Ly93b3JrZXIvcXVldWUvZGVmYXVsdCIsIm5lZ2F0aXZlX3Rlc3RzIjp0cnVlfV0sImV2aWRlbmNlIjpbeyJjYXNfdXJpIjoiY2FzOi8vZ3JhcGguanNvbiIsImRzc2UiOnsicGF0aCI6InRlc3RzL1ZleC9Qcm9vZkJ1bmRsZXMvY2FzL2dyYXBoLmpzb24uZHNzZS5qc29uIiwic2hhMjU2Ijoic2hhMjU2OjNiYjFkYzZhZjVjOTc0NjM1ZWQzODdmZGY5MzhmNWE5ODNjMzcwZDc3ZDAxYTAzMmFhNjNmNTQwN2VmY2ZjN2YifSwiZXhwaXJlc19hdCI6IjIwMjYtMTItMzFUMDA6MDA6MDBaIiwiaGFzaCI6ImJsYWtlMzo3NDY0MDc1NDY5NWU2ZTVjZGE0MTU2YTBlZjFmZDNhNTU3ZDgwMmVmMTE4ZmVmOGFmYWVkNjcwODljZDM5Y2IxIiwidHlwZSI6ImdyYXBoIn0seyJjYXNfdXJpIjoiY2FzOi8vY292ZXJhZ2UuanNvbiIsImRzc2UiOnsicGF0aCI6InRlc3RzL1ZleC9Qcm9vZkJ1bmRsZXMvY2FzL2NvdmVyYWdlLmpzb24uZHNzZS5qc29uIiwic2hhMjU2Ijoic2hhMjU2OjYwNjg2NGQyMTY1YjlkZGZlYTY2NGRjYTM2MzE4NjE2ZTVlYTU3NWUyZTk2ZTdmYTJiYzIwNGNjM2Y3OWZlMmYifSwiZXhwaXJlc19hdCI6IjIwMjYtMDYtMzBUMDA6MDA6MDBaIiwiaGFzaCI6InNoYTI1Njo0MjJmOTg0MGQ2ZmFjYWFlMDkzZDY0OTZlZWFjNDcyZTEwYjE5NTE5ODU0OTUzNDU0MTA3YzFiMTQ5NDVmNTEwIiwidHlwZSI6ImNvdmVyYWdlIn0seyJjYXNfdXJpIjoiY2FzOi8vcnVudGltZS10cmFjZS5uZGpzb24iLCJleHBpcmVzX2F0IjoiMjAyNi0wNi0zMFQwMDowMDowMFoiLCJoYXNoIjoic2hhMjU2OmMwYTkxZjY0NWI4OTllNDU3MmVjMjQ2MDM5MTZjZGZlOTgyOTM0ZjQ3ZWJkYWVjMmVmNjdlZTkzMDM1NjhhNzciLCJ0eXBlIjoicnVudGltZV90cmFjZSJ9LHsiY2FzX3VyaSI6ImNhczovL25lZ2F0aXZlLXRlc3RzLm5kanNvbiIsImV4cGlyZXNfYXQiOiIyMDI2LTA2LTMwVDAwOjAwOjAwWiIsImhhc2giOiJzaGEyNTY6MDllZmRhMDU3Nzk2YjhmMGYwZmEwMDE1MDVkOWU2ODRjZjA0ZTA1YWM4ZTNjNmZlMjQ0NzZhMzY3YmI3OGFhYSIsInR5cGUiOiJuZWdhdGl2ZV90ZXN0In0seyJjYXNfdXJpIjoiY2FzOi8vY29uZmlnLmxvY2siLCJleHBpcmVzX2F0IjoiMjAyNi0wMy0zMVQwMDowMDowMFoiLCJoYXNoIjoic2hhMjU2OmJiNDkwY2U0Y2RlNjA3NjhlMmI2MTU3MWJiZTQ0ODI5MGU0MjU2ZDJkOTMwYWRlYTBlZTI0YzA3ZTVjNjNkYmMiLCJ0eXBlIjoiY29uZmlnIn0seyJjYXNfdXJpIjoiY2FzOi8vZmxhZ3MuanNvbiIsImV4cGlyZXNfYXQiOiIyMDI2LTAzLTMxVDAwOjAwOjAwWiIsImhhc2giOiJzaGEyNTY6ZDA2MGFiOGNkZjc1YWVkYTYzNjNiY2M2ZGU0OTVlMjdiNTNjOWQ1OTM4ZDk3ZjU0OTJlODY0NjgxZDhjYmU1MyIsInR5cGUiOiJmbGFncyJ9XSwiZ3JhcGgiOnsiZHNzZSI6eyJwYXRoIjoidGVzdHMvVmV4L1Byb29mQnVuZGxlcy9jYXMvZ3JhcGguanNvbi5kc3NlLmpzb24iLCJwYXlsb2FkX3NoYTI1NiI6InNoYTI1NjozNGQ4MDUxYmI5N2JkM2MwMzRlNmEyMjIxNDc0Y2UyZmFhYWNhNTkzNTc3MjFmYTFiNDdkZjg4YTI4MWQwNTdiIiwic2hhMjU2Ijoic2hhMjU2OjNiYjFkYzZhZjVjOTc0NjM1ZWQzODdmZGY5MzhmNWE5ODNjMzcwZDc3ZDAxYTAzMmFhNjNmNTQwN2VmY2ZjN2YifSwiaGFzaCI6ImJsYWtlMzo3NDY0MDc1NDY5NWU2ZTVjZGE0MTU2YTBlZjFmZDNhNTU3ZDgwMmVmMTE4ZmVmOGFmYWVkNjcwODljZDM5Y2IxIn0sImlkIjoidXJuOnN0ZWxsYW9wczpwcm9vZmJ1bmRsZTpzYW1wbGUtaGVsbG8tMSIsImp1c3RpZmljYXRpb24iOnsiZHNzZSI6eyJwYXRoIjoiZG9jcy9iZW5jaG1hcmtzL3ZleC1qdXN0aWZpY2F0aW9ucy5jYXRhbG9nLmRzc2UuanNvbiIsInNoYTI1NiI6InNoYTI1Njo3ZGYzY2JkOTcwYmM4NTFiNTFjZTM1ZmYxYzYxZjkyN2I2MmZlMzUxNGU1ZmY2MzEzYTViYWQyNmQ2NzViMGM3In0sImlkIjoiVkVYMS52dWxuZXJhYmxlX2NvZGVfbm90X3ByZXNlbnQifSwib3BlbnZleCI6eyJjYW5vbmljYWxfYmxha2UzIjoiYmxha2UzOjAzNTA0ZjJiMWMzYjI5ODcwODUxYmFlYmM5ZTY2NThiNzZhZjJlOTI2MjA3NjcwODljZWNiNGMyMDA3MmQ4NGIiLCJjYW5vbmljYWxfc2hhMjU2Ijoic2hhMjU2Ojk0MDYzYTc4Y2MxYjBjZTM2Mzk0MTQ2N2M4ZTY3ZTM2OGMxMWRlNGQ4MjYyNWMyY2YwNWNlZGQ3NzMyNTdhM2UiLCJwYXRoIjoidGVzdHMvVmV4L1Byb29mQnVuZGxlcy9vcGVudmV4LXNhbXBsZS5qc29uIiwic2VyaWFsaXphdGlvbiI6ImNhbm9uaWNhbC1qc29uIiwic3RhdGVtZW50X2lkIjoidXJuOnN0ZWxsYW9wczp2ZXg6c3RhdGVtZW50OnNhbXBsZS1oZWxsby0xIn0sInBvbGljeSI6eyJjYW5vbmljYWxfZW5jb2RpbmciOiJKQ1MiLCJkZWNpc2lvbiI6Im5vdF9hZmZlY3RlZCIsImRlY2lzaW9uX3JlYXNvbiI6InZ1bG5lcmFibGVfY29kZV9ub3RfcHJlc2VudCIsIm9wZW52ZXhfc2VyaWFsaXphdGlvbiI6ImNhbm9uaWNhbC1qc29uIn0sInJiYWMiOnsiYXBwcm92YWxzX3JlcXVpcmVkIjoyLCJlbmZvcmNlbWVudCI6InBvbGljeStzaWduZXIiLCJyb2xlc19hbGxvd2VkIjpbInZleC1hdXRob3IiLCJwb2xpY3ktYWRtaW4iXX0sInJlZXZhbHVhdGlvbiI6eyJvbl9ncmFwaF9jaGFuZ2UiOnRydWUsIm9uX3J1bnRpbWVfY2hhbmdlIjp0cnVlLCJvbl9zYm9tX2NoYW5nZSI6dHJ1ZSwidHRsX2RheXMiOjMwfSwic2lnbmF0dXJlcyI6W3siZW52ZWxvcGVfZGlnZXN0Ijoic2hhMjU2OmI3YmVhNjY1Mjk3M2IzZjY0MWM4MzAyMTU3OTI3NzJlZWQzMzdjMDdkZjkzYzc1ZjJjYTM3MjU1MDhiY2MwZjUiLCJrZXlfaWQiOiJkZW1vLXJvb3QiLCJyZWtvcl9lbnRyeV91dWlkIjoiZGVtby1lbnRyeS0wMDAxIiwicmVrb3JfbG9nX2lkIjoiZGVtby1sb2ciLCJzaWciOiJDM21pSkZoRFJkTlR4bkJKU1hTS2VpaWxxVGFGNDRwb1hWM0dIQWpmVnhRPSIsInRyYW5zcGFyZW5jeV9jaGVja3BvaW50IjoiY2hlY2twb2ludC1kZW1vIiwidHlwZSI6ImRzc2UifV0sInVuY2VydGFpbnR5Ijp7ImVudHJvcHkiOjAuMDgsIm5vdGVzIjoiQ292ZXJhZ2UgPjk1JSBhbmQgbmVnYXRpdmUgdGVzdHMgY2xlYW47IHJ1bnRpbWUgcHJvYmVzIG1hdGNoIHJlYWNoYWJpbGl0eSBncmFwaC4iLCJzdGF0ZSI6IlUxLWxvdyJ9LCJ2ZXJzaW9uIjoiMS4wLjAifQ==", + "signatures": [ + { + "keyid": "demo-root", + "sig": "v9/Ny2xTMDg14BQjxtMinPM9ByL/9S5zH9JH8uRg6ww=" + } + ], + "subject": [ + { + "name": "sample-proof-bundle.json", + "hashes": { + "sha256": "a66c154c3452bf0445a3548ea96d2a60fa9454eb895283b322fbb29b3607bc51", + "blake3": "a578362df67b5d131e70ffa615553cf61cfbce736a60896c0c763d2e4964c1de" + } + } + ] +} diff --git a/tests/Vex/ProofBundles/sample-proof-bundle.json b/tests/Vex/ProofBundles/sample-proof-bundle.json new file mode 100644 index 000000000..39ccda7c5 --- /dev/null +++ b/tests/Vex/ProofBundles/sample-proof-bundle.json @@ -0,0 +1,126 @@ +{ + "id": "urn:stellaops:proofbundle:sample-hello-1", + "version": "1.0.0", + "created_at": "2025-12-04T00:00:00Z", + "created_by": "StellaOps QA Guild", + "graph": { + "hash": "blake3:74640754695e6e5cda4156a0ef1fd3a557d802ef118fef8afaed67089cd39cb1", + "dsse": { + "path": "tests/Vex/ProofBundles/cas/graph.json.dsse.json", + "sha256": "sha256:3bb1dc6af5c974635ed387fdf938f5a983c370d77d01a032aa63f5407efcfc7f", + "payload_sha256": "sha256:34d8051bb97bd3c034e6a2221474ce2faaaca59357721fa1b47df88a281d057b" + } + }, + "openvex": { + "path": "tests/Vex/ProofBundles/openvex-sample.json", + "statement_id": "urn:stellaops:vex:statement:sample-hello-1", + "canonical_sha256": "sha256:94063a78cc1b0ce363941467c8e67e368c11de4d82625c2cf05cedd773257a3e", + "canonical_blake3": "blake3:03504f2b1c3b29870851baebc9e6658b76af2e92620767089cecb4c20072d84b", + "serialization": "canonical-json" + }, + "justification": { + "id": "VEX1.vulnerable_code_not_present", + "dsse": { + "path": "docs/benchmarks/vex-justifications.catalog.dsse.json", + "sha256": "sha256:7df3cbd970bc851b51ce35ff1c61f927b62fe3514e5ff6313a5bad26d675b0c7" + } + }, + "entrypoints": [ + { + "id": "app://api/GET-/healthz", + "coverage_percent": 96.3, + "negative_tests": true, + "config_hash": "sha256:bb490ce4cde60768e2b61571bbe448290e4256d2d930adea0ee24c07e5c63dbc", + "flags_hash": "sha256:d060ab8cdf75aeda6363bcc6de495e27b53c9d5938d97f5492e864681d8cbe53" + }, + { + "id": "app://worker/queue/default", + "coverage_percent": 95.1, + "negative_tests": true, + "config_hash": "sha256:bb490ce4cde60768e2b61571bbe448290e4256d2d930adea0ee24c07e5c63dbc", + "flags_hash": "sha256:d060ab8cdf75aeda6363bcc6de495e27b53c9d5938d97f5492e864681d8cbe53" + } + ], + "evidence": [ + { + "type": "graph", + "cas_uri": "cas://graph.json", + "hash": "blake3:74640754695e6e5cda4156a0ef1fd3a557d802ef118fef8afaed67089cd39cb1", + "dsse": { + "path": "tests/Vex/ProofBundles/cas/graph.json.dsse.json", + "sha256": "sha256:3bb1dc6af5c974635ed387fdf938f5a983c370d77d01a032aa63f5407efcfc7f" + }, + "expires_at": "2026-12-31T00:00:00Z" + }, + { + "type": "coverage", + "cas_uri": "cas://coverage.json", + "hash": "sha256:422f9840d6facaae093d6496eeac472e10b19519854953454107c1b14945f510", + "dsse": { + "path": "tests/Vex/ProofBundles/cas/coverage.json.dsse.json", + "sha256": "sha256:606864d2165b9ddfea664dca36318616e5ea575e2e96e7fa2bc204cc3f79fe2f" + }, + "expires_at": "2026-06-30T00:00:00Z" + }, + { + "type": "runtime_trace", + "cas_uri": "cas://runtime-trace.ndjson", + "hash": "sha256:c0a91f645b899e4572ec24603916cdfe982934f47ebdaec2ef67ee9303568a77", + "expires_at": "2026-06-30T00:00:00Z" + }, + { + "type": "negative_test", + "cas_uri": "cas://negative-tests.ndjson", + "hash": "sha256:09efda057796b8f0f0fa001505d9e684cf04e05ac8e3c6fe24476a367bb78aaa", + "expires_at": "2026-06-30T00:00:00Z" + }, + { + "type": "config", + "cas_uri": "cas://config.lock", + "hash": "sha256:bb490ce4cde60768e2b61571bbe448290e4256d2d930adea0ee24c07e5c63dbc", + "expires_at": "2026-03-31T00:00:00Z" + }, + { + "type": "flags", + "cas_uri": "cas://flags.json", + "hash": "sha256:d060ab8cdf75aeda6363bcc6de495e27b53c9d5938d97f5492e864681d8cbe53", + "expires_at": "2026-03-31T00:00:00Z" + } + ], + "reevaluation": { + "on_sbom_change": true, + "on_graph_change": true, + "on_runtime_change": true, + "ttl_days": 30 + }, + "rbac": { + "roles_allowed": [ + "vex-author", + "policy-admin" + ], + "approvals_required": 2, + "enforcement": "policy+signer" + }, + "uncertainty": { + "state": "U1-low", + "entropy": 0.08, + "notes": "Coverage >95% and negative tests clean; runtime probes match reachability graph." + }, + "policy": { + "decision": "not_affected", + "decision_reason": "vulnerable_code_not_present", + "openvex_serialization": "canonical-json", + "canonical_encoding": "JCS" + }, + "signatures": [ + { + "type": "dsse", + "key_id": "demo-root", + "sig": "C3miJFhDRdNTxnBJSXSKeiilqTaF44poXV3GHAjfVxQ=", + "envelope_digest": "sha256:cacd00d318a3f0b3f579f57322619f99e772cced0c2a7bf14a684c6ce55da7b4", + "rekor_log_id": "demo-log", + "rekor_entry_uuid": "demo-entry-0001", + "transparency_checkpoint": "checkpoint-demo" + } + ] +} diff --git a/tests/Vex/ProofBundles/test_verify_sample.sh b/tests/Vex/ProofBundles/test_verify_sample.sh new file mode 100644 index 000000000..700b0f7c0 --- /dev/null +++ b/tests/Vex/ProofBundles/test_verify_sample.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" + +schema="$repo_root/docs/benchmarks/vex-evidence-playbook.schema.json" +catalog="$repo_root/docs/benchmarks/vex-justifications.catalog.json" +cas_root="$repo_root/tests/Vex/ProofBundles/cas" + +for bundle in "$repo_root"/tests/Vex/ProofBundles/*proof-bundle*.json; do + [[ "$bundle" == *.dsse.json ]] && continue + python "$repo_root/scripts/vex/verify_proof_bundle.py" \ + --bundle "$bundle" \ + --schema "$schema" \ + --catalog "$catalog" \ + --cas-root "$cas_root" +done