up
This commit is contained in:
@@ -0,0 +1 @@
|
||||
true
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"files": {
|
||||
"src/app.js": {
|
||||
"lines_covered": [
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
13,
|
||||
18,
|
||||
19
|
||||
],
|
||||
"lines_total": 40
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"entry": "POST /api/admin/exec",
|
||||
"path": [
|
||||
"app.js::createServer",
|
||||
"handler",
|
||||
"eval(code)"
|
||||
],
|
||||
"sink": "ExpressEval::exec",
|
||||
"notes": "Admin exec reached"
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"files": {
|
||||
"src/app.js": {
|
||||
"lines_covered": [
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15
|
||||
],
|
||||
"lines_total": 50
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"entry": "POST /api/admin/exec",
|
||||
"path": [
|
||||
"app.js::createServer",
|
||||
"guard: ALLOW_EXEC!=true"
|
||||
],
|
||||
"sink": "ExpressGuarded::exec",
|
||||
"notes": "Guard blocked sink"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
true
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"files": {
|
||||
"src/app.js": {
|
||||
"lines_covered": [
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
13,
|
||||
18,
|
||||
20
|
||||
],
|
||||
"lines_total": 45
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"entry": "POST /api/render",
|
||||
"path": [
|
||||
"app.js::createServer",
|
||||
"render template"
|
||||
],
|
||||
"sink": "FastifyTemplate::render",
|
||||
"notes": "Template rendered with user input"
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"files": {
|
||||
"src/app.js": {
|
||||
"lines_covered": [
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
9,
|
||||
10,
|
||||
11
|
||||
],
|
||||
"lines_total": 32
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"entry": "POST /api/exec",
|
||||
"path": [
|
||||
"app.js:handleRequest",
|
||||
"guard: FEATURE_ENABLE != 1"
|
||||
],
|
||||
"sink": "GuardedEval::handleRequest",
|
||||
"notes": "Guard prevented sink execution"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
true
|
||||
Binary file not shown.
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"files": {
|
||||
"src/app.js": {
|
||||
"lines_covered": [
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
12,
|
||||
15
|
||||
],
|
||||
"lines_total": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"entry": "POST /api/exec",
|
||||
"path": [
|
||||
"app.js:handleRequest",
|
||||
"eval(code)"
|
||||
],
|
||||
"sink": "UnsafeEval::handleRequest",
|
||||
"notes": "Test-driven dynamic trace"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
true
|
||||
Binary file not shown.
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"files": {
|
||||
"src/app.py": {
|
||||
"lines_covered": [
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10
|
||||
],
|
||||
"lines_total": 38
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"entry": "POST /render",
|
||||
"path": [
|
||||
"app.py::handle_request",
|
||||
"render"
|
||||
],
|
||||
"sink": "DjangoSSTI::render",
|
||||
"notes": "Template rendered (autoescape off)"
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"files": {
|
||||
"src/app.py": {
|
||||
"lines_covered": [
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
8,
|
||||
9,
|
||||
11
|
||||
],
|
||||
"lines_total": 40
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"entry": "POST /exec",
|
||||
"path": [
|
||||
"app.py::handle_request",
|
||||
"guard: ALLOW_EXEC!=true"
|
||||
],
|
||||
"sink": "FastApiGuarded::handle_request",
|
||||
"notes": "Guard blocked eval"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
true
|
||||
Binary file not shown.
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"files": {
|
||||
"src/app.py": {
|
||||
"lines_covered": [
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11
|
||||
],
|
||||
"lines_total": 40
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"entry": "POST /render",
|
||||
"path": [
|
||||
"app.py::handle_request",
|
||||
"render"
|
||||
],
|
||||
"sink": "FlaskTemplate::render",
|
||||
"notes": "Template rendered"
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"files": {
|
||||
"src/app.py": {
|
||||
"lines_covered": [
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
8,
|
||||
9,
|
||||
11
|
||||
],
|
||||
"lines_total": 34
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"entry": "POST /api/exec",
|
||||
"path": [
|
||||
"app.py::handle_request",
|
||||
"guard: FEATURE_ENABLE != 1"
|
||||
],
|
||||
"sink": "PyGuardedExec::handle_request",
|
||||
"notes": "Guard blocked eval"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
true
|
||||
Binary file not shown.
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"files": {
|
||||
"src/app.py": {
|
||||
"lines_covered": [
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
8,
|
||||
10
|
||||
],
|
||||
"lines_total": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"entry": "POST /api/exec",
|
||||
"path": [
|
||||
"app.py::handle_request",
|
||||
"eval(code)"
|
||||
],
|
||||
"sink": "PyUnsafeExec::handle_request",
|
||||
"notes": "Eval reached"
|
||||
}
|
||||
@@ -21,8 +21,9 @@
|
||||
|
||||
1. **Value in context** – [Overview](overview.md) compresses the “Why” + “What” stories and shows how Stella Ops stands apart.
|
||||
2. **Try it fast** – [Quickstart](quickstart.md) walks through fetching the signed bundles, configuring `.env`, and verifying the first scan.
|
||||
3. **Feature confidence** – [Key Features](key-features.md) gives five capability cards covering Delta SBOM, VEX‑first policy, Sovereign crypto, Deterministic replay, and Transparent quotas.
|
||||
4. **Up-next checkpoints** – [Evaluation checklist](evaluate/checklist.md) helps teams plan Day‑0 to Day‑30 adoption milestones.
|
||||
3. **Feature confidence** – [Key Features](key-features.md) gives five capability cards covering Delta SBOM, VEX-first policy, Sovereign crypto, Deterministic replay, and Transparent quotas.
|
||||
4. **Up-next checkpoints** – [Evaluation checklist](evaluate/checklist.md) helps teams plan Day-0 to Day-30 adoption milestones.
|
||||
5. **Be dev-ready** – [Developer Quickstart](onboarding/dev-quickstart.md) (29-Nov-2025 advisory) walks through the core repos, determinism tests, attestations, and starter issues for a mid-level .NET engineer.
|
||||
|
||||
## Key capabilities that define Stella Ops
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
| 1 | WEB-AIAI-31-001 | BLOCKED (2025-11-22) | Gateway policy/contract for `/advisory/ai/*` not present in Web workspace; need backend gateway service location + policy spec to proceed. | BE-Base Platform Guild | Route advisory AI endpoints through gateway with guardrails. |
|
||||
| 2 | WEB-AIAI-31-002 | BLOCKED (2025-11-22) | Blocked by WEB-AIAI-31-001; batching/streaming cannot start until gateway contract exists. | BE-Base Platform Guild | Streaming responses for CLI automation with job orchestration. |
|
||||
| 3 | WEB-AIAI-31-003 | BLOCKED (2025-11-22) | Blocked by WEB-AIAI-31-002; telemetry targets depend on routing/batching contract. | BE-Base Platform Guild; Observability Guild | Telemetry + audit for advisory AI, guardrail block visibility. |
|
||||
| 4 | WEB-AOC-19-002 | TODO | Depends on WEB-AOC-19-001; align DSSE/CMS helper APIs. | BE-Base Platform Guild | Ship `ProvenanceBuilder`, checksum utilities, signature verification helper with tests. |
|
||||
| 4 | WEB-AOC-19-002 | DONE (2025-11-30) | Depends on WEB-AOC-19-001; align DSSE/CMS helper APIs. | BE-Base Platform Guild | Ship `ProvenanceBuilder`, checksum utilities, signature verification helper with tests. |
|
||||
| 5 | WEB-AOC-19-003 | TODO | Depends on WEB-AOC-19-002; confirm Roslyn analyzer rules. | QA Guild; BE-Base Platform Guild | Analyzer to prevent forbidden key writes; shared guard-validation fixtures. |
|
||||
| 6 | WEB-CONSOLE-23-001 | DONE (2025-11-28) | `/console/dashboard` and `/console/filters` endpoints implemented with tenant-scoped aggregates. | BE-Base Platform Guild; Product Analytics Guild | Tenant-scoped aggregates for findings, VEX overrides, advisory deltas, run health, policy change log. |
|
||||
| 7 | CONSOLE-VULN-29-001 | BLOCKED (2025-11-19) | Blocked on WEB-CONSOLE-23-001 contract and Concelier graph schema freeze. | Console Guild; BE-Base Platform Guild | `/console/vuln/*` workspace endpoints with filters/reachability badges and DTOs once schemas stabilize. |
|
||||
@@ -80,3 +80,4 @@
|
||||
| 2025-11-22 | Added completion dates in `tasks-all` for WEB-CONTAINERS-44/45/46 and aligned BLOCKED dates for VULN-29-001/VEX-30-001. | Planning |
|
||||
| 2025-11-22 | Harmonized all `CONTAINERS-44/45/46` rows in `tasks-all` to DONE with dates to match sprint status. | Planning |
|
||||
| 2025-11-28 | Completed WEB-CONSOLE-23-001: Implemented `/console/dashboard` and `/console/filters` endpoints in Authority module. Dashboard returns tenant-scoped aggregates (findings summary, VEX overrides, advisory deltas, run health, policy change log) with 30-day trend data. Filters endpoint returns deterministic filter categories with counts and cache-validation hash. Added 8 unit tests for dashboard/filters endpoints. Implementation in `src/Authority/StellaOps.Authority/StellaOps.Authority/Console/`. | Policy Guild |
|
||||
| 2025-11-30 | Completed WEB-AOC-19-002: added deterministic provenance builder, checksum utilities, and DSSE/CMS signature verification helpers with unit tests under `src/Web/StellaOps.Web/src/app/core/aoc`. Added Web TASKS board and marked task DONE. | BE-Base Platform Guild |
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
| 4 | BENCH-CASES-PY-513-004 | DONE (2025-11-30) | Depends on 513-002. | Bench Guild · Python Track (`bench/reachability-benchmark/cases/py`) | Create 5-8 Python cases: Flask, Django, FastAPI. Include requirements.txt pinned, pytest oracles, coverage.py output. Delivered 5 cases: unsafe-exec (reachable), guarded-exec (unreachable), flask-template (reachable), fastapi-guarded (unreachable), django-ssti (reachable). |
|
||||
| 5 | BENCH-CASES-JAVA-513-005 | BLOCKED (2025-11-30) | Depends on 513-002. | Bench Guild · Java Track (`bench/reachability-benchmark/cases/java`) | Create 5-8 Java cases: Spring Boot, Micronaut. Include pom.xml locked, JUnit oracles, JaCoCo coverage. Progress: 2/5 seeded (`spring-deserialize` reachable, `spring-guarded` unreachable); build/test blocked by missing JDK (`javac` not available in runner). |
|
||||
| 6 | BENCH-CASES-C-513-006 | TODO | Depends on 513-002. | Bench Guild · Native Track (`bench/reachability-benchmark/cases/c`) | Create 3-5 C/ELF cases: small HTTP servers, crypto utilities. Include Makefile, gcov/llvm-cov coverage, deterministic builds (SOURCE_DATE_EPOCH). |
|
||||
| 7 | BENCH-BUILD-513-007 | DOING | Depends on 513-003 through 513-006. | Bench Guild · DevOps Guild | Implement `build_all.py` and `validate_builds.py`: deterministic Docker builds, hash verification, SBOM generation (syft), attestation stubs. Progress: added scripts (hash check, deterministic ordering); SBOM/attestation stubs pending. |
|
||||
| 7 | BENCH-BUILD-513-007 | DOING | Depends on 513-003 through 513-006. | Bench Guild · DevOps Guild | Implement `build_all.py` and `validate_builds.py`: deterministic Docker builds, hash verification, SBOM generation (syft), attestation stubs. Progress: added scripts (hash check, deterministic ordering) + README; SBOM/attestation stubs pending. |
|
||||
| 8 | BENCH-SCORER-513-008 | DONE (2025-11-30) | Depends on 513-002. | Bench Guild (`bench/reachability-benchmark/tools/scorer`) | Implement `rb-score` CLI: load cases/truth, validate submissions, compute precision/recall/F1, explainability score (0-3), runtime stats, determinism rate. |
|
||||
| 9 | BENCH-EXPLAIN-513-009 | DONE (2025-11-30) | Depends on 513-008. | Bench Guild | Implement explainability scoring rules: 0=no context, 1=path with ≥2 nodes, 2=entry+≥3 nodes, 3=guards/constraints included. Unit tests for each level. |
|
||||
| 10 | BENCH-BASELINE-SEMGREP-513-010 | TODO | Depends on 513-008 and cases. | Bench Guild | Semgrep baseline runner: `baselines/semgrep/run_case.sh`, rule config, output normalization to submission format. |
|
||||
@@ -98,3 +98,4 @@
|
||||
| 2025-11-30 | BLOCKED BENCH-CASES-JAVA-513-005: `javac`/JDK not available in runner; Java builds/tests cannot execute. Need JDK (>=17) in CI/runner before unblocking. | Implementer |
|
||||
| 2025-11-30 | BENCH-EXPLAIN-513-009 DONE: added explainability tier tests (0–3) to scorer; tiers already implemented (guards→3, entry+path>=3→2, path>=2→1, else 0). | Implementer |
|
||||
| 2025-11-30 | BENCH-BUILD-513-007 DOING: added `tools/build/build_all.py` and `tools/build/validate_builds.py` for deterministic builds and hash checks; SBOM/attestation stubs still pending. | Implementer |
|
||||
| 2025-11-30 | BENCH-BUILD-513-007: build_all/validate_builds run; all JS/PY cases deterministic, Java cases fail due to missing `javac` (same blocker as task 5). | Implementer |
|
||||
|
||||
@@ -18,12 +18,34 @@
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 200.A Docs Tasks.md ladder (Sprint 301 onwards) | BLOCKED (2025-11-19) | Docs Guild · Ops Guild | Attestor 100.A; Advisory AI 110.A; AirGap 120.A; Scanner 130.A; Graph 140.A; Orchestrator 150.A; EvidenceLocker 160.A; Notifier 170.A; CLI 180.A; Ops Deployment 190.A | Awaiting upstream artefacts (SBOM/CLI/Policy/AirGap determinism) before Md.I template rollout can continue. |
|
||||
| 200.B Module dossiers (Sprints 312–335) | TODO | Docs Guild · Module Guild owners | Docs Tasks Md ladder to at least Md.II; Ops deployment evidence | Stays queued until Docs Tasks Md ladder provides updated process + assets. |
|
||||
| Developer quickstart advisory sync | TODO | Docs Guild | 29-Nov-2025 advisory + onboarding doc draft | Publish the onboarding quickstart advisory + `docs/onboarding/dev-quickstart.md`, update `docs/README.md`, `modules/platform/architecture-overview.md`, and `ADVISORY_INDEX.md`, and confirm sprint/AGENTS references per the advisory workflow. |
|
||||
| Acceptance tests guardrails sync | TODO | Docs Guild | 29-Nov-2025 advisory + checklist draft | Publish the Acceptance Tests Pack advisory, cross-link to sprint/guardrail docs, and capture sprint board checklist for CI/DB/rew definitions. |
|
||||
| CVSS v4.0 momentum sync | TODO | Docs Guild | 29-Nov-2025 advisory + briefing draft | Publish the CVSS v4.0 momentum briefing, highlight adoption signals, and link to sprint decisions for SPRINT_0190.* and docs coverage. |
|
||||
| SBOM→VEX proof blueprint sync | TODO | Docs Guild | 29-Nov-2025 advisory + blueprint draft | Publish the SBOM→VEX blueprint, link to platform/blueprint docs, and capture diagram/stub updates for DSSE/Rekor/VEX. |
|
||||
| SCA failure catalogue sync | TODO | Docs Guild | 29-Nov-2025 advisory + catalogue draft | Publish the SCA failure catalogue, reference the concrete regressions, and tie the test-vector guidance back into sprint risk logs. |
|
||||
| Implementor guidelines sync | TODO | Docs Guild | 30-Nov-2025 advisory + checklist draft | Publish the Implementor Guidelines advisory, note the checklist extraction, and mention the doc in sprint/AGENTS references. |
|
||||
| Rekor receipt checklist sync | TODO | Docs Guild | 30-Nov-2025 advisory + checklist draft | Publish the Rekor Receipt Checklist, update module docs (Authority/Sbomer/Vexer) with ownership map, highlight offline metadata requirements. |
|
||||
| Unknowns decay/triage sync | TODO | Docs Guild | 30-Nov-2025 advisory + heuristic draft | Publish the Unknowns Decay & Triage brief, link to UnknownsRegistry docs, and capture UI artifacts for cards + queue exports. |
|
||||
| Ecosystem reality test cases sync | TODO | Docs Guild | 30-Nov-2025 advisory + test spec draft | Publish the Ecosystem Reality Test Cases advisory, link each incident to an acceptance test, and note exported artifacts/commands. |
|
||||
| Standup sprint kickstarters sync | TODO | Docs Guild | 30-Nov-2025 advisory + task plan draft | Publish the Standup Sprint Kickstarters advisory, surface ticket names, and tie the tasks into MSC sprint logs. |
|
||||
| Evidence + suppression pattern sync | TODO | Docs Guild | 30-Nov-2025 advisory + comparison draft | Publish the Comparative Evidence Patterns advisory, highlight the UX/data-model takeaways, and reference doc links per tool. |
|
||||
|
||||
## Execution Log
|
||||
| Date (UTC) | Update | Owner |
|
||||
| --- | --- | --- |
|
||||
| 2025-11-13 | Sprint 300 switched to topic-oriented template; Docs Tasks Md ladder marked DOING to reflect ongoing restructuring work. | Docs Guild |
|
||||
| 2025-11-19 | Marked Docs Tasks Md ladder BLOCKED pending upstream artefacts for Md.I dossier rollouts. | Implementer |
|
||||
| 2025-11-30 | Added the 29-Nov-2025 Developer Quickstart advisory, `docs/onboarding/dev-quickstart.md`, and cross-links (README/platform/ADVISORY_INDEX); created this advisory sync task row. | Docs Guild |
|
||||
| 2025-11-30 | Added the 29-Nov-2025 Acceptance Tests Pack advisory and checklist; noted new task row for guardrail sprint artifacts. | Docs Guild |
|
||||
| 2025-11-30 | Added the 29-Nov-2025 CVSS v4.0 Momentum advisory and indexed the adoption briefing; noted sprint sync row for CVSS momentum context. | Docs Guild |
|
||||
| 2025-11-30 | Added the 29-Nov-2025 SCA Failure Catalogue advisory and indexed the concrete test vectors; noted sprint sync row for failure catalog references. | Docs Guild |
|
||||
| 2025-11-30 | Added the 29-Nov-2025 SBOM→VEX Proof Blueprint advisory and outlined diagram/stub follow-up; logged sprint sync row for the blueprint. | Docs Guild |
|
||||
| 2025-11-30 | Added the 30-Nov-2025 Rekor Receipt Checklist advisory and noted the ownership/action map for Authority/Sbomer/Vexer. | Docs Guild |
|
||||
| 2025-11-30 | Added the 30-Nov-2025 Ecosystem Reality Test Cases advisory (credential leak, Trivy offline DB, SBOM parity, Grype divergence) and logged the acceptance test intent. | Docs Guild |
|
||||
| 2025-11-30 | Added the 30-Nov-2025 Unknowns Decay & Triage advisory and noted UI + export artifacts for UnknownsRegistry + queues. | Docs Guild |
|
||||
| 2025-11-30 | Added the 30-Nov-2025 Standup Sprint Kickstarters advisory, highlighting the three unblocker tasks/tickets and the proposed owners. | Docs Guild |
|
||||
| 2025-11-30 | Added the 30-Nov-2025 Comparative Evidence Patterns advisory and recorded cross-tool evidence/suppression nuggets for UX designers. | Docs Guild |
|
||||
| 2025-11-30 | Added the 30-Nov-2025 Implementor Guidelines advisory and checked the docs + sprint sync references; the row stays TODO until docs link updates finish. | Docs Guild |
|
||||
|
||||
## Decisions & Risks
|
||||
| Item | Type | Owner(s) | Due | Notes |
|
||||
|
||||
@@ -30,7 +30,7 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A
|
||||
| DEVOPS-AIRGAP-56-003 | DONE (2025-11-30) | Build Bootstrap Pack pipeline bundling images/charts, generating checksums, and publishing manifest for offline transfer. Dependencies: DEVOPS-AIRGAP-56-002. | DevOps Guild, Container Distribution Guild (ops/devops) |
|
||||
| DEVOPS-AIRGAP-57-001 | DONE (2025-11-30) | Automate Mirror Bundle creation jobs with dual-control approvals, artifact signing, and checksum publication. Dependencies: DEVOPS-AIRGAP-56-003. | DevOps Guild, Mirror Creator Guild (ops/devops) |
|
||||
| DEVOPS-AIRGAP-57-002 | BLOCKED (2025-11-18) | Waiting on upstream DEVOPS-AIRGAP-57-001 (mirror bundle automation) to provide artifacts/endpoints for sealed-mode CI; no sealed fixtures available to exercise tests. | DevOps Guild, Authority Guild (ops/devops) |
|
||||
| DEVOPS-AIRGAP-58-001 | TODO | Provide local SMTP/syslog container templates and health checks for sealed environments; integrate into Bootstrap Pack. Dependencies: DEVOPS-AIRGAP-57-002. | DevOps Guild, Notifications Guild (ops/devops) |
|
||||
| DEVOPS-AIRGAP-58-001 | DONE (2025-11-30) | Provide local SMTP/syslog container templates and health checks for sealed environments; integrate into Bootstrap Pack. Dependencies: DEVOPS-AIRGAP-57-002. | DevOps Guild, Notifications Guild (ops/devops) |
|
||||
| DEVOPS-AIRGAP-58-002 | TODO | Ship sealed-mode observability stack (Prometheus/Grafana/Tempo/Loki) pre-configured with offline dashboards and no remote exporters. Dependencies: DEVOPS-AIRGAP-58-001. | DevOps Guild, Observability Guild (ops/devops) |
|
||||
| DEVOPS-AOC-19-001 | BLOCKED (2025-10-26) | Integrate the AOC Roslyn analyzer and guard tests into CI, failing builds when ingestion projects attempt banned writes. | DevOps Guild, Platform Guild (ops/devops) |
|
||||
| DEVOPS-AOC-19-002 | BLOCKED (2025-10-26) | Add pipeline stage executing `stella aoc verify --since` against seeded Mongo snapshots for Concelier + Excititor, publishing violation report artefacts. Dependencies: DEVOPS-AOC-19-001. | DevOps Guild (ops/devops) |
|
||||
@@ -47,13 +47,14 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A
|
||||
| DEVOPS-CONCELIER-CI-24-101 | DONE (2025-11-25) | Provide clean CI runner + warmed NuGet cache + vstest harness for Concelier WebService & Storage; deliver TRX/binlogs and unblock CONCELIER-GRAPH-24-101/28-102 and LNM-21-004..203. | DevOps Guild, Concelier Core Guild (ops/devops) |
|
||||
| DEVOPS-SCANNER-CI-11-001 | DONE (2025-11-30) | Supply warmed cache/diag runner for Scanner analyzers (LANG-11-001, JAVA 21-005/008) with binlogs + TRX; unblock restore/test hangs. | DevOps Guild, Scanner EPDR Guild (ops/devops) |
|
||||
| DEVOPS-SCANNER-JAVA-21-011-REL | TODO | Package/sign Java analyzer plug-in once dev task 21-011 delivers; publish to Offline Kit/CLI release pipelines with provenance. | DevOps Guild, Scanner Release Guild (ops/devops) |
|
||||
| DEVOPS-SBOM-23-001 | TODO | Publish vetted offline NuGet feed + CI recipe for SbomService; prove with `dotnet test` run and share cache hashes; unblock SBOM-CONSOLE-23-001/002. | DevOps Guild, SBOM Service Guild (ops/devops) |
|
||||
| DEVOPS-SBOM-23-001 | DONE (2025-11-30) | Publish vetted offline NuGet feed + CI recipe for SbomService; prove with `dotnet test` run and share cache hashes; unblock SBOM-CONSOLE-23-001/002. | DevOps Guild, SBOM Service Guild (ops/devops) |
|
||||
| FEED-REMEDIATION-1001 | BLOCKED (2025-11-24) | Define remediation scope and runbook for overdue feeds (CCCS/CERTBUND); schedule refresh; depends on PREP-FEEDCONN-ICS-KISA-PLAN. | Concelier Feed Owners (ops/devops) |
|
||||
| FEEDCONN-ICSCISA-02-012 / FEEDCONN-KISA-02-008 | BLOCKED (2025-11-24) | Publish provenance refresh/connector schedule for ICSCISA/KISA feeds; execute remediation per runbook once owners provide plan. | Concelier Feed Owners (ops/devops) |
|
||||
|
||||
## Execution Log
|
||||
| Date (UTC) | Update | Owner |
|
||||
| --- | --- | --- |
|
||||
| 2025-11-30 | Completed DEVOPS-SBOM-23-001: added SBOM CI runner (`ops/devops/sbom-ci-runner/run-sbom-ci.sh`) with warmed-cache restore, binlog/TRX outputs, and NuGet cache hash evidence; documented in runner README. | DevOps |
|
||||
| 2025-11-30 | Completed DEVOPS-SCANNER-CI-11-001: added offline-friendly Scanner CI runner (`ops/devops/scanner-ci-runner/run-scanner-ci.sh`) and README; produces build binlog + TRX outputs from key test projects with warmed NuGet cache. | DevOps |
|
||||
| 2025-11-30 | Completed DEVOPS-ATTEST-73-001/73-002: added attestor CI stub (`ops/devops/attestation/ci.yml`) and secrets/rotation plan in `ops/devops/attestation/README.md`; pending mirror into `.gitea/workflows/attestor-ci.yml` for live runs. | DevOps |
|
||||
| 2025-11-30 | Completed DEVOPS-SPANSINK-31-003: added OTLP span sink compose stack + collector config (`docker-compose.spansink.yml`, `otel-spansink.yaml`), run script, and Grafana dashboard stub (`ops/devops/signals/dashboards/excititor-vex-traces.json`). | DevOps |
|
||||
@@ -68,6 +69,7 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A
|
||||
| 2025-11-24 | Added DEVOPS-SCANNER-JAVA-21-011-REL (moved from SPRINT_0131_0001_0001_scanner_surface.md) to keep DevOps release packaging in ops track. | Project Mgmt |
|
||||
| 2025-11-24 | Added DEVOPS-SPANSINK-31-003 (Excititor span sink for 31-003 traces) moved from SPRINT_0119_0001_0001_excititor_i per ops-only directive. | Project Mgmt |
|
||||
| 2025-11-24 | Imported Concelier feed ops items FEED-REMEDIATION-1001 and FEEDCONN-ICSCISA/KISA from Sprint 110; keeping feed remediation in ops track. | Project Mgmt |
|
||||
| 2025-11-30 | Completed DEVOPS-AIRGAP-58-001: added syslog/SMTP compose stack (`ops/devops/airgap/compose-syslog-smtp.yaml`) and health script (`health_syslog_smtp.sh`); documented in airgap README for sealed environments. | DevOps |
|
||||
| 2025-11-30 | DEVOPS-AIAI-31-001 DONE: added Advisory AI CI harness (`ops/devops/advisoryai-ci-runner/run-advisoryai-ci.sh`) producing binlog/TRX/summary; warmed local NuGet cache for offline runs; docs in runner README. | DevOps |
|
||||
|
||||
## Decisions & Risks
|
||||
|
||||
@@ -2100,7 +2100,7 @@
|
||||
| WEB-AIRGAP-56-002 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | |
|
||||
| WEB-AIRGAP-57-001 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, AirGap Policy Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | |
|
||||
| WEB-AIRGAP-58-001 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, AirGap Importer Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | |
|
||||
| WEB-AOC-19-002 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Ship `ProvenanceBuilder`, checksum utilities, and signature verification helper integrated with guard logging. Cover DSSE/CMS formats with unit tests. Dependencies: WEB-AOC-19-001. | | |
|
||||
| WEB-AOC-19-002 | DONE (2025-11-30) | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Ship `ProvenanceBuilder`, checksum utilities, and signature verification helper integrated with guard logging. Cover DSSE/CMS formats with unit tests. Dependencies: WEB-AOC-19-001. | | |
|
||||
| WEB-AOC-19-003 | TODO | | SPRINT_116_concelier_v | QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | |
|
||||
| WEB-AOC-19-004 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | |
|
||||
| WEB-AOC-19-005 | TODO | 2025-11-08 | SPRINT_116_concelier_v | Concelier WebService Guild, QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | |
|
||||
@@ -4290,7 +4290,7 @@
|
||||
| WEB-AIRGAP-56-002 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | |
|
||||
| WEB-AIRGAP-57-001 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, AirGap Policy Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | |
|
||||
| WEB-AIRGAP-58-001 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, AirGap Importer Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | |
|
||||
| WEB-AOC-19-002 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Ship `ProvenanceBuilder`, checksum utilities, and signature verification helper integrated with guard logging. Cover DSSE/CMS formats with unit tests. Dependencies: WEB-AOC-19-001. | | |
|
||||
| WEB-AOC-19-002 | DONE (2025-11-30) | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Ship `ProvenanceBuilder`, checksum utilities, and signature verification helper integrated with guard logging. Cover DSSE/CMS formats with unit tests. Dependencies: WEB-AOC-19-001. | | |
|
||||
| WEB-AOC-19-003 | TODO | | SPRINT_116_concelier_v | QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | |
|
||||
| WEB-AOC-19-004 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | |
|
||||
| WEB-AOC-19-005 | TODO | 2025-11-08 | SPRINT_116_concelier_v | Concelier WebService Guild, QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | |
|
||||
|
||||
@@ -8,6 +8,23 @@ This dossier summarises the end-to-end runtime topology after the Aggregation-On
|
||||
|
||||
---
|
||||
|
||||
> Need a quick orientation? The [Developer Quickstart](../onboarding/dev-quickstart.md) (29-Nov-2025 advisory) captures the core repositories, determinism checks, DSSE conventions, and starter tasks that explain how the platform pieces fit together.
|
||||
|
||||
> Planner note: the [SBOM→VEX proof blueprint](../product-advisories/29-Nov-2025 - SBOM to VEX Proof Pipeline Blueprint.md) shows the DSSE → Rekor v2 tiles → VEX linkage, so threat-model and compliance teams can copy the capture/verification checkpoints.
|
||||
|
||||
> Working on a feature? Check the [Implementor Guidelines](../product-advisories/30-Nov-2025 - Implementor Guidelines for Stella Ops.md) to align with the SRS + release playbook checklist before you merge anything into main.
|
||||
|
||||
> Need to prove Rekor receipts? The [Rekor Receipt Checklist](../product-advisories/30-Nov-2025 - Rekor Receipt Checklist for Stella Ops.md) maps each field to a module owner and explains offline metadata for deterministic re-verification.
|
||||
|
||||
> Taming unknowns? The [Unknowns Decay & Triage Heuristics](../product-advisories/30-Nov-2025 - Unknowns Decay & Triage Heuristics.md) explains the confidence decay card, triage queue view, and the daily export artifact for planning.
|
||||
|
||||
> Check the [Ecosystem Reality Test Cases](../product-advisories/30-Nov-2025 - Ecosystem Reality Test Cases for StellaOps.md) for reproducible acceptance tests based on credential leaks, offline DB schema issues, SBOM parity drift, and scanner version divergence.
|
||||
|
||||
> Need unblocker tasks? The [Standup Sprint Kickstarters](../product-advisories/30-Nov-2025 - Standup Sprint Kickstarters.md) lists three day-0 wins (scanner regressions, Postgres slice, DSSE/Rekor sweep) plus ready-to-copy ticket names.
|
||||
> Compare how evidence/suppression/audit flows work elsewhere via the [Comparative Evidence Patterns](../product-advisories/30-Nov-2025 - Comparative Evidence Patterns for Stella Ops.md) brief—Snyk, GitHub, Aqua, Anchore/Grype, Prisma Cloud, and the UX trade-offs.
|
||||
|
||||
> Evaluate public scanner incidents? The [Ecosystem Test Cases](../product-advisories/30-Nov-2025 - Ecosystem Test Cases for StellaOps.md) document five hardened regressions (Grype credential leak, Trivy offline schema, SBOM parity, Grype instability) that you can turn into acceptance tests today.
|
||||
|
||||
## 1 · System landscape
|
||||
|
||||
```mermaid
|
||||
|
||||
326
docs/onboarding/dev-quickstart.md
Normal file
326
docs/onboarding/dev-quickstart.md
Normal file
@@ -0,0 +1,326 @@
|
||||
# StellaOps Developer Quickstart
|
||||
|
||||
> **Audience:** Mid-level .NET developers
|
||||
> **Goal:** Get you productive on StellaOps in 1–2 days, with special focus on determinism, cryptographic attestations, and the canonical data model.
|
||||
|
||||
---
|
||||
|
||||
This quickstart mirrors the 29-Nov-2025 Developer Onboarding advisory (`docs/product-advisories/29-Nov-2025 - StellaOps – Mid-Level .NET Onboarding (Quick Start).md`) and keeps the determinism-first guidance in sync with that release note.
|
||||
|
||||
## 1. What You’re Building (Context)
|
||||
|
||||
StellaOps is a sovereign, air-gap-friendly platform that turns **SBOMs → VEX** with a fully **replayable, deterministic trust graph**.
|
||||
|
||||
Core concepts:
|
||||
|
||||
- **Deterministic scans:** Same inputs → same graph, hashes, and verdicts.
|
||||
- **Cryptographic attestations:** DSSE/in-toto envelopes, optional PQC.
|
||||
- **Trust lattice:** Merges vendor VEX, runtime signals, configs, etc. into a single deterministic verdict.
|
||||
- **Audit trail:** Every decision is reproducible from stored inputs and proofs.
|
||||
|
||||
If you think “content-addressed trust pipeline for SBOMs + VEX,” you’re in the right mental model.
|
||||
|
||||
---
|
||||
|
||||
## 2. Repository & Docs Map
|
||||
|
||||
Start by opening these projects **in order**:
|
||||
|
||||
1. `src/StellaOps.Scanner.WebService/`
|
||||
Scanning endpoints, rule plumbing, and calls into the trust lattice.
|
||||
2. `src/StellaOps.Vexer/` (a.k.a. *Excititor*)
|
||||
VEX verdict engine and trust-merge logic.
|
||||
3. `src/StellaOps.Sbomer/`
|
||||
SBOM ingest / normalize (CycloneDX, SPDX).
|
||||
4. `src/StellaOps.Authority/`
|
||||
Key management, DSSE/in-toto attestations, license tokens, Rekor integration.
|
||||
5. `src/StellaOps.Scheduler/`
|
||||
Batch processing, replay orchestration.
|
||||
6. `src/StellaOps.Shared/CanonicalModel/`
|
||||
Canonical entities & graph IDs. **Read this carefully** – it underpins determinism.
|
||||
|
||||
Helpful docs:
|
||||
|
||||
- `docs/modules/platform/*` – protocols (DSSE envelopes, lattice terms, trust receipts).
|
||||
- `docs/architecture/*` – high-level diagrams and flows.
|
||||
|
||||
---
|
||||
|
||||
## 3. Local Dev Setup
|
||||
|
||||
### 3.1 Prerequisites
|
||||
|
||||
- **.NET 10 SDK** (preview as specified in repo).
|
||||
- **Docker** (for DB, queues, object storage).
|
||||
- **Node.js** (for Angular UI, if you’re touching the frontend).
|
||||
- **WSL2** (optional but convenient on Windows).
|
||||
|
||||
### 3.2 Bring Up Infra
|
||||
|
||||
From the repo root:
|
||||
|
||||
```bash
|
||||
# Bring up core infra for offline / air-gap friendly dev
|
||||
docker compose -f compose/offline-kit.yml up -d
|
||||
```
|
||||
|
||||
This usually includes:
|
||||
|
||||
- MongoDB or Postgres (configurable).
|
||||
- RabbitMQ (or equivalent queue).
|
||||
- MinIO / object storage (depending on profile).
|
||||
|
||||
### 3.3 Configure Environment
|
||||
|
||||
```bash
|
||||
cp env/example.local.env .env
|
||||
```
|
||||
|
||||
Key settings:
|
||||
|
||||
- `STELLAOPS_DB=Mongo` or `Postgres`.
|
||||
- `AUTHORITY_*` – key material and config (see comments in `example.local.env`).
|
||||
- Optional: `AUTHORITY_PQC=on` to enable post-quantum keys (Dilithium).
|
||||
|
||||
### 3.4 Build & Run Backend
|
||||
|
||||
```bash
|
||||
# Restore & build everything
|
||||
dotnet restore
|
||||
dotnet build -c Debug
|
||||
|
||||
# Run a focused slice for development
|
||||
dotnet run --project src/StellaOps.Authority/StellaOps.Authority.csproj
|
||||
dotnet run --project src/StellaOps.Scanner.WebService/StellaOps.Scanner.WebService.csproj
|
||||
```
|
||||
|
||||
Health checks (adjust ports if needed):
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:5080/health # Authority
|
||||
curl -s http://localhost:5081/health # Scanner
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Deterministic Sanity Tests
|
||||
|
||||
These tests prove your local environment is configured correctly for **determinism**. If any of these fail due to snapshot mismatch, fix your environment before writing new features.
|
||||
|
||||
### 4.1 SBOM → VEX “Not Affected” (Reachability False)
|
||||
|
||||
```bash
|
||||
dotnet test tests/Determinism/Det_SbomToVex_NotAffected.csproj
|
||||
```
|
||||
|
||||
**What it checks:**
|
||||
|
||||
- Two consecutive runs with the same SBOM produce identical `GraphRevisionID` and DSSE payload hashes.
|
||||
|
||||
If they differ, inspect:
|
||||
|
||||
- JSON canonicalization.
|
||||
- Locale / culture.
|
||||
- Line endings.
|
||||
|
||||
### 4.2 In-toto Chain: Source → Build → Image Attestation
|
||||
|
||||
```bash
|
||||
dotnet test tests/Attestations/Att_InToto_Chain.csproj
|
||||
```
|
||||
|
||||
**What it checks:**
|
||||
|
||||
- DSSE envelope canonicalization is stable.
|
||||
- Signature over CBOR-canonical JSON matches the stored hash.
|
||||
- Full in-toto chain can be replayed deterministically.
|
||||
|
||||
### 4.3 Lattice Merge: Vendor VEX + Runtime Signal
|
||||
|
||||
```bash
|
||||
dotnet test tests/Lattice/Lattice_VendorPlusRuntime.csproj
|
||||
```
|
||||
|
||||
**What it checks:**
|
||||
|
||||
- Merge verdict is stable regardless of input set order.
|
||||
- Resulting `TrustReceipt` is byte-for-byte identical between runs.
|
||||
|
||||
If any “golden” snapshots differ, you likely have:
|
||||
|
||||
- Non-canonical JSON.
|
||||
- Unstable enumeration (e.g., iterating `Dictionary<>` directly).
|
||||
- Locale or newline drift.
|
||||
|
||||
---
|
||||
|
||||
## 5. Coding Conventions (Determinism & Crypto)
|
||||
|
||||
These are **non-negotiable** in code that affects trust graphs, proofs, or attestations.
|
||||
|
||||
### 5.1 JSON & Canonicalization
|
||||
|
||||
- Use the **`CanonicalJson`** helper whenever a payload is hashed, signed, or used for IDs.
|
||||
- Rules: UTF-8, sorted keys, no insignificant whitespace, `\n` line endings.
|
||||
|
||||
### 5.2 DSSE Envelopes
|
||||
|
||||
- `payloadType` must always be `application/vnd.stellaops.trust+json`.
|
||||
- Sign over the canonicalized bytes.
|
||||
|
||||
```csharp
|
||||
var payload = CanonicalJson.Serialize(trustDoc);
|
||||
var env = DsseEnvelope.Create("application/vnd.stellaops.trust+json", payload);
|
||||
var signed = await keyRing.SignAsync(env.CanonicalizeBytes());
|
||||
await rekor.SubmitAsync(signed, RekorMode.OfflineMirrorIfAirgapped);
|
||||
```
|
||||
|
||||
### 5.3 Hashing
|
||||
|
||||
- **BLAKE3** for internal content addressing.
|
||||
- **SHA-256** where interop demands it.
|
||||
- Never mix algorithms within the same ID type.
|
||||
|
||||
### 5.4 Keys & Algorithms
|
||||
|
||||
- Default signatures: **Ed25519** via `Authority.KeyRing`.
|
||||
- Optional PQC: **Dilithium** when `AUTHORITY_PQC=on`.
|
||||
- Always go through the keyring abstraction; never manage raw keys manually.
|
||||
|
||||
### 5.5 Time & Clocks
|
||||
|
||||
- Use `Instant`/`DateTimeOffset` (UTC), truncated to milliseconds.
|
||||
- Never use `DateTime.Now` or local clocks in canonical data.
|
||||
|
||||
### 5.6 IDs & Graph Nodes
|
||||
|
||||
- Canonical/public IDs derive from hashes of canonical bytes.
|
||||
- DB primary keys are implementation details.
|
||||
- Do not depend on DB auto-increment or implicit sort order when hashing.
|
||||
|
||||
### 5.7 VEX Verdicts
|
||||
|
||||
Every VEX verdict must:
|
||||
|
||||
- Carry `proofs[]` (reachability, config guards, runtime paths).
|
||||
- Emit a `receipt` signed by Authority, covering verdict, proof hashes, and context.
|
||||
|
||||
---
|
||||
|
||||
## 6. Daily Workflow
|
||||
|
||||
1. Pick a focused issue (see starter tasks below).
|
||||
2. Write tests first, especially determinism scenarios.
|
||||
3. Implement changes with canonicalization boundaries explicit and signing centralized.
|
||||
4. Run `dotnet test --filter Category=Determinism`.
|
||||
5. Commit with the appropriate prefix (`feat(scanner):`, `feat(vexer):`, `feat(authority):`) and mention the affected `GraphRevisionID` if your change alters the trust graph.
|
||||
|
||||
---
|
||||
|
||||
## 7. Suggested Starter Tasks
|
||||
|
||||
These introduce the canonical data model and determinism mindset.
|
||||
|
||||
### 7.1 Normalize CycloneDX Components → Canonical Packages
|
||||
|
||||
**Area:** `StellaOps.Sbomer`
|
||||
|
||||
**Tests:** `tests/Determinism/Det_SbomMapping`
|
||||
|
||||
**Definition of done:**
|
||||
|
||||
- Equivalent SBOMs (even if fields shuffle) yield identical package sets and canonical IDs.
|
||||
- `CanonicalPackageSet.hash` is stable.
|
||||
- Edge cases covered: missing `purl`, duplicate components, case variation.
|
||||
|
||||
### 7.2 Implement “Not-Affected by Configuration” Proof
|
||||
|
||||
**Area:** `StellaOps.Vexer/Proofs/ConfigSwitchProof.cs`
|
||||
|
||||
**Definition of done:**
|
||||
|
||||
- With `FeatureX=false`, CVE-1234 reports `status = not_affected` and the proof records `configPath` + `observed=false`.
|
||||
- Proof hash is deterministic and included in the DSSE receipt.
|
||||
- Lattice merge flips the verdict to `not_affected` when the runtime/config proof weight crosses the threshold, even if the vendor says `affected`.
|
||||
|
||||
### 7.3 Authority Offline Rekor Mirror Submitter
|
||||
|
||||
**Area:** `StellaOps.Authority/Rekor/RekorMirrorClient.cs`
|
||||
|
||||
**Definition of done:**
|
||||
|
||||
- `RekorMode.OfflineMirrorIfAirgapped` records canonical entries (JSON + hash path) locally.
|
||||
- `rekor sync` replays entries in order, preserving entry IDs.
|
||||
- Golden test ensures the same input sequence → same mirror tree hash.
|
||||
|
||||
---
|
||||
|
||||
## 8. Database Notes (Mongo ↔ Postgres)
|
||||
|
||||
- Use `StellaOps.Shared.Persistence` repository interfaces.
|
||||
- Canonical/public IDs are hash-derived; DB keys are internal details.
|
||||
- Never rely on DB sort order for anything that affects hashes or verdicts; re-canonicalize before hashing and apply deterministic ordering afterwards.
|
||||
|
||||
---
|
||||
|
||||
## 9. Common Pitfalls
|
||||
|
||||
1. Non-canonical JSON (unsorted keys, extra whitespace, mixed `\r\n`).
|
||||
2. Local time creeping into proofs (`DateTime.Now`).
|
||||
3. Unstable GUIDs in tests or canonical entities.
|
||||
4. Unordered collections (`Dictionary<>` iterations, LINQ without `OrderBy`) while hashing or serializing.
|
||||
5. Platform drift (Windows vs Linux newline/culture differences) – always use invariant culture and `\n` in canonical data.
|
||||
|
||||
---
|
||||
|
||||
## 10. Useful Commands
|
||||
|
||||
### 10.1 Determinism Pack
|
||||
|
||||
```bash
|
||||
# Run determinism-tagged fixtures
|
||||
dotnet test --filter Category=Determinism
|
||||
```
|
||||
|
||||
Update golden snapshots deliberately:
|
||||
|
||||
```bash
|
||||
dotnet test --filter Category=Determinism -- \
|
||||
TestRunParameters.Parameter(name="UpdateSnapshots", value="true")
|
||||
```
|
||||
|
||||
### 10.2 Quick API Smoke
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:5080/health
|
||||
|
||||
curl -s -X POST \
|
||||
http://localhost:5081/scan \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @samples/nginx.sbom.json
|
||||
```
|
||||
|
||||
### 10.3 Verify DSSE Signature Locally
|
||||
|
||||
```bash
|
||||
dotnet run --project tools/StellaOps.Tools.Verify -- file trust.receipt.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Glossary (Ask-Once)
|
||||
|
||||
- **SBOM** – Software Bill of Materials (CycloneDX/SPDX).
|
||||
- **VEX** – Vulnerability Exploitability eXchange: verdicts include `affected`, `not_affected`, `under_investigation`.
|
||||
- **DSSE** – Dead Simple Signing Envelope; we sign canonical bytes.
|
||||
- **In-toto** – Supply-chain attestation framework for source → build → artifact chains.
|
||||
- **Lattice** – Rule system merging multiple verdicts/proofs into deterministic outcomes.
|
||||
- **GraphRevisionID** – Hash of the canonical trust graph; acts like a build number for audits.
|
||||
|
||||
Welcome aboard. Your best “map” is:
|
||||
|
||||
1. Read the CanonicalModel types.
|
||||
2. Run the determinism tests.
|
||||
3. Ship one of the starter tasks with deterministic, test-covered changes.
|
||||
|
||||
Keep everything **canonical, hashable, and replayable** and you’ll fit right in.
|
||||
@@ -10,5 +10,7 @@ Artifacts supporting `DEVOPS-AIRGAP-56-001`:
|
||||
- `build_bootstrap_pack.py` — Builds a Bootstrap Pack from images/charts/extras listed in a JSON config, writing `bootstrap-manifest.json` + `checksums.sha256` deterministically.
|
||||
- `build_bootstrap_pack.sh` — Wrapper for the bootstrap pack builder.
|
||||
- `build_mirror_bundle.py` — Generates mirror bundle manifest + checksums with dual-control approvals; optional cosign signing. Outputs `mirror-bundle-manifest.json`, `checksums.sha256`, and optional signature/cert.
|
||||
- `compose-syslog-smtp.yaml` — Local SMTP (MailHog) + syslog-ng stack for sealed environments.
|
||||
- `health_syslog_smtp.sh` — Brings up the syslog/SMTP stack via docker compose and performs health checks (MailHog API + syslog logger).
|
||||
|
||||
See also `ops/devops/sealed-mode-ci/` for the full sealed-mode compose harness and `egress_probe.py`, which this verification script wraps.
|
||||
|
||||
31
ops/devops/airgap/compose-syslog-smtp.yaml
Normal file
31
ops/devops/airgap/compose-syslog-smtp.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
smtp:
|
||||
image: mailhog/mailhog:v1.0.1
|
||||
container_name: mailhog
|
||||
ports:
|
||||
- "1025:1025" # SMTP (plain)
|
||||
- "8025:8025" # Web UI
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://localhost:8025/api/v2/health"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
start_period: 5s
|
||||
restart: unless-stopped
|
||||
|
||||
syslog:
|
||||
image: balabit/syslog-ng:4.7.1
|
||||
container_name: syslog-ng
|
||||
ports:
|
||||
- "514:514/udp"
|
||||
- "514:514/tcp"
|
||||
command: ["/usr/sbin/syslog-ng", "-F", "-p", "/var/run/syslogd.pid"]
|
||||
healthcheck:
|
||||
test: ["CMD", "syslog-ng-ctl", "stats"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
start_period: 5s
|
||||
restart: unless-stopped
|
||||
23
ops/devops/airgap/health_syslog_smtp.sh
Normal file
23
ops/devops/airgap/health_syslog_smtp.sh
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Health check for compose-syslog-smtp.yaml (DEVOPS-AIRGAP-58-001)
|
||||
|
||||
COMPOSE_FILE="$(cd "$(dirname "$0")" && pwd)/compose-syslog-smtp.yaml"
|
||||
|
||||
echo "Starting syslog+smtp stack..."
|
||||
docker compose -f "$COMPOSE_FILE" up -d
|
||||
|
||||
echo "Waiting for health checks..."
|
||||
docker compose -f "$COMPOSE_FILE" wait >/dev/null 2>&1 || true
|
||||
|
||||
echo "Current health status:"
|
||||
docker compose -f "$COMPOSE_FILE" ps
|
||||
|
||||
echo "Sending test syslog message (UDP)..."
|
||||
logger -n 127.0.0.1 -P 514 -d "test syslog message $(date -u +%s)"
|
||||
|
||||
echo "SMTP health endpoint:"
|
||||
curl -sf http://127.0.0.1:8025/api/v2/health || exit 1
|
||||
|
||||
echo "Done."
|
||||
29
ops/devops/sbom-ci-runner/README.md
Normal file
29
ops/devops/sbom-ci-runner/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# SBOM Service CI Runner Harness (DEVOPS-SBOM-23-001)
|
||||
|
||||
Purpose: deterministic, offline-friendly CI harness for SBOM Service. Produces warmed-cache restore, build binlog, TRX outputs, and a NuGet cache hash to unblock SBOM console/consumer sprints.
|
||||
|
||||
Usage
|
||||
- From repo root run: `ops/devops/sbom-ci-runner/run-sbom-ci.sh`
|
||||
- Outputs land in `ops/devops/artifacts/sbom-ci/<UTC timestamp>/`:
|
||||
- `build.binlog` (solution build)
|
||||
- `tests/sbom.trx` (VSTest results)
|
||||
- `nuget-cache.hash` (sha256 over file name+size listing for offline cache traceability)
|
||||
- `summary.json` (paths + sources + cache hash)
|
||||
|
||||
Environment defaults
|
||||
- `DOTNET_CLI_TELEMETRY_OPTOUT=1`, `DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1`, `DOTNET_RESTORE_DISABLE_PARALLEL=1`
|
||||
- `NUGET_PACKAGES=$REPO/.nuget/packages`
|
||||
- `NUGET_SOURCES=$REPO/local-nugets;$REPO/.nuget/packages`
|
||||
- `TEST_FILTER` empty (set to narrow tests)
|
||||
|
||||
What it does
|
||||
1) Warm NuGet cache from `local-nugets/` into `$NUGET_PACKAGES` for air-gap parity.
|
||||
2) `dotnet restore` + `dotnet build` on `src/SbomService/StellaOps.SbomService.sln` with `/bl`.
|
||||
3) Run `StellaOps.SbomService.Tests` with TRX output (honors `TEST_FILTER`).
|
||||
4) Produce `nuget-cache.hash` using sorted file name+size list hashed with sha256 (lightweight evidence of cache contents).
|
||||
5) Emit `summary.json` with artefact paths and cache hash value.
|
||||
|
||||
Notes
|
||||
- Offline-only; no external services required.
|
||||
- Timestamped output folders keep ordering deterministic; consumers should sort lexicographically.
|
||||
- Extend `test_project` in the script if additional SBOM test projects are added.
|
||||
72
ops/devops/sbom-ci-runner/run-sbom-ci.sh
Normal file
72
ops/devops/sbom-ci-runner/run-sbom-ci.sh
Normal file
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# SBOM Service CI runner (DEVOPS-SBOM-23-001)
|
||||
# Builds SBOM solution and runs tests with warmed NuGet cache; emits binlog + TRX + cache hash summary.
|
||||
|
||||
repo_root="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
out_dir="$repo_root/ops/devops/artifacts/sbom-ci/$ts"
|
||||
logs_dir="$out_dir/tests"
|
||||
mkdir -p "$logs_dir"
|
||||
|
||||
export DOTNET_CLI_TELEMETRY_OPTOUT=${DOTNET_CLI_TELEMETRY_OPTOUT:-1}
|
||||
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=${DOTNET_SKIP_FIRST_TIME_EXPERIENCE:-1}
|
||||
export DOTNET_RESTORE_DISABLE_PARALLEL=${DOTNET_RESTORE_DISABLE_PARALLEL:-1}
|
||||
export NUGET_PACKAGES=${NUGET_PACKAGES:-$repo_root/.nuget/packages}
|
||||
export NUGET_SOURCES=${NUGET_SOURCES:-"$repo_root/local-nugets;$repo_root/.nuget/packages"}
|
||||
export TEST_FILTER=${TEST_FILTER:-""}
|
||||
|
||||
mkdir -p "$NUGET_PACKAGES"
|
||||
rsync -a "$repo_root/local-nugets/" "$NUGET_PACKAGES/" >/dev/null 2>&1 || true
|
||||
|
||||
restore_sources=()
|
||||
IFS=';' read -ra SRC_ARR <<< "$NUGET_SOURCES"
|
||||
for s in "${SRC_ARR[@]}"; do
|
||||
[[ -n "$s" ]] && restore_sources+=(--source "$s")
|
||||
done
|
||||
|
||||
solution="$repo_root/src/SbomService/StellaOps.SbomService.sln"
|
||||
dotnet restore "$solution" --ignore-failed-sources "${restore_sources[@]}"
|
||||
|
||||
build_binlog="$out_dir/build.binlog"
|
||||
dotnet build "$solution" -c Release /p:ContinuousIntegrationBuild=true /bl:"$build_binlog"
|
||||
|
||||
trx_name="sbom.trx"
|
||||
test_project="$repo_root/src/SbomService/StellaOps.SbomService.Tests/StellaOps.SbomService.Tests.csproj"
|
||||
common_test_args=( -c Release --no-build --results-directory "$logs_dir" )
|
||||
if [[ -n "$TEST_FILTER" ]]; then
|
||||
common_test_args+=( --filter "$TEST_FILTER" )
|
||||
fi
|
||||
|
||||
if [[ -f "$test_project" ]]; then
|
||||
dotnet test "$test_project" "${common_test_args[@]}" --logger "trx;LogFileName=$trx_name"
|
||||
fi
|
||||
|
||||
# Lightweight cache hash: list files with size, hash the listing
|
||||
cache_listing="$out_dir/nuget-cache.list"
|
||||
find "$NUGET_PACKAGES" -type f -printf "%P %s\n" | sort > "$cache_listing"
|
||||
cache_hash=$(sha256sum "$cache_listing" | awk '{print $1}')
|
||||
|
||||
echo "$cache_hash nuget-cache.list" > "$out_dir/nuget-cache.hash"
|
||||
|
||||
summary="$out_dir/summary.json"
|
||||
{
|
||||
printf '{\n'
|
||||
printf ' "timestamp_utc": "%s",\n' "$ts"
|
||||
printf ' "build_binlog": "%s",\n' "${build_binlog#${repo_root}/}"
|
||||
printf ' "tests": [\n'
|
||||
printf ' {"project":"SbomService","trx":"%s"}\n' "${logs_dir#${repo_root}/}/$trx_name"
|
||||
printf ' ],\n'
|
||||
printf ' "nuget_packages": "%s",\n' "${NUGET_PACKAGES#${repo_root}/}"
|
||||
printf ' "cache_hash": "%s",\n' "$cache_hash"
|
||||
printf ' "sources": [\n'
|
||||
for i in "${!SRC_ARR[@]}"; do
|
||||
sep=","; [[ $i -eq $((${#SRC_ARR[@]}-1)) ]] && sep=""
|
||||
printf ' "%s"%s\n' "${SRC_ARR[$i]}" "$sep"
|
||||
done
|
||||
printf ' ]\n'
|
||||
printf '}\n'
|
||||
} > "$summary"
|
||||
|
||||
echo "Artifacts written to ${out_dir#${repo_root}/}"
|
||||
8
src/Web/StellaOps.Web/TASKS.md
Normal file
8
src/Web/StellaOps.Web/TASKS.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Web Guild Tasks
|
||||
|
||||
| Task ID | State | Notes |
|
||||
| --- | --- | --- |
|
||||
| WEB-AOC-19-002 | DONE (2025-11-30) | Added provenance builder, checksum utilities, and DSSE/CMS signature verification helpers with unit tests. |
|
||||
| WEB-AOC-19-003 | TODO | Analyzer/guard validation remains; will align once helper APIs settle. |
|
||||
| WEB-CONSOLE-23-002 | TODO | Status/stream endpoints to proxy Scheduler once contracts finalized. |
|
||||
| WEB-EXC-25-001 | TODO | Exceptions workflow CRUD pending policy scopes. |
|
||||
71
src/Web/StellaOps.Web/src/app/core/aoc/checksum.util.ts
Normal file
71
src/Web/StellaOps.Web/src/app/core/aoc/checksum.util.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
export type HashAlgorithm = 'SHA-256' | 'SHA-512';
|
||||
|
||||
export interface DigestResult {
|
||||
algorithm: HashAlgorithm;
|
||||
hex: string;
|
||||
uri: string; // e.g. sha256:abcd...
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
function toUint8(payload: string | ArrayBuffer | Uint8Array): Uint8Array {
|
||||
if (typeof payload === 'string') {
|
||||
return encoder.encode(payload);
|
||||
}
|
||||
|
||||
if (payload instanceof ArrayBuffer) {
|
||||
return new Uint8Array(payload);
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
function toHex(buffer: ArrayBuffer): string {
|
||||
return Array.from(new Uint8Array(buffer))
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Deterministically compute a digest over an input payload using WebCrypto.
|
||||
* Returns both the raw hex and a URI-style prefix (sha256:...).
|
||||
*/
|
||||
export async function computeDigest(
|
||||
payload: string | ArrayBuffer | Uint8Array,
|
||||
algorithm: HashAlgorithm = 'SHA-256'
|
||||
): Promise<DigestResult> {
|
||||
if (!globalThis.crypto?.subtle) {
|
||||
throw new Error('WebCrypto unavailable: cannot compute digest');
|
||||
}
|
||||
|
||||
const data = toUint8(payload);
|
||||
const digestBuffer = await globalThis.crypto.subtle.digest(algorithm, data);
|
||||
const hex = toHex(digestBuffer);
|
||||
const prefix = algorithm.toLowerCase().replace('-', '');
|
||||
|
||||
return {
|
||||
algorithm,
|
||||
hex,
|
||||
uri: `${prefix}:${hex}`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience helper to deterministically serialize an object before hashing.
|
||||
* Uses stable key ordering and JSON without spaces.
|
||||
*/
|
||||
export function canonicalJson(input: unknown): string {
|
||||
const replacer = (_key: string, value: unknown) => {
|
||||
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||
return Object.keys(value as Record<string, unknown>)
|
||||
.sort()
|
||||
.reduce<Record<string, unknown>>((acc, key) => {
|
||||
acc[key] = (value as Record<string, unknown>)[key];
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
return JSON.stringify(input, replacer);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { ProvenanceBuilder } from './provenance-builder';
|
||||
import { canonicalJson, computeDigest } from './checksum.util';
|
||||
import {
|
||||
dssePreAuthEncode,
|
||||
verifyCmsSignature,
|
||||
verifyDsseSignature,
|
||||
} from './signature-verifier';
|
||||
|
||||
async function exportPublicKeyPem(publicKey: CryptoKey): Promise<string> {
|
||||
const spki = await crypto.subtle.exportKey('spki', publicKey);
|
||||
const base64 = btoa(String.fromCharCode(...new Uint8Array(spki)));
|
||||
const wrapped = base64.match(/.{1,64}/g)?.join('\n') ?? base64;
|
||||
return `-----BEGIN PUBLIC KEY-----\n${wrapped}\n-----END PUBLIC KEY-----`;
|
||||
}
|
||||
|
||||
describe('Provenance utilities', () => {
|
||||
it('builds deterministic provenance for objects', async () => {
|
||||
const builder = new ProvenanceBuilder(() => new Date('2025-11-30T12:00:00Z'));
|
||||
const provenance = await builder.build(
|
||||
{ b: 2, a: 1 },
|
||||
{
|
||||
sourceId: 'registry-1',
|
||||
sourceType: 'registry',
|
||||
sourceUrl: 'https://example.test/manifest',
|
||||
submitter: 'ci-bot',
|
||||
}
|
||||
);
|
||||
|
||||
expect(provenance.ingestedAt).toBe('2025-11-30T12:00:00.000Z');
|
||||
expect(provenance.digest.startsWith('sha256:')).toBeTrue();
|
||||
|
||||
const canonical = canonicalJson({ b: 2, a: 1 });
|
||||
const digest = await computeDigest(canonical, 'SHA-256');
|
||||
expect(provenance.digest).toBe(digest.uri);
|
||||
});
|
||||
|
||||
it('computes DSSE pre-auth encoding with correct prefix', () => {
|
||||
const payload = new TextEncoder().encode('hello');
|
||||
const pae = dssePreAuthEncode('text/plain', payload);
|
||||
const asText = new TextDecoder().decode(pae);
|
||||
expect(asText.startsWith('DSSEv1 10 text/plain 5 ')).toBeTrue();
|
||||
expect(asText.endsWith('hello')).toBeTrue();
|
||||
});
|
||||
|
||||
it('verifies CMS signatures', async () => {
|
||||
const keyPair = await crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
modulusLength: 2048,
|
||||
publicExponent: new Uint8Array([1, 0, 1]),
|
||||
hash: 'SHA-256',
|
||||
},
|
||||
true,
|
||||
['sign', 'verify']
|
||||
);
|
||||
|
||||
const message = new TextEncoder().encode('aoc-proof');
|
||||
const signature = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', keyPair.privateKey, message);
|
||||
const pem = await exportPublicKeyPem(keyPair.publicKey);
|
||||
|
||||
const result = await verifyCmsSignature({
|
||||
payload: message,
|
||||
signature,
|
||||
publicKeyPem: pem,
|
||||
algorithm: 'RSASSA-PKCS1-v1_5',
|
||||
hash: 'SHA-256',
|
||||
});
|
||||
|
||||
expect(result.valid).toBeTrue();
|
||||
});
|
||||
|
||||
it('fails verification when payload changes', async () => {
|
||||
const keyPair = await crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
modulusLength: 2048,
|
||||
publicExponent: new Uint8Array([1, 0, 1]),
|
||||
hash: 'SHA-256',
|
||||
},
|
||||
true,
|
||||
['sign', 'verify']
|
||||
);
|
||||
|
||||
const message = new TextEncoder().encode('aoc-proof');
|
||||
const signature = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', keyPair.privateKey, message);
|
||||
const pem = await exportPublicKeyPem(keyPair.publicKey);
|
||||
|
||||
const result = await verifyCmsSignature({
|
||||
payload: 'tampered',
|
||||
signature,
|
||||
publicKeyPem: pem,
|
||||
algorithm: 'RSASSA-PKCS1-v1_5',
|
||||
hash: 'SHA-256',
|
||||
});
|
||||
|
||||
expect(result.valid).toBeFalse();
|
||||
});
|
||||
|
||||
it('verifies DSSE signatures using pre-auth encoding', async () => {
|
||||
const keyPair = await crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
modulusLength: 2048,
|
||||
publicExponent: new Uint8Array([1, 0, 1]),
|
||||
hash: 'SHA-256',
|
||||
},
|
||||
true,
|
||||
['sign', 'verify']
|
||||
);
|
||||
|
||||
const payloadBytes = new TextEncoder().encode('{"sub":"example"}');
|
||||
const pae = dssePreAuthEncode('application/json', payloadBytes);
|
||||
const signature = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', keyPair.privateKey, pae);
|
||||
const pem = await exportPublicKeyPem(keyPair.publicKey);
|
||||
|
||||
const result = await verifyDsseSignature({
|
||||
payload: payloadBytes,
|
||||
payloadType: 'application/json',
|
||||
signature,
|
||||
publicKeyPem: pem,
|
||||
algorithm: 'RSASSA-PKCS1-v1_5',
|
||||
hash: 'SHA-256',
|
||||
});
|
||||
|
||||
expect(result.valid).toBeTrue();
|
||||
expect(result.message?.startsWith('sha256:')).toBeTrue();
|
||||
});
|
||||
});
|
||||
40
src/Web/StellaOps.Web/src/app/core/aoc/provenance-builder.ts
Normal file
40
src/Web/StellaOps.Web/src/app/core/aoc/provenance-builder.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AocProvenance } from '../api/aoc.models';
|
||||
import { HashAlgorithm, canonicalJson, computeDigest } from './checksum.util';
|
||||
|
||||
export interface ProvenanceOptions {
|
||||
sourceId: string;
|
||||
sourceType?: AocProvenance['sourceType'];
|
||||
sourceUrl?: string;
|
||||
submitter?: string;
|
||||
ingestedAt?: string;
|
||||
hashAlgorithm?: HashAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deterministic provenance builder used by the AOC workspace to attach
|
||||
* digests and timing metadata to documents before verification/signing.
|
||||
*/
|
||||
export class ProvenanceBuilder {
|
||||
constructor(private readonly clock: () => Date = () => new Date()) {}
|
||||
|
||||
async build(
|
||||
payload: string | ArrayBuffer | Uint8Array | Record<string, unknown>,
|
||||
options: ProvenanceOptions
|
||||
): Promise<AocProvenance> {
|
||||
const serialised =
|
||||
typeof payload === 'string' || payload instanceof ArrayBuffer || payload instanceof Uint8Array
|
||||
? payload
|
||||
: canonicalJson(payload);
|
||||
|
||||
const digest = await computeDigest(serialised, options.hashAlgorithm ?? 'SHA-256');
|
||||
|
||||
return {
|
||||
sourceId: options.sourceId,
|
||||
sourceType: options.sourceType,
|
||||
sourceUrl: options.sourceUrl,
|
||||
submitter: options.submitter,
|
||||
ingestedAt: options.ingestedAt ?? this.clock().toISOString(),
|
||||
digest: digest.uri,
|
||||
};
|
||||
}
|
||||
}
|
||||
133
src/Web/StellaOps.Web/src/app/core/aoc/signature-verifier.ts
Normal file
133
src/Web/StellaOps.Web/src/app/core/aoc/signature-verifier.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { HashAlgorithm, computeDigest } from './checksum.util';
|
||||
|
||||
export interface VerificationResult {
|
||||
valid: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface VerifyOptions {
|
||||
payload: string | ArrayBuffer | Uint8Array;
|
||||
signature: ArrayBuffer | Uint8Array | string; // base64 if string
|
||||
publicKeyPem: string;
|
||||
algorithm?: 'RSASSA-PKCS1-v1_5' | 'RSA-PSS';
|
||||
hash?: HashAlgorithm;
|
||||
saltLength?: number; // RSA-PSS only
|
||||
}
|
||||
|
||||
function base64ToArrayBuffer(input: string): ArrayBuffer {
|
||||
const normalized = input.replace(/\s+/g, '');
|
||||
const binary = atob(normalized);
|
||||
const buffer = new Uint8Array(binary.length);
|
||||
for (let i = 0; i < binary.length; i += 1) {
|
||||
buffer[i] = binary.charCodeAt(i);
|
||||
}
|
||||
return buffer.buffer;
|
||||
}
|
||||
|
||||
async function importPublicKey(
|
||||
pem: string,
|
||||
algorithm: 'RSASSA-PKCS1-v1_5' | 'RSA-PSS',
|
||||
hash: HashAlgorithm
|
||||
): Promise<CryptoKey> {
|
||||
const clean = pem
|
||||
.replace('-----BEGIN PUBLIC KEY-----', '')
|
||||
.replace('-----END PUBLIC KEY-----', '')
|
||||
.replace(/\s+/g, '');
|
||||
const binaryDer = base64ToArrayBuffer(clean);
|
||||
|
||||
return globalThis.crypto.subtle.importKey(
|
||||
'spki',
|
||||
binaryDer,
|
||||
{
|
||||
name: algorithm,
|
||||
hash: { name: hash },
|
||||
},
|
||||
true,
|
||||
['verify']
|
||||
);
|
||||
}
|
||||
|
||||
function toUint8(payload: string | ArrayBuffer | Uint8Array): Uint8Array {
|
||||
if (typeof payload === 'string') {
|
||||
return new TextEncoder().encode(payload);
|
||||
}
|
||||
if (payload instanceof ArrayBuffer) {
|
||||
return new Uint8Array(payload);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
function normalizeSignature(sig: ArrayBuffer | Uint8Array | string): ArrayBuffer {
|
||||
if (typeof sig === 'string') {
|
||||
return base64ToArrayBuffer(sig);
|
||||
}
|
||||
if (sig instanceof ArrayBuffer) {
|
||||
return sig;
|
||||
}
|
||||
return sig.buffer;
|
||||
}
|
||||
|
||||
export async function verifyCmsSignature(options: VerifyOptions): Promise<VerificationResult> {
|
||||
if (!globalThis.crypto?.subtle) {
|
||||
return { valid: false, message: 'WebCrypto unavailable' };
|
||||
}
|
||||
|
||||
const algorithm = options.algorithm ?? 'RSASSA-PKCS1-v1_5';
|
||||
const hash = options.hash ?? 'SHA-256';
|
||||
|
||||
const key = await importPublicKey(options.publicKeyPem, algorithm, hash);
|
||||
const payload = toUint8(options.payload);
|
||||
const signature = normalizeSignature(options.signature);
|
||||
|
||||
const verified = await globalThis.crypto.subtle.verify(
|
||||
{ name: algorithm, saltLength: options.saltLength ?? 32 },
|
||||
key,
|
||||
signature,
|
||||
payload
|
||||
);
|
||||
|
||||
return verified
|
||||
? { valid: true }
|
||||
: { valid: false, message: 'Signature verification failed' };
|
||||
}
|
||||
|
||||
export interface DsseVerifyOptions extends VerifyOptions {
|
||||
payloadType: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* DSSE pre-authentication encoding (PAE) as defined by sigstore.
|
||||
* Format: `DSSEv1 <len(type)> <type> <len(payload)> <payload>`
|
||||
*/
|
||||
export function dssePreAuthEncode(payloadType: string, payload: Uint8Array): Uint8Array {
|
||||
const enc = new TextEncoder();
|
||||
const header = `DSSEv1 ${payloadType.length} ${payloadType} ${payload.length} `;
|
||||
const headerBytes = enc.encode(header);
|
||||
const output = new Uint8Array(headerBytes.length + payload.length);
|
||||
output.set(headerBytes, 0);
|
||||
output.set(payload, headerBytes.length);
|
||||
return output;
|
||||
}
|
||||
|
||||
export async function verifyDsseSignature(
|
||||
options: DsseVerifyOptions
|
||||
): Promise<VerificationResult> {
|
||||
if (!globalThis.crypto?.subtle) {
|
||||
return { valid: false, message: 'WebCrypto unavailable' };
|
||||
}
|
||||
|
||||
const payloadBytes = toUint8(options.payload);
|
||||
const pae = dssePreAuthEncode(options.payloadType, payloadBytes);
|
||||
const cmsResult = await verifyCmsSignature({
|
||||
...options,
|
||||
payload: pae,
|
||||
});
|
||||
|
||||
if (!cmsResult.valid) {
|
||||
return cmsResult;
|
||||
}
|
||||
|
||||
// Provide a digest hint for downstream display/debugging
|
||||
const digest = await computeDigest(payloadBytes, options.hash ?? 'SHA-256');
|
||||
return { valid: true, message: digest.uri };
|
||||
}
|
||||
Reference in New Issue
Block a user