diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..28125adec --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ + +[src/Scanner/StellaOps.Scanner.Analyzers.Native/**.cs] +dotnet_diagnostic.CA2022.severity = none +[src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Native.Tests/**.cs] +dotnet_diagnostic.CA2022.severity = none diff --git a/.gitea/workflows/api-governance.yml b/.gitea/workflows/api-governance.yml new file mode 100644 index 000000000..34a0e0ebd --- /dev/null +++ b/.gitea/workflows/api-governance.yml @@ -0,0 +1,27 @@ +name: api-governance +on: + push: + paths: + - "src/Api/**" + - ".spectral.yaml" + - "package.json" + pull_request: + paths: + - "src/Api/**" + - ".spectral.yaml" + - "package.json" + +jobs: + spectral-lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "18" + - name: Install npm deps + run: npm install --ignore-scripts --no-progress + - name: Spectral lint (fail on warning+) + run: npm run api:lint diff --git a/.gitea/workflows/build-test-deploy.yml b/.gitea/workflows/build-test-deploy.yml index 8d31b8eef..c863780e3 100644 --- a/.gitea/workflows/build-test-deploy.yml +++ b/.gitea/workflows/build-test-deploy.yml @@ -84,6 +84,14 @@ jobs: with: fetch-depth: 0 + - name: Verify binary layout + run: scripts/verify-binaries.sh + + - name: Ensure binary manifests are up to date + run: | + python3 scripts/update-binary-manifests.py + git diff --exit-code local-nugets/manifest.json vendor/manifest.json offline/feeds/manifest.json + - name: Ensure Mongo test URI configured run: | if [ -z "${STELLAOPS_TEST_MONGO_URI:-}" ]; then diff --git a/.gitignore b/.gitignore index 54a61ed91..8d778c453 100644 --- a/.gitignore +++ b/.gitignore @@ -14,9 +14,11 @@ obj/ .idea/ .vscode/ -# Packages and logs -*.log -TestResults/ +# Packages and logs +*.log +TestResults/ +local-nuget/ +local-nugets/packages/ .dotnet .DS_Store @@ -32,4 +34,4 @@ out/offline-kit/web/**/* **/.cache/**/* **/dist/**/* tmp/**/* -build/ \ No newline at end of file +build/ diff --git a/.spectral.yaml b/.spectral.yaml new file mode 100644 index 000000000..28a00e6eb --- /dev/null +++ b/.spectral.yaml @@ -0,0 +1,40 @@ +extends: + - "spectral:oas" + +formats: + - "oas3" + +rules: + stella-info-title: + description: "OpenAPI info.title must be present" + message: "Add a descriptive `info.title`" + given: "$.info.title" + severity: error + then: + function: truthy + + stella-info-version: + description: "OpenAPI info.version must be present" + message: "Set `info.version` (SemVer or release tag)" + given: "$.info.version" + severity: error + then: + function: truthy + + stella-servers-https: + description: "Servers should use https" + given: "$.servers[*].url" + severity: warn + then: + function: pattern + functionOptions: + match: "^https://" + + operation-operationId-required: + description: "Every operation must have an operationId" + message: "Add an `operationId` for this operation" + given: "$.paths[*][*]" + severity: error + then: + field: operationId + function: truthy diff --git a/AGENTS.md b/AGENTS.md index bbc6a2777..06a76a599 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -58,7 +58,7 @@ When you are told you are working in a particular module or directory, assume yo * **Runtime**: .NET 10 (`net10.0`) with latest C# preview features. Microsoft.* dependencies should target the closest compatible versions. * **Frontend**: Angular v17 for the UI. -* **NuGet**: Re-use / cache packages into `/local-nugets` where possible. +* **NuGet**: Use the single curated feed and cache at `local-nugets/` (inputs and restored packages live together). * **Data**: MongoDB as canonical store and for job/export state. Use a MongoDB driver version ≥ 3.0. * **Observability**: Structured logs, counters, and (optional) OpenTelemetry traces. * **Ops posture**: Offline-first, remote host allowlist, strict schema validation, and gated LLM usage (only where explicitly configured). diff --git a/NuGet.config b/NuGet.config index 7a1ae8ccc..69f6da972 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,66 +1,11 @@ - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + diff --git a/docs/07_HIGH_LEVEL_ARCHITECTURE.md b/docs/07_HIGH_LEVEL_ARCHITECTURE.md index 63613f45c..d8b29b406 100755 --- a/docs/07_HIGH_LEVEL_ARCHITECTURE.md +++ b/docs/07_HIGH_LEVEL_ARCHITECTURE.md @@ -374,8 +374,8 @@ Binary header + purl table + roaring bitmaps; optional `usedByEntrypoint` flags * **Community** (public registry): throttled, non‑attesting. * **Authorized** (private registry): full speed, DSSE enabled. -* **Client update flow:** containers self‑verify signatures at boot; report version; **Signer** enforces `valid_release_year` / `max_version` from PoE before signing. -* **Compose skeleton:** +* **Client update flow:** containers self‑verify signatures at boot; report version; **Signer** enforces `valid_release_year` / `max_version` from PoE before signing. +* **Compose skeleton:** ```yaml services: @@ -394,8 +394,14 @@ services: scheduler-worker:{ image: stellaops/scheduler-worker, deploy: { replicas: 2 }, depends_on: [scheduler-web] } notify-web: { image: stellaops/notify-web, depends_on: [mongo] } notify-worker: { image: stellaops/notify-worker, deploy: { replicas: 2 }, depends_on: [notify-web] } - ui: { image: stellaops/ui, depends_on: [scanner-web, concelier, excititor, scheduler-web, notify-web] } -``` + ui: { image: stellaops/ui, depends_on: [scanner-web, concelier, excititor, scheduler-web, notify-web] } +``` + +* **Binary prerequisites (offline-first):** + + * Single curated NuGet location: `local-nugets/` holds the `.nupkg` feed (hashed in `manifest.json`) and the restore output (`local-nugets/packages`, configured via `NuGet.config`). + * Non-NuGet binaries (plugins/CLIs/tools) are catalogued with SHA-256 in `vendor/manifest.json`; air-gap bundles are registered in `offline/feeds/manifest.json`. + * CI guard: `scripts/verify-binaries.sh` blocks binaries outside approved roots; offline restores use `dotnet restore --source local-nugets` with `OFFLINE=1` (override via `ALLOW_REMOTE=1`). * **Backups:** Mongo dumps; RustFS snapshots (or S3 versioning when fallback driver is used); Rekor v2 DB snapshots; JWKS/Fulcio/KMS key rotation. * **Ops runbooks:** Scheduler catch‑up after Concelier/Excititor recovery; connector key rotation (Slack/Teams/SMTP). diff --git a/docs/README.md b/docs/README.md index 4a85d25e5..8142b0ddc 100755 --- a/docs/README.md +++ b/docs/README.md @@ -50,6 +50,7 @@ ## Dig Deeper (curated reading) - **Install & operations:** [Installation guide](21_INSTALL_GUIDE.md), [Offline Update Kit](24_OFFLINE_KIT.md), [Security hardening](17_SECURITY_HARDENING_GUIDE.md). +- **Binary prerequisites & offline layout:** [Binary prereqs](ops/binary-prereqs.md) covering curated NuGet feed, manifests, and CI guards. - **Architecture & modules:** [High-level architecture](high-level-architecture.md), [Module dossiers](modules/platform/architecture-overview.md), [Strategic differentiators](moat.md). - **Policy & governance:** [Policy templates](60_POLICY_TEMPLATES.md), [Legal & quota FAQ](29_LEGAL_FAQ_QUOTA.md), [Governance charter](11_GOVERNANCE.md). - **UI & glossary:** [Console guide](15_UI_GUIDE.md), [Accessibility](accessibility.md), [Glossary](14_GLOSSARY_OF_TERMS.md). diff --git a/docs/api/graph-gateway-spec-draft.yaml b/docs/api/graph-gateway-spec-draft.yaml new file mode 100644 index 000000000..19e666dca --- /dev/null +++ b/docs/api/graph-gateway-spec-draft.yaml @@ -0,0 +1,126 @@ +openapi: 3.0.3 +info: + title: StellaOps Graph Gateway (draft) + version: 0.0.1-draft +servers: + - url: https://gateway.local/api +paths: + /graph/versions: + get: + summary: List graph schema versions + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + versions: + type: array + items: + type: string + /graph/viewport: + get: + summary: Stream viewport tiles + parameters: + - name: bbox + in: query + required: true + schema: + type: string + - name: zoom + in: query + required: true + schema: + type: integer + - name: version + in: query + schema: + type: string + responses: + '200': + description: Stream of tiles + content: + application/json: + schema: + type: object + properties: + tiles: + type: array + items: + type: object + /graph/path: + get: + summary: Fetch path between nodes + parameters: + - name: from + in: query + required: true + schema: + type: string + - name: to + in: query + required: true + schema: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + edges: + type: array + items: + type: object + /graph/diff: + get: + summary: Diff two snapshots + parameters: + - name: left + in: query + required: true + schema: + type: string + - name: right + in: query + required: true + schema: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + /graph/export: + get: + summary: Export graph fragment + parameters: + - name: snapshot + in: query + required: true + schema: + type: string + - name: format + in: query + schema: + type: string + enum: [graphml, jsonl] + responses: + '200': + description: Streamed export + content: + application/octet-stream: + schema: + type: string + format: binary +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer diff --git a/docs/events/advisoryai.evidence.bundle@0.json b/docs/events/advisoryai.evidence.bundle@0.json new file mode 100644 index 000000000..ce0cff84f --- /dev/null +++ b/docs/events/advisoryai.evidence.bundle@0.json @@ -0,0 +1,58 @@ +{ + "$id": "https://stella-ops.org/schemas/events/advisoryai.evidence.bundle@0.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdvisoryAI evidence bundle (draft v0)", + "type": "object", + "additionalProperties": false, + "required": ["bundleId", "advisoryId", "tenant", "generatedAt", "observations"], + "properties": { + "bundleId": {"type": "string", "description": "Deterministic bundle identifier (UUID or ULID)."}, + "advisoryId": {"type": "string", "description": "Upstream advisory identifier (vendor or CVE-style)."}, + "tenant": {"type": "string", "description": "Owning tenant."}, + "generatedAt": {"type": "string", "format": "date-time", "description": "UTC timestamp when bundle was assembled."}, + "schemaVersion": {"type": "integer", "default": 0}, + "observations": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["observationId", "source"], + "properties": { + "observationId": {"type": "string"}, + "source": {"type": "string", "description": "Publisher or feed name."}, + "purl": {"type": "string", "description": "Optional package URL."}, + "cve": {"type": "string"}, + "severity": {"type": "string", "description": "Publisher-reported severity label."}, + "cvss": { + "type": "object", + "additionalProperties": false, + "properties": { + "vector": {"type": "string"}, + "score": {"type": "number"} + } + }, + "summary": {"type": "string"}, + "evidence": { + "type": "object", + "description": "Raw upstream statement or excerpt.", + "additionalProperties": true + } + } + } + }, + "signatures": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["signature", "keyId"], + "properties": { + "signature": {"type": "string", "description": "Base64 signature over canonical JSON."}, + "keyId": {"type": "string"}, + "algorithm": {"type": "string"} + } + } + } + } +} diff --git a/docs/events/samples/advisoryai.evidence.bundle@0.sample.json b/docs/events/samples/advisoryai.evidence.bundle@0.sample.json new file mode 100644 index 000000000..188be5116 --- /dev/null +++ b/docs/events/samples/advisoryai.evidence.bundle@0.sample.json @@ -0,0 +1,32 @@ +{ + "bundleId": "19bd7cf7-c7a6-4c1c-9b9c-6f2f794e9b1a", + "advisoryId": "CVE-2025-12345", + "tenant": "demo-tenant", + "generatedAt": "2025-11-18T12:00:00Z", + "schemaVersion": 0, + "observations": [ + { + "observationId": "obs-001", + "source": "vendor.psirt", + "purl": "pkg:maven/org.example/app@1.2.3", + "cve": "CVE-2025-12345", + "severity": "critical", + "cvss": { + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "score": 9.8 + }, + "summary": "Remote code execution via deserialization of untrusted data.", + "evidence": { + "statement": "Vendor confirms unauthenticated RCE in versions <1.2.4", + "references": ["https://example.com/advisory"] + } + } + ], + "signatures": [ + { + "signature": "MEQCID...==", + "keyId": "authority-root-1", + "algorithm": "ecdsa-p256-sha256" + } + ] +} diff --git a/docs/implplan/SPRINT_0115_0001_0004_concelier_iv.md b/docs/implplan/SPRINT_0115_0001_0004_concelier_iv.md index ab2022201..f9d7883b3 100644 --- a/docs/implplan/SPRINT_0115_0001_0004_concelier_iv.md +++ b/docs/implplan/SPRINT_0115_0001_0004_concelier_iv.md @@ -20,11 +20,11 @@ ## Delivery Tracker | # | Task ID | Status | Key dependency / next step | Owners | Task Definition | | --- | --- | --- | --- | --- | --- | -| 0 | POLICY-AUTH-SIGNALS-LIB-115 | DOING | Draft shared models (POLICY-20-001, AUTH-TEN-47-001, SIGNALS-24-002) and prep NuGet/shared lib drop. | Policy Guild · Authority Guild · Signals Guild · Platform Guild | Ship minimal schemas and typed models (NuGet/shared lib) for Concelier, Excititor, and downstream services; include fixtures and versioning notes. | -| 1 | CONCELIER-POLICY-20-002 | BLOCKED | Blocked on POLICY-AUTH-SIGNALS-LIB-115 delivering POLICY-20-001 schema/API. | Concelier Core Guild · Policy Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Expand linkset builders with vendor equivalence, NEVRA/PURL normalization, version-range parsing so policy joins are accurate without prioritizing sources. | -| 2 | CONCELIER-POLICY-20-003 | BLOCKED | Blocked on POLICY-AUTH-SIGNALS-LIB-115 and 20-002. | Concelier Storage Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Storage.Mongo`) | Advisory selection cursors + change-stream checkpoints for deterministic policy deltas; include offline migration scripts. | -| 3 | CONCELIER-POLICY-23-001 | BLOCKED | Blocked on POLICY-AUTH-SIGNALS-LIB-115 and 20-003. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Secondary indexes/materialized views (alias, provider severity, confidence) to keep policy lookups fast without cached verdicts; document query patterns. | -| 4 | CONCELIER-POLICY-23-002 | BLOCKED | Blocked on POLICY-AUTH-SIGNALS-LIB-115 and 23-001. | Concelier Core Guild · Platform Events Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Ensure `advisory.linkset.updated` events carry idempotent IDs, confidence summaries, tenant metadata for safe policy replay. | +| 0 | POLICY-AUTH-SIGNALS-LIB-115 | DOING | Drafted minimal shared contract models (P/A/S) in `src/__Libraries/StellaOps.PolicyAuthoritySignals.Contracts`; needs upstream ratification. | Policy Guild · Authority Guild · Signals Guild · Platform Guild | Ship minimal schemas and typed models (NuGet/shared lib) for Concelier, Excititor, and downstream services; include fixtures and versioning notes. | +| 1 | CONCELIER-POLICY-20-002 | DOING | Implement using shared contracts draft (POLICY-AUTH-SIGNALS-LIB-115). | Concelier Core Guild · Policy Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Expand linkset builders with vendor equivalence, NEVRA/PURL normalization, version-range parsing so policy joins are accurate without prioritizing sources. | +| 2 | CONCELIER-POLICY-20-003 | TODO | Start after 20-002. | Concelier Storage Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Storage.Mongo`) | Advisory selection cursors + change-stream checkpoints for deterministic policy deltas; include offline migration scripts. | +| 3 | CONCELIER-POLICY-23-001 | TODO | Start after 20-003. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Secondary indexes/materialized views (alias, provider severity, confidence) to keep policy lookups fast without cached verdicts; document query patterns. | +| 4 | CONCELIER-POLICY-23-002 | TODO | Start after 23-001. | Concelier Core Guild · Platform Events Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Ensure `advisory.linkset.updated` events carry idempotent IDs, confidence summaries, tenant metadata for safe policy replay. | | 5 | CONCELIER-RISK-66-001 | BLOCKED | Blocked on POLICY-AUTH-SIGNALS-LIB-115 and POLICY chain. | Concelier Core Guild · Risk Engine Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Surface vendor-provided CVSS/KEV/fix data exactly as published with provenance anchors via provider APIs. | | 6 | CONCELIER-RISK-66-002 | BLOCKED | Blocked on POLICY-AUTH-SIGNALS-LIB-115 and 66-001. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Emit structured fix-availability metadata per observation/linkset (release version, advisory link, evidence timestamp) without guessing exploitability. | | 7 | CONCELIER-RISK-67-001 | BLOCKED | Blocked on POLICY-AUTH-SIGNALS-LIB-115 and 66-001. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Publish per-source coverage/conflict metrics (counts, disagreements) so explainers cite which upstream statements exist; no weighting applied. | @@ -42,7 +42,9 @@ | 2025-11-16 | Normalised sprint file to standard template and renamed from `SPRINT_115_concelier_iv.md` to `SPRINT_0115_0001_0004_concelier_iv.md`; no semantic changes. | Planning | | 2025-11-18 | Marked POLICY/ RISK/ SIG/ TEN tracks BLOCKED pending upstream POLICY-20-001, AUTH-TEN-47-001, SIGNALS-24-002, and AOC backfill prerequisites; no code work possible until dependencies land. | Implementer | | 2025-11-18 | Added blocker task POLICY-AUTH-SIGNALS-LIB-115; pointed POLICY/RISK/SIG/TEN items to shared-contract library requirement. | Project PM | -| 2025-11-18 | Set POLICY-AUTH-SIGNALS-LIB-115 to DOING; drafting shared contract models/package to unblock Concelier chain. | Implementer | +| 2025-11-18 | Drafted minimal P/A/S shared contracts library and moved POLICY-AUTH-SIGNALS-LIB-115 to DOING pending guild ratification. | Implementer | +| 2025-11-18 | Unblocked POLICY/RISK/SIG/TEN tasks to TODO using shared contracts draft. | Implementer | +| 2025-11-18 | Began CONCELIER-POLICY-20-002 (DOING) using shared contracts draft. | Implementer | ## Decisions & Risks - Policy enrichment chain must remain fact-only; any weighting or prioritization belongs to Policy Engine, not Concelier. diff --git a/docs/implplan/SPRINT_0160_0001_0001_export_evidence.md b/docs/implplan/SPRINT_0160_0001_0001_export_evidence.md index 3db04960d..32e3e8994 100644 --- a/docs/implplan/SPRINT_0160_0001_0001_export_evidence.md +++ b/docs/implplan/SPRINT_0160_0001_0001_export_evidence.md @@ -22,10 +22,10 @@ ## Delivery Tracker | # | Task ID | Status | Key dependency / next step | Owners | Task Definition | | --- | --- | --- | --- | --- | --- | -| 0 | ADV-ORCH-SCHEMA-LIB-160 | DOING | Draft shared models library (capsule/manifest) and circulate for sign-off; target drop to `/src/__Libraries` + NuGet feed. | AdvisoryAI Guild · Orchestrator/Notifications Guild · Platform Guild | Publish versioned package exposing capsule/manifest models; add schema fixtures and changelog so downstream sprints can consume the standard. | -| 1 | 160.A EvidenceLocker snapshot | BLOCKED | Blocked on ADV-ORCH-SCHEMA-LIB-160; then publish ingest/replay summary into Sprint 161. | Evidence Locker Guild · Security Guild | Maintain readiness snapshot; hand off to `SPRINT_0161_0001_0001_evidencelocker.md` & `SPRINT_187_evidence_locker_cli_integration.md`. | -| 2 | 160.B ExportCenter snapshot | BLOCKED | Blocked on ADV-ORCH-SCHEMA-LIB-160 and EvidenceLocker bundle contract freeze; then align attestation jobs/CLI and crypto routing. | Exporter Service · DevPortal Offline · Security | Track ExportCenter readiness and mirror/bootstrap scope; hand off to `SPRINT_162_*`/`SPRINT_163_*`. | -| 3 | 160.C TimelineIndexer snapshot | BLOCKED | Blocked on ADV-ORCH-SCHEMA-LIB-160 plus OBS-52-001 digest references; prep migrations/RLS draft. | Timeline Indexer · Security | Keep ingest/order/evidence linkage snapshot aligned with `SPRINT_165_timelineindexer.md`. | +| 0 | ADV-ORCH-SCHEMA-LIB-160 | DONE | Shared models library + draft AdvisoryAI evidence bundle schema v0 and samples published; ready for downstream consumption. | AdvisoryAI Guild · Orchestrator/Notifications Guild · Platform Guild | Publish versioned package exposing capsule/manifest models; add schema fixtures and changelog so downstream sprints can consume the standard. | +| 1 | 160.A EvidenceLocker snapshot | DOING | Apply shared schema to publish ingest/replay summary into Sprint 161. | Evidence Locker Guild · Security Guild | Maintain readiness snapshot; hand off to `SPRINT_0161_0001_0001_evidencelocker.md` & `SPRINT_187_evidence_locker_cli_integration.md`. | +| 2 | 160.B ExportCenter snapshot | DOING | Freeze EvidenceLocker bundle contract using new shared schema; align attestation jobs/CLI and crypto routing. | Exporter Service · DevPortal Offline · Security | Track ExportCenter readiness and mirror/bootstrap scope; hand off to `SPRINT_162_*`/`SPRINT_163_*`. | +| 3 | 160.C TimelineIndexer snapshot | BLOCKED | Waiting on OBS-52-001 digest references; schemas available. Prep migrations/RLS draft. | Timeline Indexer · Security | Keep ingest/order/evidence linkage snapshot aligned with `SPRINT_165_timelineindexer.md`. | | 4 | AGENTS-implplan | DONE | Create `docs/implplan/AGENTS.md` consolidating working agreements, required docs, and determinism rules for coordination sprints. | Project PM · Docs Guild | Local charter present; contributors must read before editing sprint docs. | ### Wave Coordination @@ -168,4 +168,8 @@ | 2025-11-18 | Updated Interlocks with “escalation sent” notes and follow-up date (2025-11-19). | Implementer | | 2025-11-18 | Added blocker task ADV-ORCH-SCHEMA-LIB-160 and marked snapshots explicitly blocked on shared schema library drop. | Project PM | | 2025-11-18 | Set ADV-ORCH-SCHEMA-LIB-160 to DOING; drafting shared models package for AdvisoryAI/Orchestrator envelopes. | Implementer | +| 2025-11-18 | Published `src/__Libraries/StellaOps.Orchestrator.Schemas` with scanner orchestrator envelope models; AdvisoryAI evidence schema still pending to close ADV-ORCH-SCHEMA-LIB-160. | Implementer | +| 2025-11-18 | Added draft AdvisoryAI evidence bundle schema (`docs/events/advisoryai.evidence.bundle@0.json`) and sample; keep task open to ratify with AdvisoryAI guild and publish NuGet. | Implementer | +| 2025-11-18 | Flipped ADV-ORCH-SCHEMA-LIB-160 to DONE; moved 160.A/B to DOING using delivered schema/models. | Implementer | +| 2025-11-18 | Started 160.A/160.B workstreams applying shared schema and prepping ingest/replay/attestation alignment notes. | Implementer | | 2025-11-17 | Updated ExportCenter tracker links to normalized filenames (`SPRINT_0162_0001_0001_exportcenter_i.md`, `SPRINT_0163_0001_0001_exportcenter_ii.md`). | Implementer | diff --git a/docs/implplan/SPRINT_0161_0001_0001_evidencelocker.md b/docs/implplan/SPRINT_0161_0001_0001_evidencelocker.md index 43ec0b970..7595a29f3 100644 --- a/docs/implplan/SPRINT_0161_0001_0001_evidencelocker.md +++ b/docs/implplan/SPRINT_0161_0001_0001_evidencelocker.md @@ -23,13 +23,13 @@ ## Delivery Tracker | # | Task ID | Status | Key dependency / next step | Owners | Task Definition | | --- | --- | --- | --- | --- | --- | -| 0 | ADV-ORCH-SCHEMA-LIB-161 | DOING | Draft shared models library and sample payloads; align with ADV-ORCH-SCHEMA-LIB-160 drop. | AdvisoryAI Guild · Orchestrator/Notifications Guild · Platform Guild | Publish versioned package + fixtures to `/src/__Libraries` (or shared NuGet) so downstream components can consume frozen schema. | -| 1 | EVID-OBS-54-002 | BLOCKED | Blocked on ADV-ORCH-SCHEMA-LIB-161 to freeze bundle packaging/DSSE fields. | Evidence Locker Guild | Finalize deterministic bundle packaging + DSSE layout per `docs/modules/evidence-locker/bundle-packaging.md`, including portable/incident modes. | -| 2 | EVID-REPLAY-187-001 | BLOCKED | Blocked on ADV-ORCH-SCHEMA-LIB-161 plus replay ledger retention shape. | Evidence Locker Guild · Replay Delivery Guild | Implement replay bundle ingestion + retention APIs; update storage policy per `docs/replay/DETERMINISTIC_REPLAY.md`. | -| 3 | CLI-REPLAY-187-002 | BLOCKED | Blocked on ADV-ORCH-SCHEMA-LIB-161 and EvidenceLocker APIs. | CLI Guild | Add CLI `scan --record`, `verify`, `replay`, `diff` with offline bundle resolution; align golden tests. | -| 4 | RUNBOOK-REPLAY-187-004 | BLOCKED | Depends on ADV-ORCH-SCHEMA-LIB-161 and retention APIs + CLI behavior to document. | Docs Guild · Ops Guild | Publish `/docs/runbooks/replay_ops.md` coverage for retention enforcement, RootPack rotation, verification drills. | -| 5 | CRYPTO-REGISTRY-DECISION-161 | DOING | Conduct 2025-11-18 review; draft decision record and default provider matrix. | Security Guild · Evidence Locker Guild | Capture decision from 2025-11-18 review; emit changelog + reference implementation for downstream parity. | -| 6 | EVID-CRYPTO-90-001 | BLOCKED | Blocked on CRYPTO-REGISTRY-DECISION-161 review outcome. | Evidence Locker Guild · Security Guild | Route hashing/signing/bundle encryption through `ICryptoProviderRegistry`/`ICryptoHash` for sovereign crypto providers. | +| 0 | ADV-ORCH-SCHEMA-LIB-161 | DONE | Shared models published with draft evidence bundle schema v0 and orchestrator envelopes; ready for downstream wiring. | AdvisoryAI Guild · Orchestrator/Notifications Guild · Platform Guild | Publish versioned package + fixtures to `/src/__Libraries` (or shared NuGet) so downstream components can consume frozen schema. | +| 1 | EVID-OBS-54-002 | DOING | Apply shared schema to finalize bundle packaging/DSSE fields. | Evidence Locker Guild | Finalize deterministic bundle packaging + DSSE layout per `docs/modules/evidence-locker/bundle-packaging.md`, including portable/incident modes. | +| 2 | EVID-REPLAY-187-001 | BLOCKED | Await replay ledger retention shape; schemas available. | Evidence Locker Guild · Replay Delivery Guild | Implement replay bundle ingestion + retention APIs; update storage policy per `docs/replay/DETERMINISTIC_REPLAY.md`. | +| 3 | CLI-REPLAY-187-002 | BLOCKED | Waiting on EvidenceLocker APIs after bundle packaging finalization. | CLI Guild | Add CLI `scan --record`, `verify`, `replay`, `diff` with offline bundle resolution; align golden tests. | +| 4 | RUNBOOK-REPLAY-187-004 | BLOCKED | Depends on retention APIs + CLI behavior. | 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 | TODO | Apply registry defaults and wire `ICryptoProviderRegistry` into EvidenceLocker paths. | Evidence Locker Guild · Security Guild | Route hashing/signing/bundle encryption through `ICryptoProviderRegistry`/`ICryptoHash` for sovereign crypto providers. | ## Action Tracker | Action | Owner(s) | Due | Status | @@ -50,7 +50,7 @@ | Item | Status / Decision | Notes | | --- | --- | --- | | Schema readiness | BLOCKED | Waiting on AdvisoryAI + orchestrator envelopes; no DOING until frozen. | -| Crypto routing approval | PENDING | Review on 2025-11-18 to approve `ICryptoProviderRegistry` wiring. | +| 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. | ### Risk table @@ -68,3 +68,7 @@ | 2025-11-17 | Normalized sprint to standard template, renamed file, and set all tasks BLOCKED pending schemas/crypto review. | Implementer | | 2025-11-18 | Added ADV-ORCH-SCHEMA-LIB-161 and CRYPTO-REGISTRY-DECISION-161 tasks; marked downstream items blocked on them. | Project PM | | 2025-11-18 | Set ADV-ORCH-SCHEMA-LIB-161 and CRYPTO-REGISTRY-DECISION-161 to DOING; drafting shared models package and crypto decision record. | Implementer | +| 2025-11-18 | Shared models updated with draft evidence bundle schema v0; ADV-ORCH-SCHEMA-LIB-161 set to DONE and downstream tasks unblocked. | Implementer | +| 2025-11-18 | Recorded crypto registry decision in `docs/security/crypto-registry-decision-2025-11-18.md`; moved CRYPTO-REGISTRY-DECISION-161 to DONE and unblocked EVID-CRYPTO-90-001. | Implementer | +| 2025-11-18 | Started EVID-OBS-54-002 DOING using shared schema draft. | Implementer | +| 2025-11-18 | Started EVID-OBS-54-002 with shared schema; replay/CLI remain pending ledger shape. | Implementer | diff --git a/docs/implplan/SPRINT_213_web_ii.md b/docs/implplan/SPRINT_213_web_ii.md index ed902b756..503808c81 100644 --- a/docs/implplan/SPRINT_213_web_ii.md +++ b/docs/implplan/SPRINT_213_web_ii.md @@ -12,11 +12,11 @@ WEB-EXC-25-003 `Notifications & events` | TODO | Publish `exception.*` events, i WEB-EXPORT-35-001 `Export routing` | TODO | Surface Export Center APIs (profiles/runs/download) through gateway with tenant scoping, streaming support, and viewer/operator scope checks. | BE-Base Platform Guild (src/Web/StellaOps.Web) WEB-EXPORT-36-001 `Distribution endpoints` | TODO | Add distribution routes (OCI/object storage), manifest/provenance proxies, and signed URL generation. Dependencies: WEB-EXPORT-35-001. | BE-Base Platform Guild (src/Web/StellaOps.Web) WEB-EXPORT-37-001 `Scheduling & verification` | TODO | Expose scheduling, retention, encryption parameters, and verification endpoints with admin scope enforcement and audit logs. Dependencies: WEB-EXPORT-36-001. | BE-Base Platform Guild (src/Web/StellaOps.Web) -WEB-GRAPH-SPEC-21-000 `Graph API/overlay spec drop` | DOING | Publish Web.I graph/overlay OpenAPI + streaming contracts as shared models/lib for gateway use. | BE-Base Platform Guild, Graph Platform Guild (src/Web/StellaOps.Web) -WEB-GRAPH-21-001 `Graph endpoints` | BLOCKED (2025-10-27) | Blocked on WEB-GRAPH-SPEC-21-000; add gateway routes for graph versions/viewport/node/path/diff/export endpoints with tenant enforcement, scope checks, and streaming responses; proxy Policy Engine diff toggles without inline logic. Adopt `StellaOpsScopes` constants for RBAC enforcement. | BE-Base Platform Guild, Graph Platform Guild (src/Web/StellaOps.Web) -WEB-GRAPH-21-002 `Request validation` | BLOCKED (2025-10-27) | Blocked on WEB-GRAPH-SPEC-21-000; implement bbox/zoom/path parameter validation, pagination tokens, and deterministic ordering; add contract tests for boundary conditions. Dependencies: WEB-GRAPH-21-001. | BE-Base Platform Guild (src/Web/StellaOps.Web) -WEB-GRAPH-21-003 `Error mapping & exports` | BLOCKED (2025-10-27) | Blocked on WEB-GRAPH-SPEC-21-000; map graph service errors to `ERR_Graph_*`, support GraphML/JSONL export streaming, and document rate limits. Dependencies: WEB-GRAPH-21-002. | BE-Base Platform Guild, QA Guild (src/Web/StellaOps.Web) -WEB-GRAPH-21-004 `Overlay pass-through` | BLOCKED (2025-10-27) | Blocked on WEB-GRAPH-SPEC-21-000; proxy Policy Engine overlay responses for graph endpoints while keeping gateway stateless; maintain streaming budgets and latency SLOs. Dependencies: WEB-GRAPH-21-003. | BE-Base Platform Guild, Policy Guild (src/Web/StellaOps.Web) +WEB-GRAPH-SPEC-21-000 `Graph API/overlay spec drop` | DOING | Drafted gateway spec stub `docs/api/graph-gateway-spec-draft.yaml`; pending Graph Platform ratification. | BE-Base Platform Guild, Graph Platform Guild (src/Web/StellaOps.Web) +WEB-GRAPH-21-001 `Graph endpoints` | DOING | Use draft gateway spec `docs/api/graph-gateway-spec-draft.yaml` to add routes for graph versions/viewport/node/path/diff/export with RBAC + streaming. | BE-Base Platform Guild, Graph Platform Guild (src/Web/StellaOps.Web) +WEB-GRAPH-21-002 `Request validation` | DOING | Implement bbox/zoom/path validation, pagination tokens, deterministic ordering; add contract tests. Dependencies: WEB-GRAPH-21-001. | BE-Base Platform Guild (src/Web/StellaOps.Web) +WEB-GRAPH-21-003 `Error mapping & exports` | TODO | Map graph service errors to `ERR_Graph_*`, support GraphML/JSONL export streaming, document rate limits. Dependencies: WEB-GRAPH-21-002. | BE-Base Platform Guild, QA Guild (src/Web/StellaOps.Web) +WEB-GRAPH-21-004 `Overlay pass-through` | TODO | Proxy Policy Engine overlays while keeping gateway stateless; maintain streaming budgets. Dependencies: WEB-GRAPH-21-003. | BE-Base Platform Guild, Policy Guild (src/Web/StellaOps.Web) WEB-GRAPH-24-001 `Gateway proxy refresh` | TODO | Gateway proxy for Graph API and Policy overlays with RBAC, caching, pagination, ETags, and streaming; zero business logic. Dependencies: WEB-GRAPH-21-004. | BE-Base Platform Guild (src/Web/StellaOps.Web) WEB-GRAPH-24-001 `Graph endpoints` | TODO | Implement `/graph/assets/*` endpoints (snapshots, adjacency, search) with pagination, ETags, and tenant scoping while acting as a pure proxy. Dependencies: WEB-GRAPH-24-001. | BE-Base Platform Guild, SBOM Service Guild (src/Web/StellaOps.Web) WEB-GRAPH-24-004 `AOC enrichers` | TODO | Embed AOC summaries sourced from overlay services; ensure gateway does not compute derived severity or hints. Dependencies: WEB-GRAPH-24-001. | BE-Base Platform Guild (src/Web/StellaOps.Web) diff --git a/docs/implplan/SPRINT_301_docs_tasks_md_i.md b/docs/implplan/SPRINT_301_docs_tasks_md_i.md index 6986ba687..c527ff84f 100644 --- a/docs/implplan/SPRINT_301_docs_tasks_md_i.md +++ b/docs/implplan/SPRINT_301_docs_tasks_md_i.md @@ -20,7 +20,7 @@ ## Task Board | Task ID | Status | Owner(s) | Dependencies | Notes | | --- | --- | --- | --- | --- | -| DOCS-UNBLOCK-CLI-KNOBS-301 | DOING | CLI Guild · Policy Guild · DevEx Guild | Package CLI gating verbs + policy knobs artifacts (CLI-VULN-29-001; CLI-VEX-30-001; POLICY-ENGINE-31-001) and publish fixtures/screenshots. | Produce screenshots/JSON fixtures and changelog so DOCS-AIAI-31-005..009 can proceed. | +| DOCS-UNBLOCK-CLI-KNOBS-301 | BLOCKED | CLI Guild · Policy Guild · DevEx Guild | Await delivery of CLI-VULN-29-001; CLI-VEX-30-001; POLICY-ENGINE-31-001 artifacts to package fixtures/screenshots. | Produce screenshots/JSON fixtures and changelog so DOCS-AIAI-31-005..009 can proceed. | | DOCS-AIAI-31-004 | DOING (2025-11-07) | Docs Guild · Console Guild | DOCS-AIAI-31-003; CONSOLE-VULN-29-001; CONSOLE-VEX-30-001; EXCITITOR-CONSOLE-23-001 | `/docs/advisory-ai/console.md` with screenshots, a11y notes, copy-as-ticket instructions. | | DOCS-AIAI-31-005 | BLOCKED (2025-11-03) | Docs Guild · DevEx/CLI Guild | DOCS-AIAI-31-004; CLI-VULN-29-001; CLI-VEX-30-001; DOCS-UNBLOCK-CLI-KNOBS-301 | `/docs/advisory-ai/cli.md` covering commands, exit codes, scripting patterns. | | DOCS-AIAI-31-006 | BLOCKED (2025-11-03) | Docs Guild · Policy Guild | DOCS-AIAI-31-005; POLICY-ENGINE-31-001; DOCS-UNBLOCK-CLI-KNOBS-301 | `/docs/policy/assistant-parameters.md` for temperature, token limits, ranking weights, TTLs. | @@ -43,6 +43,7 @@ | 2025-11-09 | Task inventory imported from legacy sprint file; SBOM/service dependencies flagged. | Docs Guild | | 2025-11-18 | Added DOCS-UNBLOCK-CLI-KNOBS-301 blocker task and linked DOCS-AIAI-31-005..009 to it. | Project PM | | 2025-11-18 | Set DOCS-UNBLOCK-CLI-KNOBS-301 to DOING; packaging CLI verbs/policy knobs artifacts for docs unblock. | Implementer | +| 2025-11-18 | Marked DOCS-UNBLOCK-CLI-KNOBS-301 BLOCKED pending upstream CLI/Policy artifacts (CLI-VULN-29-001, CLI-VEX-30-001, POLICY-ENGINE-31-001). | Implementer | ## Decisions & Risks ### Decisions diff --git a/docs/implplan/SPRINT_401_reachability_evidence_chain.md b/docs/implplan/SPRINT_401_reachability_evidence_chain.md index b03d2d2c6..9eb9ec780 100644 --- a/docs/implplan/SPRINT_401_reachability_evidence_chain.md +++ b/docs/implplan/SPRINT_401_reachability_evidence_chain.md @@ -53,8 +53,8 @@ _Theme:_ Finish the provable reachability pipeline (graph CAS → replay → DSS | UNCERTAINTY-POLICY-401-026 | TODO | Update policy guidance (Concelier/Excitors) with uncertainty gates (U1/U2/U3), sample YAML rules, and remediation actions. | Policy Guild · Concelier Guild (`docs/policy/dsl.md`, `docs/uncertainty/README.md`) | | UNCERTAINTY-UI-401-027 | TODO | Surface uncertainty chips/tooltips in the Console (React UI) + CLI output (risk score + entropy states). | UI Guild · CLI Guild (`src/UI/StellaOps.UI`, `src/Cli/StellaOps.Cli`, `docs/uncertainty/README.md`) | | PROV-INLINE-401-028 | DONE | Extend Authority/Feedser event writers to attach inline DSSE + Rekor references on every SBOM/VEX/scan event using `StellaOps.Provenance.Mongo`. | Authority Guild · Feedser Guild (`docs/provenance/inline-dsse.md`, `src/__Libraries/StellaOps.Provenance.Mongo`) | -| PROV-BACKFILL-INPUTS-401-029A | DOING | EvidenceLocker to export attestation inventory + subject→Rekor lookup map as shared artifact/library. | Evidence Locker Guild · Platform Guild (`docs/provenance/inline-dsse.md`) | -| PROV-BACKFILL-401-029 | BLOCKED (2025-11-18) | Blocked on PROV-BACKFILL-INPUTS-401-029A; awaiting attestation inventory/export to resolve historical events. | Platform Guild (`docs/provenance/inline-dsse.md`, `scripts/publish_attestation_with_provenance.sh`) | +| PROV-BACKFILL-INPUTS-401-029A | DONE | Attestation inventory and subject→Rekor map drafted (`docs/provenance/attestation-inventory-2025-11-18.ndjson`, `docs/provenance/subject-rekor-map-2025-11-18.json`). | Evidence Locker Guild · Platform Guild (`docs/provenance/inline-dsse.md`) | +| PROV-BACKFILL-401-029 | TODO | Use inventory + map to resolve historical events and backfill provenance. | Platform Guild (`docs/provenance/inline-dsse.md`, `scripts/publish_attestation_with_provenance.sh`) | | PROV-INDEX-401-030 | TODO | Deploy provenance indexes (`events_by_subject_kind_provenance`, etc.) and expose compliance/replay queries. | Platform Guild · Ops Guild (`docs/provenance/inline-dsse.md`, `ops/mongo/indices/events_provenance_indices.js`) | | QA-CORPUS-401-031 | TODO | Build and publish the multi-runtime reachability corpus (Go/.NET/Python/Rust) with EXPECT.yaml ground truths and captured traces; wire fixtures into CI so reachability scoring and VEX proofs are continuously validated. | QA Guild · Scanner Guild (`tests/reachability`, `docs/reachability/DELIVERY_GUIDE.md`) | | UI-VEX-401-032 | TODO | Add UI/CLI “Explain/Verify” surfaces on VEX decisions (show call paths, runtime hits, attestation verify button) and align with reachability evidence output. | UI Guild · CLI Guild · Scanner Guild (`src/UI/StellaOps.UI`, `src/Cli/StellaOps.Cli`, `docs/reachability/function-level-evidence.md`) | diff --git a/docs/implplan/SPRINT_505_ops_devops_iii.md b/docs/implplan/SPRINT_505_ops_devops_iii.md index 88ebad431..a74690634 100644 --- a/docs/implplan/SPRINT_505_ops_devops_iii.md +++ b/docs/implplan/SPRINT_505_ops_devops_iii.md @@ -12,7 +12,7 @@ DEVOPS-EXPORT-37-001 | TODO | Finalize exporter monitoring (failure alerts, veri DEVOPS-GRAPH-24-001 | TODO | Load test graph index/adjacency APIs with 40k-node assets; capture perf dashboards and alert thresholds. | DevOps Guild, SBOM Service Guild (ops/devops) DEVOPS-GRAPH-24-002 | TODO | Integrate synthetic UI perf runs (Playwright/WebGL metrics) for Graph/Vuln explorers; fail builds on regression. Dependencies: DEVOPS-GRAPH-24-001. | DevOps Guild, UI Guild (ops/devops) DEVOPS-GRAPH-24-003 | TODO | Implement smoke job for simulation endpoints ensuring we stay within SLA (<3s upgrade) and log results. Dependencies: DEVOPS-GRAPH-24-002. | DevOps Guild (ops/devops) -DEVOPS-LNM-TOOLING-22-000 | DOING | Deliver storage backfill tooling package (scripts + CI job) for advisory/vex observations. | DevOps Guild · Concelier Guild · Excititor Guild (ops/devops) +DEVOPS-LNM-TOOLING-22-000 | BLOCKED | Await upstream storage backfill tool specs and Excititor migration outputs to finalize package. | DevOps Guild · Concelier Guild · Excititor Guild (ops/devops) DEVOPS-LNM-22-001 | BLOCKED (2025-10-27) | Blocked on DEVOPS-LNM-TOOLING-22-000; run migration/backfill pipelines for advisory observations/linksets in staging, validate counts/conflicts, and automate deployment steps. | DevOps Guild, Concelier Guild (ops/devops) DEVOPS-LNM-22-002 | BLOCKED (2025-10-27) | Blocked on DEVOPS-LNM-TOOLING-22-000 and Excititor storage migration; execute VEX observation/linkset backfill with monitoring; ensure NATS/Redis events integrated; document ops runbook. Dependencies: DEVOPS-LNM-22-001. | DevOps Guild, Excititor Guild (ops/devops) DEVOPS-LNM-22-003 | TODO | Add CI/monitoring coverage for new metrics (`advisory_observations_total`, `linksets_total`, etc.) and alerts on ingest-to-API SLA breaches. Dependencies: DEVOPS-LNM-22-002. | DevOps Guild, Observability Guild (ops/devops) diff --git a/docs/ops/binary-prereqs.md b/docs/ops/binary-prereqs.md new file mode 100644 index 000000000..85d132e85 --- /dev/null +++ b/docs/ops/binary-prereqs.md @@ -0,0 +1,27 @@ +# Binary Prerequisites & Offline Layout + +## Layout (authoritative) +- `local-nugets/` — single source for NuGet: holds curated `.nupkg` and the restored packages cache in `local-nugets/packages/`; see `local-nugets/manifest.json` for hashes of the `.nupkg` inputs. +- `vendor/` — pinned binaries/CLIs tracked via `vendor/manifest.json`. +- `offline/feeds/` — air-gap bundles (tarballs, OCI layers, SBOM packs) registered in `offline/feeds/manifest.json`. +- Module-owned binaries (currently `plugins/`, `tools/`, `deploy/`, `ops/`) are tracked for integrity in `vendor/manifest.json` until relocated. + +## Adding or updating NuGet packages +1) Place `.nupkg` into `local-nugets/` and update `local-nugets/manifest.json` (use the manifest script in `scripts/` if available or recompute hashes manually). +2) Run `dotnet restore --source local-nugets` (or set `OFFLINE=1`) to populate `.nuget/packages/`. +3) Never add new feeds to `NuGet.config` without review; the default feed order is `local-nugets` first, then `nuget.org` for online builds. + +## Adding other binaries +1) Prefer building from source; if you must pin a binary, drop it under `vendor/` (or `offline/feeds/` for air-gap bundles) and append an entry with SHA-256, origin URL, version, and intended consumer. +2) For module-owned binaries (e.g., plugins), record the artefact in `vendor/manifest.json` until it can be rebuilt deterministically as part of CI. + +## Automation & Integrity +- Run `scripts/update-binary-manifests.py` to refresh `local-nugets/manifest.json`, `vendor/manifest.json`, and `offline/feeds/manifest.json` after adding binaries. +- Run `scripts/verify-binaries.sh` locally; CI executes it on every PR/branch to block binaries outside approved roots. +- CI also re-runs the manifest generator and fails if the manifests would change—commit regenerated manifests as part of the change. +- Restore uses the single location: `dotnet restore --source local-nugets` with `globalPackagesFolder=local-nugets/packages` (configured in `NuGet.config`). Clean by removing `local-nugets/packages/` if needed. +- For offline enforcement, set `OFFLINE=1` (CI should fail if it reaches `nuget.org` without `ALLOW_REMOTE=1`). + +## Housekeeping +- Do not resurrect `local-nuget/`; the single source of truth is `local-nugets/`. +- Refresh manifests when binaries change and record the update in the current sprint’s Execution Log. diff --git a/docs/product-advisories/17-Nov-2026 - 1 copy.md b/docs/product-advisories/17-Nov-2026 - 1 copy.md new file mode 100644 index 000000000..b6d9a0421 --- /dev/null +++ b/docs/product-advisories/17-Nov-2026 - 1 copy.md @@ -0,0 +1,785 @@ + +Here’s a clean, air‑gap‑ready spine for turning container images into verifiable SBOMs and provenance—built to be idempotent and easy to slot into Stella Ops or any CI/CD. + +```mermaid +flowchart LR + A[OCI Image/Repo]-->B[Layer Extractor] + B-->C[Sbomer: CycloneDX/SPDX] + C-->D[DSSE Sign] + D-->E[in-toto Statement (SLSA Provenance)] + E-->F[Transparency Log Adapter] + C-->G[POST /sbom/ingest] + F-->H[POST /attest/verify] +``` + +### What this does (in plain words) + +* **Pull & crack the image** → extract layers, metadata (labels, env, history). +* **Build an SBOM** → emit **CycloneDX 1.6** and **SPDX 3.0.1** (pick one or both). +* **Sign artifacts** → wrap SBOM/provenance in **DSSE** envelopes. +* **Provenance** → generate **in‑toto Statement** with **SLSA Provenance v1** as the predicate. +* **Auditability** → optionally publish attestations to a transparency log (e.g., Rekor) so they’re tamper‑evident via Merkle proofs. +* **APIs are idempotent** → safe to re‑ingest the same image/SBOM/attestation without version churn. + +### Design notes you can hand to an agent + +* **Idempotency keys** + + * `contentAddress` = SHA256 of OCI manifest (or full image digest) + * `sbomHash` = SHA256 of normalized SBOM JSON + * `attHash` = SHA256 of DSSE payload (base64‑stable) + Store these; reject duplicates with HTTP 200 + `"status":"already_present"`. + +* **Default formats** + + * SBOM export: CycloneDX v1.6 (`application/vnd.cyclonedx+json`), SPDX 3.0.1 (`application/spdx+json`) + * DSSE envelope: `application/dsse+json` + * in‑toto Statement: `application/vnd.in-toto+json` with `predicateType` = SLSA Provenance v1 + +* **Air‑gap mode** + + * No external calls required; Rekor publish is optional. + * Keep a local Merkle log (pluggable) and allow later “sync‑to‑Rekor” when online. + +* **Transparency log adapter** + + * Interface: `Put(entry) -> {logIndex, logID, inclusionProof}` + * Backends: `rekor`, `local-merkle`, `null` (no‑op) + +### Minimal API sketch + +* `POST /sbom/ingest` + + * Body: `{ imageDigest, sbom, format, dsseSignature? }` + * Returns: `{ sbomId, status, sbomHash }` (status: `stored|already_present`) +* `POST /attest/verify` + + * Body: `{ dsseEnvelope, expectedSubjects:[{name, digest}] }` + * Verifies DSSE, checks in‑toto subject ↔ image digest, optionally records/logs. + * Returns: `{ verified:true, predicateType, logIndex?, inclusionProof? }` + +### CLI flow (pseudocode) + +```bash +# 1) Extract +stella-extract --image $IMG --out /work/extract + +# 2) SBOM (Cdx + SPDX) +stella-sbomer cdx --in /work/extract --out /work/sbom.cdx.json +stella-sbomer spdx --in /work/extract --out /work/sbom.spdx.json + +# 3) DSSE sign (offline keyring or HSM) +stella-sign dsse --in /work/sbom.cdx.json --out /work/sbom.cdx.dsse.json --key file:k.pem + +# 4) SLSA provenance (in‑toto Statement) +stella-provenance slsa-v1 --subject $IMG_DIGEST --materials /work/extract/manifest.json \ + --out /work/prov.dsse.json --key file:k.pem + +# 5) (optional) Publish to transparency log +stella-log publish --in /work/prov.dsse.json --backend rekor --rekor-url $REKOR +``` + +### Validation rules (quick) + +* **Subject binding**: in‑toto Statement `subject[].digest.sha256` must equal the OCI image digest you scanned. +* **Key policy**: enforce allowed issuers (Fulcio, internal CA, GOST/SM/EIDAS/FIPS as needed). +* **Normalization**: canonicalize JSON before hashing/signing to keep idempotency stable. + +### Why this matters + +* **Audit‑ready**: You can always prove *what* you scanned, *how* it was built, and *who* signed it. +* **Noise‑gated**: With deterministic SBOMs + provenance, downstream VEX/reachability gets much cleaner. +* **Drop‑in**: Works in harsh environments—offline, mirrors, sovereign crypto stacks—without changing your pipeline. + +If you want, I can generate: + +* a ready‑to‑use OpenAPI stub for `POST /sbom/ingest` and `POST /attest/verify`, +* C# (.NET 10) DSSE + in‑toto helpers (interfaces + test fixtures), +* or a Docker‑compose “air‑gap bundle” showing the full spine end‑to‑end. +Below is a full architecture plan you can hand to an agent as the “master spec” for implementing the SBOM & provenance spine (image → SBOM → DSSE → in-toto/SLSA → transparency log → REST APIs), with idempotent APIs and air-gap readiness. + +--- + +## 1. Scope and Objectives + +**Goal:** Implement a deterministic, air-gap-ready “SBOM spine” that: + +* Converts OCI images into SBOMs (CycloneDX 1.6 and SPDX 3.0.1). +* Generates SLSA v1 provenance wrapped in in-toto Statements. +* Signs all artifacts with DSSE envelopes using pluggable crypto providers. +* Optionally publishes attestations to transparency logs (Rekor/local-Merkle/none). +* Exposes stable, idempotent APIs: + + * `POST /sbom/ingest` + * `POST /attest/verify` +* Avoids versioning by design; APIs are extended, not versioned; all mutations are idempotent keyed by content digests. + +**Out of scope (for this iteration):** + +* Full vulnerability scanning (delegated to Scanner service). +* Policy evaluation / lattice logic (delegated to Scanner/Graph engine). +* Vendor-facing proof-market ledger and trust economics (future module). + +--- + +## 2. High-Level Architecture + +### 2.1 Logical Components + +1. **StellaOps.SupplyChain.Core (Library)** + + * Shared types and utilities: + + * Domain models: SBOM, DSSE, in-toto Statement, SLSA predicates. + * Canonicalization & hashing utilities. + * DSSE sign/verify abstractions. + * Transparency log entry model & Merkle proof verification. + +2. **StellaOps.Sbomer.Engine (Library)** + + * Image → SBOM functionality: + + * Layer & manifest analysis. + * SBOM generation: CycloneDX, SPDX. + * Extraction of metadata (labels, env, history). + * Deterministic ordering & normalization. + +3. **StellaOps.Provenance.Engine (Library)** + + * Build provenance & in-toto: + + * In-toto Statement generator. + * SLSA v1 provenance predicate builder. + * Subject and material resolution from image metadata & SBOM. + +4. **StellaOps.Authority (Service/Library)** + + * Crypto & keys: + + * Key management abstraction (file, HSM, KMS, sovereign crypto). + * DSSE signing & verification with multiple key types. + * Trust roots, certificate chains, key policies. + +5. **StellaOps.LogBridge (Service/Library)** + + * Transparency log adapter: + + * Rekor backend. + * Local Merkle log backend (for air-gap). + * Null backend (no-op). + * Merkle proof validation. + +6. **StellaOps.SupplyChain.Api (Service)** + + * The SBOM spine HTTP API: + + * `POST /sbom/ingest` + * `POST /attest/verify` + * Optionally: `GET /sbom/{id}`, `GET /attest/{id}`, `GET /image/{digest}/summary`. + * Performs orchestrations: + + * SBOM/attestation parsing, canonicalization, hashing. + * Idempotency and persistence. + * Delegation to Authority and LogBridge. + +7. **CLI Tools (optional but recommended)** + + * `stella-extract`, `stella-sbomer`, `stella-sign`, `stella-provenance`, `stella-log`. + * Thin wrappers over the above libraries; usable offline and in CI pipelines. + +8. **Persistence Layer** + + * Primary DB: PostgreSQL (or other RDBMS). + * Optional object storage: S3/MinIO for large SBOM/attestation blobs. + * Tables: `images`, `sboms`, `attestations`, `signatures`, `log_entries`, `keys`. + +### 2.2 Deployment View (Kubernetes / Docker) + +```mermaid +flowchart LR + subgraph Node1[Cluster Node] + A[StellaOps.SupplyChain.Api (ASP.NET Core)] + B[StellaOps.Authority Service] + C[StellaOps.LogBridge Service] + end + + subgraph Node2[Worker Node] + D[Runner / CI / Air-gap host] + E[CLI Tools\nstella-extract/sbomer/sign/provenance/log] + end + + F[(PostgreSQL)] + G[(Object Storage\nS3/MinIO)] + H[(Local Merkle Log\nor Rekor)] + + A --> F + A --> G + A --> C + A --> B + C --> H + E --> A +``` + +* **Air-gap mode:** + + * Rekor backend disabled; LogBridge uses local Merkle log (`H`) or `null`. + * All components run within the offline network. +* **Online mode:** + + * LogBridge talks to external Rekor instance using outbound HTTPS only. + +--- + +## 3. Domain Model and Storage Design + +Use EF Core 9 with PostgreSQL in .NET 10. + +### 3.1 Core Entities + +1. **ImageArtifact** + + * `Id` (GUID/ULID, internal). + * `ImageDigest` (string; OCI digest; UNIQUE). + * `Registry` (string). + * `Repository` (string). + * `Tag` (string, nullable, since digest is canonical). + * `FirstSeenAt` (timestamp). + * `MetadataJson` (JSONB; manifest, labels, env). + +2. **Sbom** + + * `Id` (string, primary key = `SbomHash` or derived ULID). + * `ImageArtifactId` (FK). + * `Format` (enum: `CycloneDX_1_6`, `SPDX_3_0_1`). + * `ContentHash` (string; normalized JSON SHA-256; UNIQUE with `TenantId`). + * `StorageLocation` (inline JSONB or external object storage key). + * `CreatedAt`. + * `Origin` (enum: `Generated`, `Uploaded`, `ExternalVendor`). + * Unique constraint: `(TenantId, ContentHash)`. + +3. **Attestation** + + * `Id` (string, primary key = `AttestationHash` or derived ULID). + * `ImageArtifactId` (FK). + * `Type` (enum: `InTotoStatement_SLSA_v1`, `Other`). + * `PayloadHash` (hash of DSSE payload, before envelope). + * `DsseEnvelopeHash` (hash of full DSSE JSON). + * `StorageLocation` (inline JSONB or object storage). + * `CreatedAt`. + * `Issuer` (string; signer identity / certificate subject). + * Unique constraint: `(TenantId, DsseEnvelopeHash)`. + +4. **SignatureInfo** + + * `Id` (GUID/ULID). + * `AttestationId` (FK). + * `KeyId` (logical key identifier). + * `Algorithm` (enum; includes PQ & sovereign algs). + * `VerifiedAt`. + * `VerificationStatus` (enum: `Valid`, `Invalid`, `Unknown`). + * `DetailsJson` (JSONB; trust-chain, error reasons, etc.). + +5. **TransparencyLogEntry** + + * `Id` (GUID/ULID). + * `AttestationId` (FK). + * `Backend` (enum: `Rekor`, `LocalMerkle`). + * `LogIndex` (string). + * `LogId` (string). + * `InclusionProofJson` (JSONB). + * `RecordedAt`. + * Unique constraint: `(Backend, LogId, LogIndex)`. + +6. **KeyRecord** (optional if not reusing Authority’s DB) + + * `KeyId` (string, PK). + * `KeyType` (enum). + * `Usage` (enum: `Signing`, `Verification`, `Both`). + * `Status` (enum: `Active`, `Retired`, `Revoked`). + * `MetadataJson` (JSONB; KMS ARN, HSM slot, etc.). + +### 3.2 Idempotency Keys + +* SBOM: + + * `sbomHash = SHA256(canonicalJson(sbom))`. + * Uniqueness enforced by `(TenantId, sbomHash)` in DB. +* Attestation: + + * `attHash = SHA256(canonicalJson(dsse.payload))` or full envelope. + * Uniqueness enforced by `(TenantId, attHash)` in DB. +* Image: + + * `imageDigest` is globally unique (per OCI spec). + +--- + +## 4. Service-Level Architecture + +### 4.1 StellaOps.SupplyChain.Api (.NET 10, ASP.NET Core) + +**Responsibilities:** + +* Expose HTTP API for ingest / verify. +* Handle idempotency logic & persistence. +* Delegate cryptographic operations to Authority. +* Delegate transparency logging to LogBridge. +* Perform basic validation against schemas (SBOM, DSSE, in-toto, SLSA). + +**Key Endpoints:** + +1. `POST /sbom/ingest` + + * Request: + + * `imageDigest` (string). + * `sbom` (raw JSON). + * `format` (enum/string). + * Optional: `dsseSignature` or `dsseEnvelope`. + * Behavior: + + * Parse & validate SBOM structure. + * Canonicalize JSON, compute `sbomHash`. + * If `sbomHash` exists for `imageDigest` and tenant: + + * Return `200` with `{ status: "already_present", sbomId, sbomHash }`. + * Else: + + * Persist `Sbom` entity. + * Optionally verify DSSE signature via Authority. + * Return `201` with `{ status: "stored", sbomId, sbomHash }`. + +2. `POST /attest/verify` + + * Request: + + * `dsseEnvelope` (JSON). + * `expectedSubjects` (list of `{ name, digest }`). + * Behavior: + + * Canonicalize payload, compute `attHash`. + * Verify DSSE signature via Authority. + * Parse in-toto Statement; ensure `subject[].digest.sha256` matches `expectedSubjects`. + * Persist `Attestation` & `SignatureInfo`. + * If configured, call LogBridge to publish and store `TransparencyLogEntry`. + * If `attHash` already exists: + + * Return `200` with `status: "already_present"` and existing references. + * Else, return `201` with `verified:true`, plus log info when available. + +3. Optional read APIs: + + * `GET /sbom/by-image/{digest}` + * `GET /attest/by-image/{digest}` + * `GET /image/{digest}/summary` (SBOM + attestations + log status). + +### 4.2 StellaOps.Sbomer.Engine + +**Responsibilities:** + +* Given: + + * OCI image manifest & layers (from local tarball or remote registry). +* Produce: + + * CycloneDX 1.6 JSON. + * SPDX 3.0.1 JSON. + +**Design:** + +* Use layered analyzers: + + * `ILayerAnalyzer` for generic filesystem traversal. + * Language-specific analyzers (optional for SBOM detail): + + * `DotNetAnalyzer`, `NodeJsAnalyzer`, `PythonAnalyzer`, `JavaAnalyzer`, `PhpAnalyzer`, etc. +* Determinism: + + * Sort all lists (components, dependencies) by stable keys. + * Remove unstable fields (timestamps, machine IDs, ephemeral paths). + * Provide `Normalize()` method per format that returns canonical JSON. + +### 4.3 StellaOps.Provenance.Engine + +**Responsibilities:** + +* Build in-toto Statement with SLSA v1 predicate: + + * `subject` derived from image digest(s). + * `materials` from: + + * Git commit, tag, builder image, SBOM components if available. +* Ensure determinism: + + * Sort materials by URI + digest. + * Normalize nested maps. + +**Key APIs (internal library):** + +* `InTotoStatement BuildSlsaProvenance(ImageArtifact image, Sbom sbom, ProvenanceContext ctx)` +* `string ToCanonicalJson(InTotoStatement stmt)` + +### 4.4 StellaOps.Authority + +**Responsibilities:** + +* DSSE signing & verification. +* Key management abstraction. +* Policy enforcement (which keys/trust roots are allowed). + +**Interfaces:** + +* `ISigningProvider` + + * `Task SignAsync(byte[] payload, string payloadType, string keyId)` +* `IVerificationProvider` + + * `Task VerifyAsync(DsseEnvelope envelope, VerificationPolicy policy)` + +**Backends:** + +* File-based keys (PEM). +* HSM/KMS (AWS KMS, Azure Key Vault, on-prem HSM). +* Sovereign crypto providers (GOST, SMx, etc.). +* Optional PQ providers (Dilithium, Falcon). + +### 4.5 StellaOps.LogBridge + +**Responsibilities:** + +* Abstract interaction with transparency logs. + +**Interface:** + +* `ILogBackend` + + * `Task PutAsync(byte[] canonicalPayloadHash, DsseEnvelope env)` + * `Task VerifyInclusionAsync(LogEntryResult entry)` + +**Backends:** + +* `RekorBackend`: + + * Calls Rekor REST API with hashed payload. +* `LocalMerkleBackend`: + + * Maintains Merkle tree in local DB. + * Returns `logIndex`, `logId`, and inclusion proof. +* `NullBackend`: + + * Returns empty/no-op results. + +### 4.6 CLI Tools (Optional) + +Use the same libraries as the services: + +* `stella-extract`: + + * Input: image reference. + * Output: local tarball + manifest JSON. +* `stella-sbomer`: + + * Input: manifest & layers. + * Output: SBOM JSON. +* `stella-sign`: + + * Input: JSON file. + * Output: DSSE envelope. +* `stella-provenance`: + + * Input: image digest, build metadata. + * Output: signed in-toto/SLSA DSSE. +* `stella-log`: + + * Input: DSSE envelope. + * Output: log entry details. + +--- + +## 5. End-to-End Flows + +### 5.1 SBOM Ingest (Upload Path) + +```mermaid +sequenceDiagram + participant Client + participant API as SupplyChain.Api + participant Core as SupplyChain.Core + participant DB as PostgreSQL + + Client->>API: POST /sbom/ingest (imageDigest, sbom, format) + API->>Core: Validate & canonicalize SBOM + Core-->>API: sbomHash + API->>DB: SELECT Sbom WHERE sbomHash & imageDigest + DB-->>API: Not found + API->>DB: INSERT Sbom (sbomHash, imageDigest, content) + DB-->>API: ok + API-->>Client: 201 { status:"stored", sbomId, sbomHash } +``` + +Re-ingest of the same SBOM repeats steps up to SELECT, then returns `status:"already_present"` with `200`. + +### 5.2 Attestation Verify & Record + +```mermaid +sequenceDiagram + participant Client + participant API as SupplyChain.Api + participant Auth as Authority + participant Log as LogBridge + participant DB as PostgreSQL + + Client->>API: POST /attest/verify (dsseEnvelope, expectedSubjects) + API->>Auth: Verify DSSE (keys, policy) + Auth-->>API: VerificationResult(Valid/Invalid) + API->>API: Parse in-toto, check subjects vs expected + API->>DB: SELECT Attestation WHERE attHash + DB-->>API: Not found + API->>DB: INSERT Attestation + SignatureInfo + alt Logging enabled + API->>Log: PutAsync(attHash, envelope) + Log-->>API: LogEntryResult(logIndex, logId, proof) + API->>DB: INSERT TransparencyLogEntry + end + API-->>Client: 201 { verified:true, attestationId, logIndex?, inclusionProof? } +``` + +If attestation already exists, API returns `200` with `status:"already_present"`. + +--- + +## 6. Idempotency and Determinism Strategy + +1. **Canonicalization rules:** + + * Remove insignificant whitespace. + * Sort all object keys lexicographically. + * Sort arrays where order is not semantically meaningful (components, materials). + * Strip non-deterministic fields (timestamps, random IDs) where allowed. + +2. **Hashing:** + + * Always hash canonical JSON as UTF-8. + * Use SHA-256 for core IDs; allow crypto provider to also compute other digests if needed. + +3. **Persistence:** + + * Enforce uniqueness in DB via indices on: + + * `(TenantId, ContentHash)` for SBOMs. + * `(TenantId, AttHash)` for attestations. + * `(Backend, LogId, LogIndex)` for log entries. + * API behavior: + + * Existing row → `200` with `"already_present"`. + * New row → `201` with `"stored"`. + +4. **API design:** + + * No version numbers in path. + * Add fields over time; never break or repurpose existing ones. + * Use explicit capability discovery via `GET /meta/capabilities` if needed. + +--- + +## 7. Air-Gap Mode and Synchronization + +### 7.1 Air-Gap Mode + +* Configuration flag `Mode = Offline` on SupplyChain.Api. +* LogBridge backend: + + * Default to `LocalMerkle` or `Null`. +* Rekor-specific configuration disabled or absent. +* DB & Merkle log stored locally inside the secure network. + +### 7.2 Later Synchronization to Rekor (Optional Future Step) + +Not mandatory for first iteration, but prepare for: + +* Background job (Scheduler module) that: + + * Enumerates local `TransparencyLogEntry` not yet exported. + * Publishes hashed payloads to Rekor when network is available. + * Stores mapping between local log entries and remote Rekor entries. + +--- + +## 8. Security, Access Control, and Observability + +### 8.1 Security + +* mTLS between internal services (SupplyChain.Api, Authority, LogBridge). +* Authentication: + + * API keys/OIDC for clients. + * Per-tenant scoping; `TenantId` must be present in context. +* Authorization: + + * RBAC: which tenants/users can write/verify/only read. + +### 8.2 Crypto Policies + +* Policy object defines: + + * Allowed key types and algorithms. + * Trust roots (Fulcio, internal CA, sovereign PKI). + * Revocation checking strategy (CRL/OCSP, offline lists). +* Authority enforces policies; SupplyChain.Api only consumes `VerificationResult`. + +### 8.3 Observability + +* Logs: + + * Structured logs with correlation IDs; log imageDigest, sbomHash, attHash. +* Metrics: + + * SBOM ingest count, dedup hit rate. + * Attestation verify latency. + * Transparency log publish success/failure counts. +* Traces: + + * OpenTelemetry tracing across API → Authority → LogBridge. + +--- + +## 9. Implementation Plan (Epics & Work Packages) + +You can give this section directly to agents to split. + +### Epic 1: Core Domain & Canonicalization + +1. Define .NET 10 solution structure: + + * Projects: + + * `StellaOps.SupplyChain.Core` + * `StellaOps.Sbomer.Engine` + * `StellaOps.Provenance.Engine` + * `StellaOps.SupplyChain.Api` + * `StellaOps.Authority` (if not already present) + * `StellaOps.LogBridge` +2. Implement core domain models: + + * SBOM, DSSE, in-toto, SLSA v1. +3. Implement canonicalization & hashing utilities. +4. Unit tests: + + * Given semantically equivalent JSON, hashes must match. + * Negative tests where order changes but meaning does not. + +### Epic 2: Persistence Layer + +1. Design EF Core models for: + + * ImageArtifact, Sbom, Attestation, SignatureInfo, TransparencyLogEntry, KeyRecord. +2. Write migrations for PostgreSQL. +3. Implement repository interfaces for read/write. +4. Tests: + + * Unique constraints and idempotency behavior. + * Query performance for common access paths (by imageDigest). + +### Epic 3: SBOM Engine + +1. Implement minimal layer analysis: + + * Accepts local tarball or path (for now). +2. Implement CycloneDX 1.6 generator. +3. Implement SPDX 3.0.1 generator. +4. Deterministic normalization across formats. +5. Tests: + + * Golden files for images → SBOM output. + * Stability under repeated runs. + +### Epic 4: Provenance Engine + +1. Implement in-toto Statement model with SLSA v1 predicate. +2. Implement builder to map: + + * ImageDigest → subject. + * Build metadata → materials. +3. Deterministic canonicalization. +4. Tests: + + * Golden in-toto/SLSA statements for sample inputs. + * Subject matching logic. + +### Epic 5: Authority Integration + +1. Implement `ISigningProvider`, `IVerificationProvider` contracts. +2. Implement file-based key backend as default. +3. Implement DSSE wrapper: + + * `SignAsync(payload, payloadType, keyId)`. + * `VerifyAsync(envelope, policy)`. +4. Tests: + + * DSSE round-trip; invalid signature scenarios. + * Policy enforcement tests. + +### Epic 6: Transparency Log Bridge + +1. Implement `ILogBackend` interface. +2. Implement `LocalMerkleBackend`: + + * Simple Merkle tree with DB storage. +3. Implement `NullBackend`. +4. Define configuration model to select backend. +5. (Optional later) Implement `RekorBackend`. +6. Tests: + + * Stable Merkle root; inclusion proof verification. + +### Epic 7: SupplyChain.Api + +1. Implement `POST /sbom/ingest`: + + * Request/response DTOs. + * Integration with canonicalization, persistence, idempotency logic. +2. Implement `POST /attest/verify`: + + * End-to-end verification and persistence. + * Integration with Authority and LogBridge. +3. Optional read APIs. +4. Add input validation (JSON schema, basic constraints). +5. Integration tests: + + * Full flows for new and duplicate inputs. + * Error cases (invalid DSSE, subject mismatch). + +### Epic 8: CLI Tools + +1. Implement `stella-sbomer` (wraps Sbomer.Engine). +2. Implement `stella-provenance` (wraps Provenance.Engine + Authority). +3. Implement `stella-sign` and `stella-log`. +4. Provide clear help/usage and sample scripts. + +### Epic 9: Hardening, Air-Gap Profile, and Docs + +1. Configuration profiles: + + * `Offline` vs `Online`. + * Log backend selection. +2. Security hardening: + + * mTLS, authentication, authorization. +3. Observability: + + * Metrics, logs, traces wiring. +4. Documentation: + + * API reference. + * Sequence diagrams. + * Deployment recipes for: + + * Single-node air-gap. + * Clustered online deployment. + +--- + +If you want, next step I can: + +* Turn this into an AGENTS/TASKS/PROMPT set for your codex workers, or +* Produce concrete .NET 10 project skeletons (csproj layout, folder structure, and initial interfaces) for the core libraries and API service. diff --git a/docs/product-advisories/17-Nov-2026 - 1.md b/docs/product-advisories/17-Nov-2026 - 1.md new file mode 100644 index 000000000..adf1157c2 --- /dev/null +++ b/docs/product-advisories/17-Nov-2026 - 1.md @@ -0,0 +1,846 @@ + +Here’s a compact blueprint for bringing **stripped ELF binaries** into StellaOps’s **call‑graph + reachability scoring**—from raw bytes → neutral JSON → deterministic scoring. + +--- + +# Why this matters (quick) + +Even when symbols are missing, you can still (1) recover functions, (2) build a call graph, and (3) decide if a vulnerable function is *actually* reachable from the binary’s entrypoints. This feeds StellaOps’s deterministic scoring/lattice engine so VEX decisions are evidence‑backed, not guesswork. + +--- + +# High‑level pipeline + +1. **Ingest** + +* Accept: ELF (static/dynamic), PIE, musl/glibc, multiple arches (x86_64, aarch64, armhf, riscv64). +* Normalize: compute file hash set (SHA‑256, BLAKE3), note `PT_DYNAMIC`, `DT_NEEDED`, interpreter, RPATH/RUNPATH. + +2. **Symbolization (best‑effort)** + +* **If DWARF present**: read `.debug_*` (function names, inlines, CU boundaries, ranges). +* **If stripped**: + + * Use disassembler to **discover functions** (prolog patterns, xref‑to‑targets, thunk detection). + * Derive **synthetic names**: `sub_`, `plt_` (from dynamic symbol table if available), `extern@libc.so.6:memcpy`. + * Lift exported dynsyms and PLT stubs even when local symbols are removed. + * Recover **string‑referenced names** (e.g., Go/Python/C++ RTTI/Itanium mangling where present). + +3. **Disassembly & IR** + +* Disassemble to basic blocks; lift to a neutral IR (SSA‑like) sufficient for: + + * Call edges (direct `call`/`bl`). + * **Indirect calls** via GOT/IAT, vtables, function pointers (approximate with points‑to sets). + * Tailcalls, thunks, PLT interposition. + +4. **Call‑graph build** + +* Start from **entrypoints**: + + * ELF entry (`_start`), constructors (`.init_array`), exported API (public symbols), `main` (if recoverable). + * Optional: **entry‑trace** (cmd‑line + env + loader path) from container image to seed realistic roots. +* Build **CG** with: + + * Direct edges: precise. + * Indirect edges: conservative, with **evidence tags** (GOT target set, vtable class set, signature match). +* Record **inter‑module edges** to shared libs (soname + version) with relocation evidence. + +5. **Reachability scoring (deterministic)** + +* Input: list of vulnerable functions/paths (from CSAF/CVE KB) normalized to **function‑level identifiers** (soname!symbol or hash‑based if unnamed). +* Compute **reachability** from roots → target: + + * `REACHABLE_CONFIRMED` (path with only precise edges), + * `REACHABLE_POSSIBLE` (path contains conservative edges), + * `NOT_REACHABLE_FOUNDATION` (no path in current graph), + * Add **confidence** derived from edge evidence + relocation proof. +* Emit **proof trails** (the exact path: nodes, edges, evidence). + +6. **Neutral JSON intermediate (NJIF)** + +* Stored in cache; signed for deterministic replay. +* Consumed by StellaOps.Policy/Lattice to merge with VEX. + +--- + +# Neutral JSON Intermediate Format (NJIF) + +```json +{ + "artifact": { + "path": "/work/bin/app", + "hashes": {"sha256": "…", "blake3": "…"}, + "arch": "x86_64", + "elf": { + "type": "ET_DYN", + "interpreter": "/lib64/ld-linux-x86-64.so.2", + "needed": ["libc.so.6", "libssl.so.3"], + "rpath": [], + "runpath": [] + } + }, + "symbols": { + "exported": [ + {"id": "libc.so.6!memcpy", "kind": "dynsym", "addr": "0x0", "plt": true} + ], + "functions": [ + {"id": "sub_401000", "addr": "0x401000", "size": 112, "name_hint": null, "from": "disasm"}, + {"id": "main", "addr": "0x4023d0", "size": 348, "from": "dwarf|heuristic"} + ] + }, + "cfg": [ + {"func": "main", "blocks": [ + {"b": "0x4023d0", "succ": ["0x402415"], "calls": [{"type": "direct", "target": "sub_401000"}]}, + {"b": "0x402415", "succ": ["0x402440"], "calls": [{"type": "plt", "target": "libc.so.6!memcpy"}]} + ]} + ], + "cg": { + "nodes": [ + {"id": "main", "evidence": ["dwarf|heuristic"]}, + {"id": "sub_401000"}, + {"id": "libc.so.6!memcpy", "external": true, "lib": "libc.so.6"} + ], + "edges": [ + {"from": "main", "to": "sub_401000", "kind": "direct"}, + {"from": "main", "to": "libc.so.6!memcpy", "kind": "plt", "evidence": ["reloc@GOT"]} + ], + "roots": ["_start", "init_array[]", "main"] + }, + "reachability": [ + { + "target": "libssl.so.3!SSL_free", + "status": "NOT_REACHABLE_FOUNDATION", + "path": [] + }, + { + "target": "libc.so.6!memcpy", + "status": "REACHABLE_CONFIRMED", + "path": ["main", "libc.so.6!memcpy"], + "confidence": 0.98, + "evidence": ["plt", "dynsym", "reloc"] + } + ], + "provenance": { + "toolchain": { + "disasm": "ghidra_headless|radare2|llvm-mca", + "version": "…" + }, + "scan_manifest_hash": "…", + "timestamp_utc": "2025-11-16T00:00:00Z" + } +} +``` + +--- + +# Practical extractors (headless/CLI) + +* **DWARF**: `llvm-dwarfdump`/`eu-readelf` for quick CU/function ranges; fall back to the disassembler. +* **Disassembly/CFG/CG** (choose one or more; wrap with a stable adapter): + + * **Ghidra Headless API**: recover functions, basic blocks, references, PLT/GOT, vtables; export via a custom headless script to NJIF. + * **radare2 / rizin**: `aaa`, `agCd`, `aflj`, `agj` to export functions/graphs as JSON. + * **Binary Ninja headless** (if license permits) for cleaner IL and indirect‑call modeling. + * **angr** for path‑sensitive refinement on tricky indirect calls (optional, gated by budget). + +**Adapter principle:** All tools output a **small, consistent NJIF** so the scoring engine and lattice logic never depend on any single RE tool. + +--- + +# Indirect call modeling (concise rules) + +* **PLT/GOT**: edge from caller → `soname!symbol` with evidence: `plt`, `reloc@GOT`. +* **Function pointers**: if a store to a pointer is found and targets a known function set `{f1…fk}`, add edges with `kind: "indirect"`, `evidence: ["xref-store", "sig-compatible"]`. +* **Virtual calls / vtables**: class‑method set from RTTI/vtable scans; mark edges `evidence: ["vtable-match"]`. +* **Tailcalls**: treat as edges, not fallthrough. + +Each conservative step lowers **confidence**, but keeps determinism: the rules and their hashes are in the scan manifest. + +--- + +# Deterministic scoring (plug into Stella’s lattice) + +* **Inputs**: NJIF, CVE→function mapping (`soname!symbol` or function hash), policy knobs. +* **States**: `{NOT_OBSERVED < POSSIBLE < REACHABLE_CONFIRMED}` with **monotone** merge (never oscillates). +* **Confidence**: product of edge evidences (configurable weights): `direct=1.0, plt=0.98, vtable=0.85, funcptr=0.7`. +* **Output**: OpenVEX/CSAF annotations + human proof path; signed with DSSE to preserve replayability. + +--- + +# Minimal Ghidra headless skeleton (exporter idea) + +```bash +analyzeHeadless /work/gh_proj MyProj -import app -scriptPath scripts \ + -postScript ExportNjif.java /out/app.njif.json +``` + +```java +// ExportNjif.java (outline) +public class ExportNjif extends GhidraScript { + public void run() throws Exception { + var fns = getFunctions(true); + // collect functions, blocks, calls, externs/PLT + // map non‑named functions to sub_ + // detect PLT thunks → dynsym names + // write NJIF JSON deterministically (sorted keys, stable ordering) + } +} +``` + +--- + +# Integration points in StellaOps + +* **Scanner.Analyzers.Binary.Elf** + + * `ElfNormalizer` → hashes, dynamic deps. + * `Symbolizer` → DWARF reader + HeuristicDisasm (via tool adapter). + * `CgBuilder` → NJIF builder/merger (multi‑module). + * `ReachabilityEngine` → path search + confidence math. + * `Emitter` → NJIF cache + VEX/CSAF notes. + +* **Scheduler**: memoize by `(hashes, toolchain_version, ruleset_hash)` to ensure replayable results. + +* **Authority**: sign NJIF + scoring outputs; store manifests (feeds, rule weights, tool versions). + +--- + +# Test fixtures (suggested) + +* Tiny ELF zoo: statically linked, PIE, stripped/non‑stripped, C++ with vtables, musl vs glibc. +* Known CVE libs (e.g., `libssl`, `zlib`) with versioned symbols to validate soname!symbol mapping. +* Synthetic binaries with function‑pointer tables to validate conservative edges. + +--- + +If you want, I can generate: + +* A ready‑to‑run **Ghidra headless exporter** (Java) that writes NJIF exactly like above. +* A small **.NET parser** that ingests NJIF and emits StellaOps reachability + OpenVEX notes. +Below is a full architecture plan for implementing **stripped-ELF binary reachability** (call graph + NJIF + deterministic scoring, with a hook for patch-oracles) inside **StellaOps**. + +I will assume .NET 10, existing microservice split (Scanner.WebService, Scanner.Worker, Concelier, Excitior, Authority, Scheduler, Sbomer, Signals), and your standing rule: **all lattice logic runs in Scanner.WebService**. + +--- + +## 1. Scope, Objectives, Non-Goals + +### 1.1 Objectives + +1. **Recover function-level call graphs from ELF binaries**, including **stripped** ones: + +* Support ET_EXEC / ET_DYN / PIE, static & dynamic linking. +* Support at least **x86_64, aarch64** in v1, later armhf, riscv64. + +2. **Produce a neutral, deterministic JSON representation (NJIF)**: + +* Tool-agnostic: can be generated from Ghidra, radare2/rizin, Binary Ninja, angr, etc. +* Stable identifiers and schema so downstream services don’t depend on a specific RE engine. + +3. **Compute function-level reachability for vulnerabilities**: + +* Given CVE → `soname!symbol` (and later function-hash) mappings from Concelier, +* Decide `REACHABLE_CONFIRMED` / `REACHABLE_POSSIBLE` / `NOT_REACHABLE_FOUNDATION` with evidence and confidence. + +4. **Integrate with StellaOps lattice and VEX outputs**: + +* Lattice logic runs in **Scanner.WebService**. +* Results flow into Excitior (VEX) and Sbomer (SBOM annotations), preserving provenance. + +5. **Enable deterministic replay**: + +* Every analysis run is tied to a **Scan Manifest**: tool versions, ruleset hashes, policy hashes, container image digests. + +### 1.2 Non-Goals (v1) + +* No dynamic runtime probes (EventPipe/JFR) in this phase. +* No full decompilation; we only need enough IR for calls/edges. +* No aggressive path-sensitive analysis (symbolic execution) in v1; that can be a v2 enhancement. + +--- + +## 2. High-Level System Architecture + +### 2.1 Components + +* **Scanner.WebService (existing)** + + * REST/gRPC API for scans. + * Orchestrates analysis jobs via Scheduler. + * Hosts **Lattice & Reachability Engine** for all artifact types. + * Reads NJIF results, merges with Concelier function mappings and policies. + +* **Scanner.Worker (existing, extended)** + + * Executes **Binary Analyzer Pipelines**. + * Invokes RE tools (Ghidra, rizin, etc.) in dedicated containers. + * Produces NJIF and persists it. + +* **Binary Tools Containers (new)** + + * `stellaops-tools-ghidra:` + * `stellaops-tools-rizin:` + * Optionally `stellaops-tools-angr` for advanced passes. + * Pinned versions, no network access (for determinism & air-gap). + +* **Storage & Metadata** + + * **DB (PostgreSQL)**: scan records, NJIF metadata, reachability summaries. + * **Object store** (MinIO/S3/Filesystem): NJIF JSON blobs, tool logs. + * **Authority**: DSSE signatures for Scan Manifest, NJIF, and reachability outputs. + +* **Concelier** + + * Provides **CVE → component → function symbol/hashes** resolution. + * Exposes “Link-Not-Merge” graph of advisory, component, and function nodes. + +* **Excitior (VEX)** + + * Consumes Scanner.WebService reachability states. + * Emits OpenVEX/CSAF with properly justified statuses. + +* **UnknownsRegistry (future)** + + * Receives unresolvable call edges / ambiguous functions from the analyzer, + * Feeds them into “adaptive security” workflows. + +### 2.2 End-to-End Flow (Binary / Image Scan) + +1. Client requests scan (binary or container image) via **Scanner.WebService**. +2. WebService: + + * Extracts binaries from OCI layers (if scanning image), + * Registers **Scan Manifest**, + * Submits a job to Scheduler (queue: `binary-elfflow`). +3. Scanner.Worker dequeues the job: + + * Detects ELF binaries, + * Runs **Binary Analyzer Pipeline** for each unique binary hash. +4. Worker uses tools containers: + + * Ghidra/rizin → CFG, function discovery, call graph, + * Converts to **NJIF**. +5. Worker persists NJIF + metadata; marks analysis complete. +6. Scanner.WebService picks up NJIF: + + * Fetches advisory function mappings from Concelier, + * Runs **Reachability & Lattice scoring**, + * Updates scan results and triggers Excitior / Sbomer. + +All steps are deterministic given: + +* Input artifact, +* Tool container digests, +* Ruleset/policy versions. + +--- + +## 3. Binary Analyzer Subsystem (Scanner.Worker) + +Introduce a dedicated module: + +* `StellaOps.Scanner.Analyzers.Binary.Elf` + +### 3.1 Internal Layers + +1. **ElfDetector** + + * Inspects files in a scan: + + * Magic `0x7f 'E' 'L' 'F'`, + * Confirms architecture via ELF header. + * Produces `BinaryArtifact` records with: + + * `hashes` (SHA-256, BLAKE3), + * `path` in container, + * `arch`, `endianness`. + +2. **ElfNormalizer** + + * Uses a lightweight library (e.g., ElfSharp) to extract: + + * `ElfType` (ET_EXEC, ET_DYN), + * interpreter (`PT_INTERP`), + * `DT_NEEDED` list, + * RPATH/RUNPATH, + * presence/absence of DWARF sections. + * Emits a normalized `ElfMetadata` DTO. + +3. **Symbolization Layer** + + * Sub-components: + + * `DwarfSymbolReader`: if DWARF present, read CU, function ranges, names, inlines. + * `DynsymReader`: parse `.dynsym`, `.plt`, exported symbols. + * `HeuristicFunctionFinder`: + + * For stripped binaries: + + * Use disassembler xrefs, prolog patterns, return instructions, call-targets. + * Recognize PLT thunks → `soname!symbol`. + * Consolidates into `FunctionSymbol` entities: + + * `id` (e.g., `main`, `sub_401000`, `libc.so.6!memcpy`), + * `addr`, `size`, `is_external`, `from` (`dwarf`, `dynsym`, `heuristic`). + +4. **Disassembly & IR Layer** + + * Abstraction: `IDisassemblyAdapter`: + + * `Task AnalyzeAsync(BinaryArtifact, ElfMetadata, ScanManifest)` + * Implementations: + + * `GhidraDisassemblyAdapter`: + + * Invokes headless Ghidra in container, + * Receives machine-readable JSON (script-produced), + * Extracts functions, basic blocks, calls, GOT/PLT info, vtables. + * `RizinDisassemblyAdapter` (backup/fallback). + * Produces: + + * `BasicBlock` objects, + * `Instruction` metadata where needed for calls, + * `CallSite` records (direct, PLT, indirect). + +5. **Call-Graph Builder** + + * Consumes `FunctionSymbol` + `CallSite` sets. + * Identifies **roots**: + + * `_start`, `.init_array` entries, + * `main` (if present), + * Exported API functions for shared libs. + * Creates `CallGraph`: + + * Nodes: functions (`FunctionNode`), + * Edges: `CallEdge` with: + + * `kind`: `direct`, `plt`, `indirect-funcptr`, `indirect-vtable`, `tailcall`, + * `evidence`: tags like `["reloc@GOT", "sig-match", "vtable-class"]`. + +6. **Evidence & Confidence Annotator** + + * For each edge, computes a **local confidence**: + + * `direct`: 1.0 + * `plt`: 0.98 + * `indirect-funcptr`: 0.7 + * `indirect-vtable`: 0.85 + * For each path later, Scanner.WebService composes these. + +7. **NJIF Serializer** + + * Transforms domain objects into **NJIF JSON**: + + * Sorted keys, stable ordering for determinism. + * Writes: + + * `artifact`, `elf`, `symbols`, `cfg`, `cg`, and partial `reachability: []` (filled by WebService). + * Stores in object store, returns location + hash to DB. + +8. **Unknowns Reporting** + + * Any unresolved: + + * Indirect call with empty target set, + * Function region not mapped to symbol, + * Logged as `UnknownEvidence` records and optionally published to **UnknownsRegistry** stream. + +--- + +## 4. NJIF Data Model (Neutral JSON Intermediate Format) + +Define a stable schema with a top-level `njif_schema_version` field. + +### 4.1 Top-Level Shape + +```json +{ + "njif_schema_version": "1.0.0", + "artifact": { ... }, + "symbols": { ... }, + "cfg": [ ... ], + "cg": { ... }, + "reachability": [ ... ], + "provenance": { ... } +} +``` + +### 4.2 Key Sections + +1. `artifact` + + * `path`, `hashes`, `arch`, `elf.type`, `interpreter`, `needed`, `rpath`, `runpath`. + +2. `symbols` + + * `exported`: external/dynamic symbols, especially PLT: + + * `id`, `kind`, `plt`, `lib`. + * `functions`: + + * `id` (synthetic or real name), + * `addr`, `size`, `from` (source of naming info), + * `name_hint` (optional). + +3. `cfg` + + * Per-function basic block CFG plus call sites: + + * Blocks with `succ`, `calls` entries. + * Sufficient for future static checks, not full IR. + +4. `cg` + + * `nodes`: function nodes with evidence tags. + * `edges`: call edges with: + + * `from`, `to`, `kind`, `evidence`. + * `roots`: entrypoints for reachability algorithms. + +5. `reachability` + + * Initially empty from Worker. + * Populated in Scanner.WebService as: + +```json +{ + "target": "libssl.so.3!SSL_free", + "status": "REACHABLE_CONFIRMED", + "path": ["_start", "main", "libssl.so.3!SSL_free"], + "confidence": 0.93, + "evidence": ["plt", "dynsym", "reloc"] +} +``` + +6. `provenance` + + * `toolchain`: + + * `disasm`: `"ghidra_headless:10.4"`, etc. + * `scan_manifest_hash`, + * `timestamp_utc`. + +### 4.3 Persisting NJIF + +* Object store (versioned path): + + * `njif/{sha256}/njif-v1.json` +* DB table `binary_njif`: + + * `binary_hash`, `njif_hash`, `schema_version`, `toolchain_digest`, `scan_manifest_id`. + +--- + +## 5. Reachability & Lattice Integration (Scanner.WebService) + +### 5.1 Inputs + +* **NJIF** for each binary (possibly multiple binaries per container). +* Concelier’s **CVE → (component, function)** resolution: + + * `component_id` → `soname!symbol` sets, and where available, function hashes. +* Scanner’s existing **lattice policies**: + + * States: e.g. `NOT_OBSERVED < POSSIBLE < REACHABLE_CONFIRMED`. + * Merge rules are monotone. + +### 5.2 Reachability Engine + +New service module: + +* `StellaOps.Scanner.Domain.Reachability` + + * `INjifRepository` (reads NJIF JSON), + * `IFunctionMappingResolver` (Concelier adapter), + * `IReachabilityCalculator`. + +Algorithm per target function: + +1. Resolve vulnerable function(s): + + * From Concelier: `soname!symbol` and/or `func_hash`. + * Map to NJIF `symbols.exported` or `symbols.functions`. + +2. For each binary: + + * Use `cg.roots` as entry set. + * BFS/DFS along edges until: + + * Reaching target node(s), + * Or graph fully explored. + +3. For each successful path: + + * Collect edges’ `confidence` weights, compute path confidence: + + * e.g., product of edge confidences or a log/additive scheme. + +4. Aggregate result: + + * If ≥ 1 path with only `direct/plt` edges: + + * `status = REACHABLE_CONFIRMED`. + * Else if only paths with indirect edges: + + * `status = REACHABLE_POSSIBLE`. + * Else: + + * `status = NOT_REACHABLE_FOUNDATION`. + +5. Emit `reachability` entry back into NJIF (or as separate DB table) and into scan result graph. + +### 5.3 Lattice & VEX + +* Lattice computation is done per `(CVE, component, binary)` triple: + + * Input: reachability status + other signals. +* Resulting state is: + + * Exposed to **Excitior** as a set of **evidence-annotated VEX facts**. +* Excitior translates: + + * `NOT_REACHABLE_FOUNDATION` → likely `not_affected` with justification “code_not_reachable”. + * `REACHABLE_CONFIRMED` → `affected` or “present_and_exploitable” (depending on overall policy). + +--- + +## 6. Patch-Oracle Extension (Advanced, but Architected Now) + +While not strictly required for v1, we should reserve architecture hooks. + +### 6.1 Concept + +* Given: + + * A **vulnerable** library build (or binary), + * A **patched** build. +* Run analyzers on both; produce NJIF for each. +* Compare call graphs & function bodies (e.g., hash of normalized bytes): + + * Identify **changed functions** and potentially changed code regions. +* Concelier links those function IDs to specific CVEs (via vendor patch metadata). +* These become authoritative “patched function sets” (the **patch oracle**). + +### 6.2 Integration Points + +Add a module: + +* `StellaOps.Scanner.Analysis.PatchOracle` + + * Input: pair of artifact hashes (old, new) + NJIF. + * Output: list of `FunctionPatchRecord`: + + * `function_id`, `binary_hash_old`, `binary_hash_new`, `change_kind` (`added`, `modified`, `deleted`). + +Concelier: + +* Ingests `FunctionPatchRecord` via internal API and updates advisory graph: + + * CVE → function set derived from real patch. +* Reachability Engine: + + * Uses patch-derived function sets instead of or in addition to symbol mapping from vendor docs. + +--- + +## 7. Persistence, Determinism, Caching + +### 7.1 Scan Manifest + +For every scan job, create: + +* `scan_manifest`: + + * Input artifact hashes, + * List of binaries, + * Tool container digests (Ghidra, rizin, etc.), + * Ruleset/policy/lattice hashes, + * Time, user, and config flags. + +Authority signs this manifest with DSSE. + +### 7.2 Binary Analysis Cache + +Key: `(binary_hash, arch, toolchain_digest, njif_schema_version)`. + +* If present: + + * Skip re-running Ghidra/rizin; reuse NJIF. +* If absent: + + * Run analysis, then cache NJIF. + +This provides deterministic replay and prevents re-analysis across scans and across customers (if allowed by tenancy model). + +--- + +## 8. APIs & Integration Contracts + +### 8.1 Scanner.WebService External API (REST) + +1. `POST /api/scans/images` + + * Existing; extended to flag: `includeBinaryReachability: true`. +2. `POST /api/scans/binaries` + + * Upload a standalone ELF; returns `scan_id`. +3. `GET /api/scans/{scanId}/reachability` + + * Returns list of `(cve_id, component, binary_path, function_id, status, confidence, path)`. + +No path versioning; idempotent and additive (new fields appear, old ones remain valid). + +### 8.2 Internal APIs + +* **Worker ↔ Object Store**: + + * `PUT /binary-njif/{sha256}/njif-v1.json`. + +* **WebService ↔ Worker (via Scheduler)**: + + * Job payload includes: + + * `scan_manifest_id`, + * `binary_hashes`, + * `analysis_profile` (`default`, `deep`). + +* **WebService ↔ Concelier**: + + * `POST /internal/functions/resolve`: + + * Input: `(cve_id, component_ids[])`, + * Output: `soname!symbol[]`, optional `func_hash[]`. + +* **WebService ↔ Excitior**: + + * Existing VEX ingestion extended with **reachability evidence** fields. + +--- + +## 9. Observability, Security, Resource Model + +### 9.1 Observability + +* **Metrics**: + + * Analysis duration per binary, + * NJIF size, + * Cache hit ratio, + * Reachability evaluation time per CVE. + +* **Logs**: + + * Ghidra/rizin container logs stored alongside NJIF, + * Unknowns logs for unresolved call targets. + +* **Tracing**: + + * Each scan/analysis annotated with `scan_manifest_id` to allow end-to-end trace. + +### 9.2 Security + +* Tools containers: + + * No outbound network. + * Limited to read-only artifact mount + write-only result mount. +* Binary content: + + * Treated as confidential; stored encrypted at rest if your global policy requires it. +* DSSE: + + * Authority signs: + + * Scan Manifest, + * NJIF blob hash, + * Reachability summary. + * Enables “Proof-of-Integrity Graph” linkage later. + +### 9.3 Resource Model + +* ELF analysis can be heavy; design for: + + * Separate **worker queue** and autoscaling group for binary analysis. + * Configurable max concurrency and per-job CPU/memory limits. +* Deep analysis (indirect calls, vtables) can be toggled via `analysis_profile`. + +--- + +## 10. Implementation Roadmap + +A pragmatic, staged plan: + +### Phase 0 – Foundations (1–2 sprints) + +* Create `StellaOps.Scanner.Analyzers.Binary.Elf` project. +* Implement: + + * `ElfDetector`, `ElfNormalizer`. + * DB tables: `binary_artifacts`, `binary_njif`. +* Integrate with Scheduler and Worker pipeline. + +### Phase 1 – Non-stripped ELF + NJIF v1 (2–3 sprints) + +* Implement **DWARF + dynsym symbolization**. +* Implement **GhidraDisassemblyAdapter** for x86_64. +* Build **CallGraphBuilder** (direct + PLT calls). +* Implement NJIF serializer v1; store in object store. +* Basic reachability engine in WebService: + + * Only direct and PLT edges, + * Only for DWARF-named functions. +* Integrate with Concelier function mapping via `soname!symbol`. + +### Phase 2 – Stripped ELF Support (2–3 sprints) + +* Implement `HeuristicFunctionFinder` for function discovery in stripped binaries. +* Extend Ghidra script to mark PLT/GOT, vtables, function pointers. +* Call graph: add: + + * `indirect-funcptr`, `indirect-vtable`, `tailcall` edges. +* Evidence tagging and local confidence scoring. +* Extend reachability engine to: + + * Distinguish `REACHABLE_CONFIRMED` vs `REACHABLE_POSSIBLE`. + +### Phase 3 – Multi-Arch & Performance (2–3 sprints) + +* Add support for **aarch64** (Ghidra language, appropriate calling conventions). +* Optimize: + + * Binary analysis cache, + * Tool container lifecycle, + * Concurrent analysis. +* Add Unknowns reporting and hookup to UnknownsRegistry (if already implemented). + +### Phase 4 – Patch-Oracle Pilot (2–3 sprints) + +* Implement PatchOracle module: + + * Compare old/new NJIFs, + * Detect changed functions. +* Integrate with Concelier’s advisory graph. +* Start validating against curated CVE/patch datasets. + +### Phase 5 – Hardening & Documentation + +* Golden fixtures: + + * Small ELF zoo (stripped/non-stripped, PIE, static, C++, vtables). + * Known vulnerable libs (e.g., OpenSSL, zlib) to confirm correct function mapping. +* Add CLI/demo in `StellaOps.Scanner.Cli`: + + * `stellaops scan-binary --file app --show-reachability`. +* Customer-facing and internal docs: + + * NJIF schema, + * API usage, + * Limitations and interpretation guidelines. + +--- + +If you want, next step I can do is take this plan and: + +* Break it into **epics / tickets** (SCAN-BINARY-xxx) with clear DoD per phase, or +* Draft the **Ghidra headless Java script** and the **.NET NJIF model classes** so your agents can plug them straight into the Scanner repo. diff --git a/docs/product-advisories/18-Nov-2026 - 1 copy 2.md b/docs/product-advisories/18-Nov-2026 - 1 copy 2.md new file mode 100644 index 000000000..fe0ccd594 --- /dev/null +++ b/docs/product-advisories/18-Nov-2026 - 1 copy 2.md @@ -0,0 +1,989 @@ +Vlad, here’s a concrete, **pure‑C#** blueprint to build a multi‑format binary analyzer (Mach‑O, ELF, PE) that produces **call graphs + reachability**, with **no external tools**. Where needed, I point to permissively‑licensed code you can **port** (copy) from other ecosystems. + +--- + +## 0) Targets & non‑negotiables + +* **Formats:** Mach‑O (inc. LC_DYLD_INFO / LC_DYLD_CHAINED_FIXUPS), ELF (SysV gABI), PE/COFF +* **Architectures:** x86‑64 (and x86), AArch64 (ARM64) +* **Outputs:** JSON with **purls** per module + function‑level call graph & reachability +* **No tool reuse:** Only pure C# libraries or code **ported** from permissive sources + +--- + +## 1) Parsing the containers (pure C#) + +**Pick one C# reader per format, keeping licenses permissive:** + +* **ELF & Mach‑O:** `ELFSharp` (pure managed C#; ELF + Mach‑O reading). MIT/X11 license. ([GitHub][1]) +* **ELF & PE (+ DWARF v4):** `LibObjectFile` (C#, BSD‑2). Good ELF relocations (i386, x86_64, ARM, AArch64), PE directories, DWARF sections. Use it as your **common object model** for ELF+PE, then add a Mach‑O adapter. ([GitHub][2]) +* **PE (optional alternative):** `PeNet` (pure C#, broad PE directories, imp/exp, TLS, certs). MIT. Useful if you want a second implementation for cross‑checks. ([GitHub][3]) + +> Why two libs? `LibObjectFile` gives you DWARF and clean models for ELF/PE; `ELFSharp` covers Mach‑O today (and ELF as a fallback). You control the code paths. + +**Spec references you’ll implement against** (for correctness of your readers & link‑time semantics): + +* **ELF (gABI, AMD64 supplement):** dynamic section, PLT/GOT, `R_X86_64_JUMP_SLOT` semantics (eager vs lazy). ([refspecs.linuxbase.org][4]) +* **PE/COFF:** imports/exports/IAT, delay‑load, TLS. ([Microsoft Learn][5]) +* **Mach‑O:** file layout, load commands (`LC_SYMTAB`, `LC_DYSYMTAB`, `LC_FUNCTION_STARTS`, `LC_DYLD_INFO(_ONLY)`), and the modern `LC_DYLD_CHAINED_FIXUPS`. ([leopard-adc.pepas.com][6]) + +--- + +## 2) Mach‑O: what you must **port** (byte‑for‑byte compatible) + +Apple moved from traditional dyld bind opcodes to **chained fixups** on macOS 12/iOS 15+; you need both: + +* **Dyld bind opcodes** (`LC_DYLD_INFO(_ONLY)`): parse the BIND/LAZY_BIND streams (tuples of ``). Port minimal logic from **LLVM** or **LIEF** (both Apache‑2.0‑compatible) into C#. ([LIEF][7]) +* **Chained fixups** (`LC_DYLD_CHAINED_FIXUPS`): port `dyld_chained_fixups_header` structs & chain walking from LLVM’s `MachO.h` or Apple’s dyld headers. This restores imports/rebases without running dyld. ([LLVM][8]) +* **Function discovery hint:** read `LC_FUNCTION_STARTS` (ULEB128 deltas) to seed function boundaries—very helpful on stripped binaries. ([Stack Overflow][9]) +* **Stubs mapping:** resolve `__TEXT,__stubs` ↔ `__DATA,__la_symbol_ptr` via the **indirect symbol table**; conceptually identical to ELF’s PLT/GOT. ([MaskRay][10]) + +> If you prefer an in‑C# base for Mach‑O manipulation, **Melanzana.MachO** exists (MIT) and has been used by .NET folks for Mach‑O/Code Signing/obj writing; you can mine its approach for load‑command modeling. ([GitHub][11]) + +--- + +## 3) Disassembly (pure C#, multi‑arch) + +* **x86/x64:** `iced` (C# decoder/disassembler/encoder; MIT; fast & complete). ([GitHub][12]) +* **AArch64/ARM64:** two options that keep you pure‑C#: + + * **Disarm** (pure C# ARM64 disassembler; MIT). Good starting point to decode & get branch/call kinds. ([GitHub][13]) + * **Port from Ryujinx ARMeilleure** (ARMv8 decoder/JIT in C#, MIT). You can lift only the **decoder** pieces you need. ([Gitee][14]) +* **x86 fallback:** `SharpDisasm` (udis86 port in C#; BSD‑2). Older than iced; keep as a reference. ([GitHub][15]) + +--- + +## 4) Call graph recovery (static) + +**4.1 Function seeds** + +* From symbols (`.dynsym`/`LC_SYMTAB`/PE exports) +* From **LC_FUNCTION_STARTS** (Mach‑O) for stripped code ([Stack Overflow][9]) +* From entrypoints (`_start`/`main` or PE AddressOfEntryPoint) +* From exception/unwind tables & DWARF (when present)—`LibObjectFile` already models DWARF v4. ([GitHub][2]) + +**4.2 CFG & interprocedural calls** + +* **Decode** with iced/Disarm from each seed; form **basic blocks** by following control‑flow until terminators (ret/jmp/call). +* **Direct calls:** immediate targets become edges (PC‑relative fixups where needed). +* **Imported calls:** + + * **ELF:** calls to PLT stubs → resolve via `.rela.plt` & `R_*_JUMP_SLOT` to symbol names (link‑time target). ([cs61.seas.harvard.edu][16]) + * **PE:** calls through the **IAT** → resolve via `IMAGE_IMPORT_DESCRIPTOR` / thunk tables. ([Microsoft Learn][5]) + * **Mach‑O:** calls to `__stubs` use **indirect symbol table** + `__la_symbol_ptr` (or chained fixups) → map to dylib/symbol. ([reinterpretcast.com][17]) +* **Indirect calls within the binary:** heuristics only (function pointer tables, vtables, small constant pools). Keep them labeled **“indirect‑unresolved”** unless a heuristic yields a concrete target. + +**4.3 Cross‑binary graph** + +* Build module‑level edges by simulating the platform’s loader: + + * **ELF:** honor `DT_NEEDED`, `DT_RPATH/RUNPATH`, versioning (`.gnu.version*`) to pick the definer of an imported symbol. gABI rules apply. ([refspecs.linuxbase.org][4]) + * **PE:** pick DLL from the import descriptors. ([Microsoft Learn][5]) + * **Mach‑O:** `LC_LOAD_DYLIB` + dyld binding / chained fixups determine the provider image. ([LIEF][7]) + +--- + +## 5) Reachability analysis + +Represent the **call graph** using a .NET graph lib (or a simple adjacency set). I suggest: + +* **QuikGraph** (successor of QuickGraph; MIT) for algorithms (DFS/BFS, SCCs). Use it to compute reachability from chosen roots (entrypoint(s), exported APIs, or “sinks”). ([GitHub][18]) + +You can visualize with **MSAGL** (MIT) when you need layouts, but your core output is JSON. ([GitHub][19]) + +--- + +## 6) Symbol demangling (nice‑to‑have, pure C#) + +* **Itanium (ELF/Mach‑O):** Either port LLVM’s Itanium demangler or use a C# lib like **CxxDemangler** (a C# rewrite of `cpp_demangle`). ([LLVM][20]) +* **MSVC (PE):** Port LLVM’s `MicrosoftDemangle.cpp` (Apache‑2.0 with LLVM exception) to C#. ([LLVM][21]) + +--- + +## 7) JSON output (with purls) + +Use a stable schema (example) to feed SBOM/vuln matching downstream: + +```json +{ + "modules": [ + { + "purl": "pkg:deb/ubuntu/openssl@1.1.1w-0ubuntu1?arch=amd64", + "format": "ELF", + "arch": "x86_64", + "path": "/usr/lib/x86_64-linux-gnu/libssl.so.1.1", + "exports": ["SSL_read", "SSL_write"], + "imports": ["BIO_new", "EVP_CipherInit_ex"], + "functions": [{"name":"SSL_do_handshake","va":"0x401020","size":512,"demangled": "..."}] + } + ], + "graph": { + "nodes": [ + {"id":"bin:main@0x401000","module": "pkg:generic/myapp@1.0.0"}, + {"id":"lib:SSL_read","module":"pkg:deb/ubuntu/openssl@1.1.1w-0ubuntu1?arch=amd64"} + ], + "edges": [ + {"src":"bin:main@0x401000","dst":"lib:SSL_read","kind":"import_call","evidence":"ELF.R_X86_64_JUMP_SLOT"} + ] + }, + "reachability": { + "roots": ["bin:_start","bin:main@0x401000"], + "reachable": ["lib:SSL_read", "lib:SSL_write"], + "unresolved_indirect_calls": [ + {"site":"0x402ABC","reason":"register-indirect"} + ] + } +} +``` + +--- + +## 8) Minimal C# module layout (sketch) + +``` +Stella.Analysis.Core/ + BinaryModule.cs // common model (sections, symbols, relocs, imports/exports) + Loader/ + PeLoader.cs // wrap LibObjectFile (or PeNet) to BinaryModule + ElfLoader.cs // wrap LibObjectFile to BinaryModule + MachOLoader.cs // wrap ELFSharp + your ported Dyld/ChainedFixups + Disasm/ + X86Disassembler.cs // iced bridge: bytes -> instructions + Arm64Disassembler.cs // Disarm (or ARMeilleure port) bridge + Graph/ + CallGraphBuilder.cs // builds CFG per function + inter-procedural edges + Reachability.cs // BFS/DFS over QuikGraph + Demangle/ + ItaniumDemangler.cs // port or wrap CxxDemangler + MicrosoftDemangler.cs // port from LLVM + Export/ + JsonWriter.cs // writes schema above +``` + +--- + +## 9) Implementation notes (where issues usually bite) + +* **Mach‑O moderns:** Implement both dyld opcode **and** chained fixups; many macOS 12+/iOS15+ binaries only have chained fixups. ([emergetools.com][22]) +* **Stubs vs real targets (Mach‑O):** map `__stubs` → `__la_symbol_ptr` via **indirect symbols** to the true imported symbol (or its post‑fixup target). ([reinterpretcast.com][17]) +* **ELF PLT/GOT:** treat `.plt` entries as **call trampolines**; ultimate edge should point to the symbol (library) that satisfies `DT_NEEDED` + version. ([refspecs.linuxbase.org][4]) +* **PE delay‑load:** don’t forget `IMAGE_DELAYLOAD_DESCRIPTOR` for delayed IATs. ([Microsoft Learn][5]) +* **Function discovery:** use `LC_FUNCTION_STARTS` when symbols are stripped; it’s a cheap way to seed analysis. ([Stack Overflow][9]) +* **Name clarity:** demangle Itanium/MSVC so downstream vuln rules can match consistently. ([LLVM][20]) + +--- + +## 10) What to **copy/port** verbatim (safe licenses) + +* **Dyld bind & exports trie logic:** from **LLVM** or **LIEF** Mach‑O (Apache‑2.0). Great for getting the exact opcode semantics right. ([LIEF][7]) +* **Chained fixups structs/walkers:** from **LLVM MachO.h** or Apple dyld headers (permissive headers). ([LLVM][8]) +* **Itanium/MS demanglers:** LLVM demangler sources are standalone; easy to translate to C#. ([LLVM][23]) +* **ARM64 decoder:** if Disarm gaps hurt, lift just the **decoder** pieces from **Ryujinx ARMeilleure** (MIT). ([Gitee][14]) + +*(Avoid GPL’d parsers like binutils/BFD; they will contaminate your codebase’s licensing.)* + +--- + +## 11) End‑to‑end pipeline (per container image) + +1. **Enumerate binaries** in the container FS. +2. **Parse** each with the appropriate loader → `BinaryModule` (+ imports/exports/symbols/relocs). +3. **Simulate linking** per platform to resolve imported functions to provider libraries. ([refspecs.linuxbase.org][4]) +4. **Disassemble** functions (iced/Disarm) → CFGs → **call edges** (direct, PLT/IAT/stub, indirect). +5. **Assemble call graph** across modules; normalize names via demangling. +6. **Reachability**: given roots (entry or user‑specified) compute reachable set; emit JSON with **purls** (from your SBOM/package resolver). +7. **(Optional)** dump GraphViz / MSAGL views for debugging. ([GitHub][19]) + +--- + +## 12) Quick heuristics for vulnerability triage + +* **Sink maps**: flag edges to high‑risk APIs (`strcpy`, `gets`, legacy SSL ciphers) even without CVE versioning. +* **DWARF line info** (when present): attach file:line to nodes for developer action. `LibObjectFile` gives you DWARF v4 reads. ([GitHub][2]) + +--- + +## 13) Test corpora + +* **ELF:** glibc/openssl/libpng from distro repos; validate `R_*_JUMP_SLOT` handling and PLT edges. ([cs61.seas.harvard.edu][16]) +* **PE:** system DLLs (Kernel32, Advapi32) and a small MSVC console app; validate IAT & delay‑load. ([Microsoft Learn][5]) +* **Mach‑O:** Xcode‑built binaries across macOS 11 & 12+ to cover both dyld opcode and chained fixups paths; verify `LC_FUNCTION_STARTS` improves discovery. ([Stack Overflow][9]) + +--- + +## 14) Deliverables you can start coding now + +* **MachOLoader.cs** + + * Parse headers + load commands (ELFSharp). + * Implement `DyldInfoParser` (port from LLVM/LIEF) and `ChainedFixupsParser` (port structs & walkers). ([LIEF][7]) +* **X86Disassembler.cs / Arm64Disassembler.cs** (iced / Disarm bridges). ([GitHub][12]) +* **CallGraphBuilder.cs** (recursive descent + linear sweep fallback; PLT/IAT/stub resolution). +* **Reachability.cs** (QuikGraph BFS/DFS). ([GitHub][18]) +* **JsonWriter.cs** (schema above with purls). + +--- + +### References (core, load‑bearing) + +* **ELFSharp** (ELF + Mach‑O pure C#). ([GitHub][1]) +* **LibObjectFile** (ELF/PE/DWARF C#, BSD‑2). ([GitHub][2]) +* **iced** (x86/x64 disasm, C#, MIT). ([GitHub][12]) +* **Disarm** (ARM64 disasm, C#, MIT). ([GitHub][13]) +* **Ryujinx (ARMeilleure)** (ARMv8 decode/JIT in C#, MIT). ([Gitee][14]) +* **ELF gABI & AMD64 supplement** (PLT/GOT, relocations). ([refspecs.linuxbase.org][4]) +* **PE/COFF** (imports/exports/IAT). ([Microsoft Learn][5]) +* **Mach‑O docs** (load commands; LC_FUNCTION_STARTS; dyld bindings; chained fixups). ([Apple Developer][24]) + +--- + +If you want, I can draft **`MachOLoader` + `DyldInfoParser`** in C# next, including chained‑fixups structs (ported from LLVM’s headers) and an **iced**‑based call‑edge walker for x86‑64. + +[1]: https://github.com/konrad-kruczynski/elfsharp "GitHub - konrad-kruczynski/elfsharp: Pure managed C# library for reading ELF, UImage, Mach-O binaries." +[2]: https://github.com/xoofx/LibObjectFile "GitHub - xoofx/LibObjectFile: LibObjectFile is a .NET library to read, manipulate and write linker and executable object files (e.g ELF, PE, DWARF, ar...)" +[3]: https://github.com/secana/PeNet?utm_source=chatgpt.com "secana/PeNet: Portable Executable (PE) library written in . ..." +[4]: https://refspecs.linuxbase.org/elf/gabi4%2B/contents.html?utm_source=chatgpt.com "System V Application Binary Interface - DRAFT - 24 April 2001" +[5]: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format?utm_source=chatgpt.com "PE Format - Win32 apps" +[6]: https://leopard-adc.pepas.com/documentation/DeveloperTools/Conceptual/MachOTopics/0-Introduction/introduction.html?utm_source=chatgpt.com "Mach-O Programming Topics: Introduction" +[7]: https://lief.re/doc/stable/doxygen/classLIEF_1_1MachO_1_1DyldInfo.html?utm_source=chatgpt.com "MachO::DyldInfo Class Reference - LIEF" +[8]: https://llvm.org/doxygen/structllvm_1_1MachO_1_1dyld__chained__fixups__header.html?utm_source=chatgpt.com "MachO::dyld_chained_fixups_header Struct Reference" +[9]: https://stackoverflow.com/questions/9602438/mach-o-file-lc-function-starts-load-command?utm_source=chatgpt.com "Mach-O file LC_FUNCTION_STARTS load command" +[10]: https://maskray.me/blog/2021-09-19-all-about-procedure-linkage-table?utm_source=chatgpt.com "All about Procedure Linkage Table" +[11]: https://github.com/dotnet/runtime/issues/77178 "Discussion: ObjWriter in C# · Issue #77178 · dotnet/runtime · GitHub" +[12]: https://github.com/icedland/iced?utm_source=chatgpt.com "icedland/iced: Blazing fast and correct x86/x64 ..." +[13]: https://github.com/SamboyCoding/Disarm?utm_source=chatgpt.com "SamboyCoding/Disarm: Fast, pure-C# ARM64 Disassembler" +[14]: https://gitee.com/ryujinx/Ryujinx/blob/master/LICENSE.txt?utm_source=chatgpt.com "Ryujinx/Ryujinx" +[15]: https://github.com/justinstenning/SharpDisasm?utm_source=chatgpt.com "justinstenning/SharpDisasm" +[16]: https://cs61.seas.harvard.edu/site/2022/pdf/x86-64-abi-20210928.pdf?utm_source=chatgpt.com "System V Application Binary Interface" +[17]: https://www.reinterpretcast.com/hello-world-mach-o?utm_source=chatgpt.com "The Nitty Gritty of “Hello World” on macOS | reinterpretcast.com" +[18]: https://github.com/KeRNeLith/QuikGraph?utm_source=chatgpt.com "KeRNeLith/QuikGraph: Generic Graph Data Structures and ..." +[19]: https://github.com/microsoft/automatic-graph-layout?utm_source=chatgpt.com "microsoft/automatic-graph-layout: A set of tools for ..." +[20]: https://llvm.org/doxygen/structllvm_1_1ItaniumPartialDemangler.html?utm_source=chatgpt.com "ItaniumPartialDemangler Struct Reference" +[21]: https://llvm.org/doxygen/MicrosoftDemangle_8cpp_source.html?utm_source=chatgpt.com "lib/Demangle/MicrosoftDemangle.cpp Source File" +[22]: https://www.emergetools.com/blog/posts/iOS15LaunchTime?utm_source=chatgpt.com "How iOS 15 makes your app launch faster" +[23]: https://llvm.org/doxygen/ItaniumDemangle_8cpp.html?utm_source=chatgpt.com "lib/Demangle/ItaniumDemangle.cpp File Reference" +[24]: https://developer.apple.com/library/archive/documentation/Performance/Conceptual/CodeFootprint/Articles/MachOOverview.html?utm_source=chatgpt.com "Overview of the Mach-O Executable Format" +Good, this is exactly the point where you want to nail the internal model, otherwise everything on top (VEX, policy, reporting) will be shaky. + +Below is a detailed, implementation-ready plan for a **reachability graph with purl-aware edges**, covering ELF, PE, and Mach-O, in C#. + +I’ll structure it as: + +1. Overall graph design (3 layers: function, module, purl) +2. Core C# data model +3. Pipeline steps (end-to-end) +4. Format-specific edge construction (ELF / PE / Mach-O) +5. Reachability queries (from entrypoints to vulnerable purls / functions) +6. JSON output layout and integration with SBOM + +--- + +## 1. Overall graph design + +You want three tightly linked graph layers: + +1. **Function-level call graph (FLG)** + + * Nodes: individual **functions** inside binaries + * Edges: calls from function A → function B (intra- or inter-module) + +2. **Module-level graph (MLG)** + + * Nodes: **binaries** (ELF/PE/Mach-O files) + * Edges: “module A calls module B at least once” (aggregated from FLG) + +3. **Purl-level graph (PLG)** + + * Nodes: **purls** (packages or generic artifacts) + * Edges: “purl P1 depends-at-runtime on purl P2” (aggregated from module edges) + +The **reachability algorithm** runs primarily on the **function graph**, but: + +* You can project reachability results to **module** and **purl** nodes. +* You can also run coarse-grained analysis directly on **purl graph** when needed (“Is any code in purl X reachable from the container entrypoint?”). + +--- + +## 2. Core C# data model + +### 2.1 Identifiers and enums + +```csharp +public enum BinaryFormat { Elf, Pe, MachO } + +public readonly record struct ModuleId(string Path, BinaryFormat Format); + +public readonly record struct Purl(string Value); + +public enum EdgeKind +{ + IntraModuleDirect, // call foo -> bar in same module + ImportCall, // call via plt/iat/stub to imported function + SyntheticRoot, // root (entrypoint) edge + IndirectUnresolved // optional: we saw an indirect call we couldn't resolve +} +``` + +### 2.2 Function node + +```csharp +public sealed class FunctionNode +{ + public int Id { get; init; } // internal numeric id + public ModuleId Module { get; init; } + public Purl Purl { get; init; } // resolved from Module -> Purl + public ulong Address { get; init; } // VA or RVA + public string Name { get; init; } // mangled + public string? DemangledName { get; init; } // optional + public bool IsExported { get; init; } + public bool IsImportedStub { get; init; } // e.g. PLT stub, Mach-O stub, PE thunks + public bool IsRoot { get; set; } // _start/main/entrypoint etc. +} +``` + +### 2.3 Edges + +```csharp +public sealed class CallEdge +{ + public int FromId { get; init; } // FunctionNode.Id + public int ToId { get; init; } // FunctionNode.Id + public EdgeKind Kind { get; init; } + public string Evidence { get; init; } // e.g. "ELF.R_X86_64_JUMP_SLOT", "PE.IAT", "MachO.indirectSym" +} +``` + +### 2.4 Graph container + +```csharp +public sealed class CallGraph +{ + public IReadOnlyDictionary Nodes { get; init; } + public IReadOnlyDictionary> OutEdges { get; init; } + public IReadOnlyDictionary> InEdges { get; init; } + + // Convenience: mappings + public IReadOnlyDictionary> FunctionsByModule { get; init; } + public IReadOnlyDictionary> FunctionsByPurl { get; init; } +} +``` + +### 2.5 Purl-level graph view + +You don’t store a separate physical graph; you **derive** it on demand: + +```csharp +public sealed class PurlEdge +{ + public Purl From { get; init; } + public Purl To { get; init; } + public List<(int FromFnId, int ToFnId)> SupportingCalls { get; init; } +} + +public sealed class PurlGraphView +{ + public IReadOnlyDictionary> Adjacent { get; init; } + public IReadOnlyList Edges { get; init; } +} +``` + +--- + +## 3. Pipeline steps (end-to-end) + +### Step 0 – Inputs + +* Set of binaries (files) extracted from container image. +* SBOM or other metadata that can map a file path (or hash) → **purl**. + +### Step 1 – Parse binaries → `BinaryModule` objects + +You define a common in-memory model: + +```csharp +public sealed class BinaryModule +{ + public ModuleId Id { get; init; } + public Purl Purl { get; init; } + public BinaryFormat Format { get; init; } + + // Raw sections / segments + public IReadOnlyList Sections { get; init; } + + // Symbols + public IReadOnlyList Symbols { get; init; } // imports + exports + locals + + // Relocations / fixups + public IReadOnlyList Relocations { get; init; } + + // Import/export tables (PE)/dylib commands (Mach-O)/DT_NEEDED (ELF) + public ImportInfo[] Imports { get; init; } + public ExportInfo[] Exports { get; init; } +} +``` + +Implement format-specific loaders: + +* `ElfLoader : IBinaryLoader` +* `PeLoader : IBinaryLoader` +* `MachOLoader : IBinaryLoader` + +Each loader uses your chosen C# parsers or ported code and fills `BinaryModule`. + +### Step 2 – Disassembly → basic blocks & candidate functions + +For each `BinaryModule`: + +1. Use appropriate decoder (iced for x86/x64; Disarm/ported ARMeilleure for AArch64). +2. Seed function starts: + + * Exported functions + * Entry points (`_start`, `main`, AddressOfEntryPoint) + * Mach-O `LC_FUNCTION_STARTS` if available +3. Walk instructions to build basic blocks: + + * Stop blocks at conditional/unconditional branches, calls, rets. + * Record for each call site: + + * Address of caller function + * Operand type (immediate, memory with import table address, etc.) + +Disassembler outputs a list of `FunctionNode` skeletons (no cross-module link yet) and a list of **raw call sites**: + +```csharp +public sealed class RawCallSite +{ + public int CallerFunctionId { get; init; } + public ulong InstructionAddress { get; init; } + public ulong? DirectTargetAddress { get; init; } // e.g. CALL 0x401000 + public ulong? MemoryTargetAddress { get; init; } // e.g. CALL [0x404000] + public bool IsIndirect { get; init; } // register-based etc. +} +``` + +### Step 3 – Build function nodes + +Using disassembly + symbol tables: + +* For each discovered function: + + * Determine: address, name (if sym available), export/import flags. + * Map `ModuleId` → `Purl` using `IPurlResolver`. +* Populate `FunctionNode` instances and index them by `Id`. + +### Step 4 – Construct intra-module edges + +For each `RawCallSite`: + +* If `DirectTargetAddress` falls inside a known function’s address range in the **same module**, add **IntraModuleDirect** edge. + +This gives you “normal” calls like `foo()` calling `bar()` in the same .so/.dll/. + +### Step 5 – Construct inter-module edges (import calls) + +This is where ELF/PE/Mach-O differ; details in section 4 below. + +But the abstract logic is: + +1. For each call site with `MemoryTargetAddress` (IAT slot / GOT entry / la_symbol_ptr / PLT): +2. From the module’s import, relocation or fixup tables, determine: + + * Which **imported symbol** it corresponds to (name, ordinal, etc.). + * Which **imported module / dylib / DLL** provides that symbol. +3. Find (or create) a `FunctionNode` representing that imported symbol in the **provider module**. +4. Add an **ImportCall** edge from caller function to the provider `FunctionNode`. + +This is the key to turning low-level dynamic linking into **purl-aware cross-module edges**, because each `FunctionNode` is already stamped with a `Purl`. + +### Step 6 – Build adjacency structures + +Once you have all `FunctionNode`s and `CallEdge`s: + +* Build `OutEdges` and `InEdges` dictionaries keyed by `FunctionNode.Id`. +* Build `FunctionsByModule` / `FunctionsByPurl`. + +--- + +## 4. Format-specific edge construction + +This is the “how” for step 5, per binary format. + +### 4.1 ELF + +Goal: map call sites that go via PLT/GOT to an imported function in a `DT_NEEDED` library. + +Algorithm: + +1. Parse: + + * `.dynsym`, `.dynstr` – dynamic symbol table + * `.rela.plt` / `.rel.plt` – relocation entries for PLT + * `.got.plt` / `.got` – PLT’s GOT + * `DT_NEEDED` entries – list of linked shared objects and their sonames + +2. For each relocation of type `R_*_JUMP_SLOT`: + + * It applies to an entry in the PLT GOT; that GOT entry is what CALL instructions read from. + * Relocation gives you: + + * Offset in GOT (`r_offset`) + * Symbol index (`r_info` → symbol) → dynamic symbol (`ElfSymbol`) + * Symbol name, type (FUNC), binding, etc. + +3. Link GOT entries to call sites: + + * For each `RawCallSite` with `MemoryTargetAddress`, check if that address falls inside `.got.plt` (or `.got`). If it does: + + * Find relocation whose `r_offset` equals that GOT entry offset. + * That tells you which **symbol** is being called. + +4. Determine provider module: + + * From the symbol’s `st_name` and `DT_NEEDED` list, decide which shared object is expected to define it (an approximation is: first DT_NEEDED that provides that name). + * Map DT_NEEDED → `ModuleId` (you’ll have loaded these modules separately, or you can create “placeholder modules” if they’re not in the container image). + +5. Create edges: + + * Create/find `FunctionNode` for the **imported symbol** in provider module. + * Add `CallEdge` from caller function to imported function, `EdgeKind = ImportCall`, `Evidence = "ELF.R_X86_64_JUMP_SLOT"` (or arch-specific). + +This yields edges like: + +* `myapp:main` → `libssl.so.1.1:SSL_read` +* `libfoo.so:foo` → `libc.so.6:malloc` + +### 4.2 PE + +Goal: map call sites that go via the Import Address Table (IAT) to imported functions in DLLs. + +Algorithm: + +1. Parse: + + * `IMAGE_IMPORT_DESCRIPTOR[]` – each for a DLL name. + * Original thunk table (INT) – names/ordinals of imported symbols. + * IAT – where the loader writes function addresses at runtime. + +2. For each import entry: + + * Determine: + + * DLL name (`Name`) + * Function name or ordinal (from INT) + * IAT slot address (RVA) + +3. Link IAT slots to call sites: + + * For each `RawCallSite` with `MemoryTargetAddress`: + + * Check if this address equals the VA of an IAT slot. + * If yes, the call site is effectively calling that imported function. + +4. Determine provider module: + + * The DLL name gives you a target module (e.g. `KERNEL32.dll` → `ModuleId`). + * Ensure that DLL is represented as a `BinaryModule` or a “placeholder” if not present in image. + +5. Create edges: + + * Create/find `FunctionNode` for imported function in provider module. + * Add `CallEdge` with `EdgeKind = ImportCall` and `Evidence = "PE.IAT"` (or `"PE.DelayLoad"` if using delay load descriptors). + +Example: + +* `myservice.exe:Start` → `SSPICLI.dll:AcquireCredentialsHandleW` + +### 4.3 Mach-O + +Goal: map stub calls via `__TEXT,__stubs` / `__DATA,__la_symbol_ptr` (and / or chained fixups) to symbols in dependent dylibs. + +Algorithm (for classic dyld opcodes, not chained fixups, then extend): + +1. Parse: + + * Load commands: + + * `LC_SYMTAB`, `LC_DYSYMTAB` + * `LC_LOAD_DYLIB` (to know dependent dylibs) + * `LC_FUNCTION_STARTS` (for seeding functions) + * `LC_DYLD_INFO` (rebase/bind/lazy bind) + * `__TEXT,__stubs` – stub code + * `__DATA,__la_symbol_ptr` (or `__DATA_CONST,__la_symbol_ptr`) – lazy pointer table + * **Indirect symbol table** – maps slot indices to symbol table indices + +2. Stub → la_symbol_ptr mapping: + + * Stubs are small functions (usually a few instructions) that indirect through the corresponding `la_symbol_ptr` entry. + * For each stub function: + + * Determine which la_symbol_ptr entry it uses (based on stub index and linking metadata). + * From the indirect symbol table, find which dynamic symbol that la_symbol_ptr entry corresponds to. + + * This gives you symbol name and the index in `LC_LOAD_DYLIB` (dylib ordinal). + +3. Link stub call sites: + + * In disassembly, treat calls to these stub functions as **import calls**. + * For each call instruction `CALL stub_function`: + + * `RawCallSite.DirectTargetAddress` lies inside `__TEXT,__stubs`. + * Resolve stub → la_symbol_ptr → symbol → dylib. + +4. Determine provider module: + + * From dylib ordinal and load commands, get the path / install name of dylib (`libssl.1.1.dylib`, etc.). + * Map that to a `ModuleId` in your module set. + +5. Create edges: + + * Create/find imported `FunctionNode` in provider module. + * Add `CallEdge` from caller to that function with `EdgeKind = ImportCall`, `Evidence = "MachO.IndirectSymbol"`. + +For **chained fixups** (`LC_DYLD_CHAINED_FIXUPS`), you’ll compute a similar mapping but walking chain entries instead of traditional lazy/weak binds. The key is still: + +* Map a stub or function to a **fixup** entry. +* From fixup, determine the symbol and dylib. +* Then connect call-site → imported function. + +--- + +## 5. Reachability queries + +Once the graph is built, reachability is “just graph algorithms” + mapping back to purls. + +### 5.1 Roots + +Decide what are your **root functions**: + +* Binary entrypoints: + + * ELF: `_start`, `main`, constructors (`.init_array`) + * PE: AddressOfEntryPoint, registered service entrypoints + * Mach-O: `_main`, constructors +* Optionally, any exported API function that a container orchestrator or plugin system will call. + +Mark them as `FunctionNode.IsRoot = true` and create synthetic edges from a special root node if you want: + +```csharp +var syntheticRoot = new FunctionNode +{ + Id = 0, + Name = "", + IsRoot = true, + // Module, Purl can be special markers +}; + +foreach (var fn in allFunctions.Where(f => f.IsRoot)) +{ + edges.Add(new CallEdge + { + FromId = syntheticRoot.Id, + ToId = fn.Id, + Kind = EdgeKind.SyntheticRoot, + Evidence = "Root" + }); +} +``` + +### 5.2 Reachability algorithm (function-level) + +Use BFS/DFS from the root node(s): + +```csharp +public sealed class ReachabilityResult +{ + public HashSet ReachableFunctions { get; } = new(); +} + +public ReachabilityResult ComputeReachableFunctions(CallGraph graph, IEnumerable rootIds) +{ + var visited = new HashSet(); + var stack = new Stack(); + + foreach (var root in rootIds) + { + if (visited.Add(root)) + stack.Push(root); + } + + while (stack.Count > 0) + { + var current = stack.Pop(); + + if (!graph.OutEdges.TryGetValue(current, out var edges)) + continue; + + foreach (var edge in edges) + { + if (visited.Add(edge.ToId)) + stack.Push(edge.ToId); + } + } + + return new ReachabilityResult { ReachableFunctions = visited }; +} +``` + +### 5.3 Project reachability to modules and purls + +Given `ReachableFunctions`: + +```csharp +public sealed class ReachabilityProjection +{ + public HashSet ReachableModules { get; } = new(); + public HashSet ReachablePurls { get; } = new(); +} + +public ReachabilityProjection ProjectToModulesAndPurls(CallGraph graph, ReachabilityResult result) +{ + var projection = new ReachabilityProjection(); + + foreach (var fnId in result.ReachableFunctions) + { + if (!graph.Nodes.TryGetValue(fnId, out var fn)) + continue; + + projection.ReachableModules.Add(fn.Module); + projection.ReachablePurls.Add(fn.Purl); + } + + return projection; +} +``` + +Now you can answer questions like: + +* “Is any code from purl `pkg:deb/openssl@1.1.1w-1` reachable from the container entrypoint?” +* “Which purls are reachable at all?” + +### 5.4 Vulnerability reachability + +Assume you’ve mapped each vulnerability to: + +* `Purl` (where it lives) +* `AffectedFunctionNames` (symbols; optionally demangled) + +You can implement: + +```csharp +public sealed class VulnerabilitySink +{ + public string VulnerabilityId { get; init; } // CVE-... + public Purl Purl { get; init; } + public string FunctionName { get; init; } // symbol name or demangled +} +``` + +Resolution algorithm: + +1. For each `VulnerabilitySink`, find all `FunctionNode` with: + + * `node.Purl == sink.Purl` and + * `node.Name` or `node.DemangledName` matches `sink.FunctionName`. + +2. For each such node, check `ReachableFunctions.Contains(node.Id)`. + +3. Build a `Finding` object: + +```csharp +public sealed class VulnerabilityFinding +{ + public string VulnerabilityId { get; init; } + public Purl Purl { get; init; } + public bool IsReachable { get; init; } + public List SinkFunctionIds { get; init; } = new(); +} +``` + +Plus, if you want **path evidence**, you run a shortest-path search (BFS predecessor map) from root to sink and store the sequence of `FunctionNode.Id`s. + +--- + +## 6. Purl edges (derived graph) + +For reporting and analytics, it’s useful to produce a **purl-level dependency graph**. + +Given `CallGraph`: + +```csharp +public PurlGraphView BuildPurlGraph(CallGraph graph) +{ + var edgesByPair = new Dictionary<(Purl From, Purl To), PurlEdge>(); + + foreach (var kv in graph.OutEdges) + { + var fromFn = graph.Nodes[kv.Key]; + + foreach (var edge in kv.Value) + { + var toFn = graph.Nodes[edge.ToId]; + + if (fromFn.Purl.Equals(toFn.Purl)) + continue; // intra-purl, skip if you only care about inter-purl + + var key = (fromFn.Purl, toFn.Purl); + if (!edgesByPair.TryGetValue(key, out var pe)) + { + pe = new PurlEdge + { + From = fromFn.Purl, + To = toFn.Purl, + SupportingCalls = new List<(int, int)>() + }; + edgesByPair[key] = pe; + } + + pe.SupportingCalls.Add((fromFn.Id, toFn.Id)); + } + } + + var adj = new Dictionary>(); + + foreach (var kv in edgesByPair) + { + var (from, to) = kv.Key; + if (!adj.TryGetValue(from, out var list)) + { + list = new HashSet(); + adj[from] = list; + } + list.Add(to); + } + + return new PurlGraphView + { + Adjacent = adj, + Edges = edgesByPair.Values.ToList() + }; +} +``` + +This gives you: + +* A coarse view of runtime dependencies between purls (“Purl A calls into Purl B”). +* Enough context to emit purl-level VEX or to reason about trust at package granularity. + +--- + +## 7. JSON output and SBOM integration + +### 7.1 JSON shape (high level) + +You can emit a composite document: + +```json +{ + "image": "registry.example.com/app@sha256:...", + "modules": [ + { + "moduleId": { "path": "/usr/lib/libssl.so.1.1", "format": "Elf" }, + "purl": "pkg:deb/ubuntu/openssl@1.1.1w-0ubuntu1", + "arch": "x86_64" + } + ], + "functions": [ + { + "id": 42, + "name": "SSL_do_handshake", + "demangledName": null, + "module": { "path": "/usr/lib/libssl.so.1.1", "format": "Elf" }, + "purl": "pkg:deb/ubuntu/openssl@1.1.1w-0ubuntu1", + "address": "0x401020", + "exported": true + } + ], + "edges": [ + { + "from": 10, + "to": 42, + "kind": "ImportCall", + "evidence": "ELF.R_X86_64_JUMP_SLOT" + } + ], + "reachability": { + "roots": [1], + "reachableFunctions": [1,10,42] + }, + "purlGraph": { + "edges": [ + { + "from": "pkg:generic/myapp@1.0.0", + "to": "pkg:deb/ubuntu/openssl@1.1.1w-0ubuntu1", + "supportingCalls": [[10,42]] + } + ] + }, + "vulnerabilities": [ + { + "id": "CVE-2024-XXXX", + "purl": "pkg:deb/ubuntu/openssl@1.1.1w-0ubuntu1", + "sinkFunctions": [42], + "reachable": true, + "paths": [ + [1, 10, 42] + ] + } + ] +} +``` + +### 7.2 Purl resolution + +Implement an `IPurlResolver` interface: + +```csharp +public interface IPurlResolver +{ + Purl ResolveForModule(string filePath, byte[] contentHash); +} +``` + +Possible implementations: + +* `SbomPurlResolver` – given a CycloneDX/SPDX SBOM for the image, match by path or checksum. +* `LinuxPackagePurlResolver` – read `/var/lib/dpkg/status` / rpm DB in the filesystem. +* `GenericPurlResolver` – fallback: `pkg:generic/`. + +You call the resolver in your loaders so that **every `BinaryModule` has a purl** and thus every `FunctionNode` has a purl. + +--- + +## 8. Concrete implementation tasks for your team + +1. **Data model & interfaces** + + * Implement `ModuleId`, `FunctionNode`, `CallEdge`, `CallGraph`. + * Define `RawCallSite`, `BinaryModule`, and `IPurlResolver`. + +2. **Loaders** + + * `ElfLoader`: fill symbols, dynamic relocations (PLT), DT_NEEDED, etc. + * `PeLoader`: import descriptors, IAT, delay-load. + * `MachOLoader`: load commands, stubs, la_symbol_ptr, indirect symbols / chained fixups. + +3. **Disassembly** + + * `X86Disassembler` (iced) and `Arm64Disassembler` (Disarm or port). + * Function detection and `RawCallSite` extraction. + +4. **CallGraphBuilder** + + * Build intra-module edges from direct calls. + * Build inter-module edges using the format-specific rules above. + * Construct final `CallGraph` with adjacency maps and purl mappings. + +5. **Reachability** + + * Implement BFS/DFS from root functions. + * Projection to modules + purls. + * Vulnerability sink resolution & path reconstruction. + +6. **Export** + + * JSON serializer for the schema above. + * Optional: purl-level summary / VEX generator. + +--- + +If you want, next step I can do a **more concrete design for `CallGraphBuilder`** (including per-format helper classes with method signatures) or a **C# skeleton** for the `ElfImportResolver`, `PeImportResolver`, and `MachOStubResolver` that plug directly into this plan. diff --git a/docs/product-advisories/18-Nov-2026 - 1 copy 4.md b/docs/product-advisories/18-Nov-2026 - 1 copy 4.md new file mode 100644 index 000000000..d99db6660 --- /dev/null +++ b/docs/product-advisories/18-Nov-2026 - 1 copy 4.md @@ -0,0 +1,927 @@ + +Here’s a crisp idea that could give Stella Ops a real moat: **binary‑level reachability**—linking CVEs directly to the exact functions and offsets inside compiled artifacts (ELF/PE/Mach‑O), not just to packages. + +--- + +### Why this matters (quick background) + +* **Package‑level flags are noisy.** Most scanners say “vuln in `libX v1.2`,” but that library might be present and never executed. +* **Language‑level call graphs help** (when you have source or rich metadata), but containers often ship only **stripped binaries**. +* **Binary reachability** answers: *Is the vulnerable function actually in this image? Is its code path reachable from the entrypoints we observed or can construct?* + +--- + +### The missing layer: Symbolization + +Build a **symbolization layer** that normalizes debug and symbol info across platforms: + +* **Inputs**: DWARF (ELF/Mach‑O), PDB (PE/Windows), symtabs, exported symbols, `.eh_frame`, and (when stripped) heuristic signatures (e.g., function byte‑hashes, CFG fingerprints). +* **Outputs**: a source‑agnostic map: `{binary → sections → functions → (addresses, ranges, hashes, demangled names, inlined frames)}`. +* **Normalization**: Put everything into a common schema (e.g., `Stella.Symbolix.v1`) so higher layers don’t care if it came from DWARF or PDB. + +--- + +### End‑to‑end reachability (binary‑first, source‑agnostic) + +1. **Acquire & parse** + + * Detect format (ELF/PE/Mach‑O), parse headers, sections, symbol tables. + * If debug info present: parse DWARF/PDB; else fall back to disassembly + function boundary recovery. +2. **Function catalog** + + * Assign stable IDs per function: `(imageHash, textSectionHash, startVA, size, fnHashXX)`. + * Record x‑refs (calls/jumps), imports/exports, PLT/IAT edges. +3. **Entrypoint discovery** + + * Docker entry, process launch args, service scripts; infer likely mains (Go `main.main`, .NET hostfxr path, JVM launcher, etc.). +4. **Call‑graph build (binary CFG)** + + * Build inter/intra‑procedural graph (direct + resolved indirect via IAT/PLT). Keep “unknown‑target” edges for conservative safety. +5. **CVE→function linking** + + * Maintain a **signature bank** per CVE advisory: vulnerable function names, file paths, and—crucially—**byte‑sequence or basic‑block fingerprints** for patched vs vulnerable versions (works even when stripped). +6. **Reachability analysis** + + * Is the vulnerable function present? Is there a path from any entrypoint to it (under conservative assumptions)? Tag as `Present+Reachable`, `Present+Uncertain`, or `Absent`. +7. **Runtime confirmation (optional, when users allow)** + + * Lightweight probes (eBPF on Linux, ETW on Windows, perf/JFR/EventPipe) capture function hits; cross‑check with the static result to upgrade confidence. + +--- + +### Minimal component plan (drop into Stella Ops) + +* **Scanner.Symbolizer** + Parsers: ELF/DWARF (libdw or pure‑managed reader), PE/PDB (Dia/LLVM PDB), Mach‑O/DSYM. + Output: `Symbolix.v1` blobs stored in OCI layer cache. +* **Scanner.CFG** + Lifts functions to a normalized IR (capstone/iced‑x86 for decode) → builds CFG & call graph. +* **Advisory.FingerprintBank** + Ingests CSAF/OpenVEX plus curated fingerprints (fn names, block hashes, patch diff markers). Versioned, signed, air‑gap‑syncable. +* **Reachability.Engine** + Joins (`Symbolix` + `CFG` + `FingerprintBank`) → emits `ReachabilityEvidence` with lattice states for VEX. +* **VEXer.Adapter** + Emits **OpenVEX** statements with `status: affected/not_affected` and `justification: function_not_present | function_not_reachable | mitigated_at_runtime`, attaching Evidence URIs. +* **Console UX** + “Why not affected?” panel showing entrypoint→…→function path (or absence), with byte‑hash proof. + +--- + +### Data model sketch (concise) + +* `ImageFunction { id, name?, startVA, size, fnHash, sectionHash, demangled?, provenance:{DWARF|PDB|Heuristic} }` +* `Edge { srcFnId, dstFnId, kind:{direct|plt|iat|indirect?} }` +* `CveSignature { cveId, fnName?, libHints[], blockFingerprints[], versionRanges }` +* `Evidence { cveId, imageId, functionMatches[], reachable: bool?, confidence:[low|med|high], method:[static|runtime|hybrid] }` + +--- + +### Practical phases (8–10 weeks of focused work) + +1. **P0**: ELF/DWARF symbolizer + basic function catalog; link a handful of CVEs via name‑only; emit OpenVEX `function_not_present`. +2. **P1**: CFG builder (direct calls) + PLT/IAT resolution; simple reachability; first fingerprints for top 50 CVEs in glibc, openssl, curl, zlib. +3. **P2**: Stripped‑binary heuristics (block hashing) + Go/Rust name demangling; Windows PDB ingestion for PE. +4. **P3**: Runtime probes (opt‑in) + confidence upgrade logic; Console path explorer; evidence signing (DSSE). + +--- + +### KPIs to prove the moat + +* **Noise cut**: % reduction in “affected” flags after reachability (target 40–70% on typical containers). +* **Precision**: Ground‑truth validation vs PoC images (TP/FP/FN on presence & reachability). +* **Coverage**: % images where we can make a determination without source (goal: >80%). +* **Latency**: Added scan time per image (<15s typical with caches). + +--- + +### Risks & how to handle them + +* **Stripped binaries** → mitigate with block‑hash fingerprints & library‑version heuristics. +* **Obfuscated/packed code** → mark `Uncertain`; allow user‑supplied hints; prefer runtime confirmation. +* **Advisory inconsistency** → keep our own curated CVE→function fingerprint bank; sign & version it. +* **Platform spread** → start Linux/ELF, then Windows/PDB, then Mach‑O. + +--- + +### Why competitors struggle + +Most tools stop at packages because binary CFG + fingerprint curation is hard and expensive. Shipping a **source‑agnostic reachability engine** tied to signed evidence in VEX would set Stella Ops apart—especially in offline/air‑gapped and sovereign contexts you already target. + +If you want, I can draft: + +* the `Symbolix.v1` protobuf, +* a tiny PoC (ELF→functions→match CVE with a block fingerprint), +* and the OpenVEX emission snippet your VEXer can produce. +Below is a detailed architecture plan for implementing reachability and call-graph analysis in Stella Ops, covering JavaScript, Python, PHP, and binaries, and integrating with your existing Scanner / Concelier / VEXer stack. + +I will assume: + +* .NET 10 for core services. +* Scanner is the place where all “trust algebra / lattice” runs (per your standing rule). +* Concelier and VEXer remain “preserve/prune” layers and do not run lattice logic. +* Output must be JSON-centric with PURLs and OpenVEX. + +--- + +## 1. Scope & Objectives + +### 1.1 Primary goals + +1. From an OCI image, build: + + * A **library-level usage graph** (which libraries are used by which entrypoints). + * A **function-level call graph** for JS / Python / PHP / binaries. +2. Map CVEs (from Concelier) to: + + * Concrete **components** (PURLs) in the SBOM. + * Concrete **functions / entrypoints / code regions** inside those components. +3. Perform **reachability analysis** to classify each vulnerability as: + + * `present + reachable` + * `present + not_reachable` + * `function_not_present` (no vulnerable symbol) + * `uncertain` (dynamic features, unresolved calls) +4. Emit: + + * **Structured JSON** with PURLs and call-graph nodes/edges (“reachability evidence”). + * **OpenVEX** documents with appropriate `status`/`justification`. + +### 1.2 Non-goals (for now) + +* Full dynamic analysis of the running container (eBPF, ptrace, etc.) – leave as Phase 3+ optional add-on. +* Perfect call graph precision for dynamic languages (aim for safe, conservative approximations). +* Automatic “fix recommendations” (handled by other Stella Ops agents later). + +--- + +## 2. High-Level Architecture + +### 2.1 Major components + +Within Stella Ops: + +* **Scanner.WebService** + + * User-facing API. + * Orchestrates full scan (SBOM, CVEs, reachability). + * Hosts the **Lattice/Policy engine** that merges evidence and produces decisions. +* **Scanner.Worker** + + * Runs per-image analysis jobs. + * Invokes analyzers (JS, Python, PHP, Binary) inside its own container context. +* **Scanner.Reachability Core Library** + + * Unified IR for call graphs and reachability evidence. + * Interfaces for language and binary analyzers. + * Graph algorithms (BFS/DFS, lattice evaluation, entrypoint expansion). +* **Language Analyzers** + + * `Scanner.Analyzers.JavaScript` + * `Scanner.Analyzers.Python` + * `Scanner.Analyzers.Php` + * `Scanner.Analyzers.Binary` +* **Symbolization & CFG (for binaries)** + + * `Scanner.Symbolization` (ELF, PE, Mach-O parsers, DWARF/PDB) + * `Scanner.Cfg` (CFG + call graph for binaries) +* **Vulnerability Signature Bank** + + * `Concelier.Signatures` (curated CVE→function/library fingerprints). + * Exposed to Scanner as **offline bundle**. +* **VEXer** + + * `Vexer.Adapter.Reachability` – transforms reachability evidence into OpenVEX. + +### 2.2 Data flow (logical) + +```mermaid +flowchart LR + A[OCI Image / Tar] --> B[Scanner.Worker: Extract FS] + B --> C[SBOM Engine (CycloneDX/SPDX)] + C --> D[Vuln Match (Concelier feeds)] + B --> E1[JS Analyzer] + B --> E2[Python Analyzer] + B --> E3[PHP Analyzer] + B --> E4[Binary Analyzer + Symbolizer/CFG] + + D --> F[Reachability Orchestrator] + E1 --> F + E2 --> F + E3 --> F + E4 --> F + F --> G[Lattice/Policy Engine (Scanner.WebService)] + G --> H[Reachability Evidence JSON] + G --> I[VEXer: OpenVEX] + G --> J[Graph/Cartographer (optional)] +``` + +--- + +## 3. Data Model & JSON Contracts + +### 3.1 Core IR types (Scanner.Reachability) + +Define in a central assembly, e.g. `StellaOps.Scanner.Reachability`: + +```csharp +public record ComponentRef( + string Purl, + string? BomRef, + string? Name, + string? Version); + +public enum SymbolKind { Function, Method, Constructor, Lambda, Import, Export } + +public record SymbolId( + string Language, // "js", "python", "php", "binary" + string ComponentPurl, // SBOM component PURL or "" for app code + string LogicalName, // e.g., "server.js:handleLogin" + string? FilePath, + int? Line); + +public record CallGraphNode( + string Id, // stable id, e.g., hash(SymbolId) + SymbolId Symbol, + SymbolKind Kind, + bool IsEntrypoint); + +public enum CallEdgeKind { Direct, Indirect, Dynamic, External, Ffi } + +public record CallGraphEdge( + string FromNodeId, + string ToNodeId, + CallEdgeKind Kind); + +public record CallGraph( + string GraphId, + IReadOnlyList Nodes, + IReadOnlyList Edges); +``` + +### 3.2 Vulnerability mapping + +```csharp +public record VulnerabilitySignature( + string Source, // "csaf", "nvd", "vendor" + string Id, // "CVE-2023-12345" + IReadOnlyList Purls, + IReadOnlyList TargetSymbolPatterns, // glob-like or regex + IReadOnlyList? FilePathPatterns, + IReadOnlyList? BlockFingerprints // for binaries, optional +); +``` + +### 3.3 Reachability evidence + +```csharp +public enum ReachabilityStatus +{ + PresentReachable, + PresentNotReachable, + FunctionNotPresent, + Unknown +} + +public record ReachabilityEvidence +( + string ImageRef, + string VulnId, // CVE or advisory id + ComponentRef Component, + ReachabilityStatus Status, + double Confidence, // 0..1 + string Method, // "static-callgraph", "binary-fingerprint", etc. + IReadOnlyList EntrypointNodeIds, + IReadOnlyList>? ExamplePaths // optional list of node-paths +); +``` + +### 3.4 JSON structure (external) + +Minimal external JSON (what you store / expose): + +```json +{ + "image": "registry.example.com/app:1.2.3", + "components": [ + { + "purl": "pkg:npm/express@4.18.0", + "bomRef": "component-1" + } + ], + "callGraphs": [ + { + "graphId": "js-main", + "language": "js", + "nodes": [ /* CallGraphNode */ ], + "edges": [ /* CallGraphEdge */ ] + } + ], + "reachability": [ + { + "vulnId": "CVE-2023-12345", + "componentPurl": "pkg:npm/express@4.18.0", + "status": "PresentReachable", + "confidence": 0.92, + "entrypoints": [ "node:..." ], + "paths": [ + ["node:entry", "node:routeHandler", "node:vulnFn"] + ] + } + ] +} +``` + +--- + +## 4. Scanner-Side Architecture + +### 4.1 Project layout (suggested) + +```text +src/ + Scanner/ + StellaOps.Scanner.WebService/ + StellaOps.Scanner.Worker/ + StellaOps.Scanner.Core/ # shared scan domain + StellaOps.Scanner.Reachability/ + StellaOps.Scanner.Symbolization/ + StellaOps.Scanner.Cfg/ + StellaOps.Scanner.Analyzers.JavaScript/ + StellaOps.Scanner.Analyzers.Python/ + StellaOps.Scanner.Analyzers.Php/ + StellaOps.Scanner.Analyzers.Binary/ +``` + +### 4.2 API surface (Scanner.WebService) + +* `POST /api/scan/image` + + * Request: `{ "imageRef": "...", "profile": { "reachability": true, ... } }` + * Returns: scan id. +* `GET /api/scan/{id}/reachability` + + * Returns: `ReachabilityEvidence[]`, plus call graph summary (optional). +* `GET /api/scan/{id}/vex` + + * Returns: OpenVEX with statuses based on reachability lattice. + +### 4.3 Worker orchestration + +`StellaOps.Scanner.Worker`: + +1. Receives scan job with `imageRef`. + +2. Extracts filesystem (layered rootfs) under `/mnt/scans/{scanId}/rootfs`. + +3. Invokes SBOM generator (CycloneDX/SPDX). + +4. Invokes Concelier via offline feeds to get: + + * Component vulnerabilities (CVE list per PURL). + * Vulnerability signatures (fingerprints). + +5. Builds a `ReachabilityPlan`: + + ```csharp + public record ReachabilityPlan( + IReadOnlyList Components, + IReadOnlyList Vulns, + IReadOnlyList AnalyzerTargets // files/dirs grouped by language + ); + ``` + +6. For each language target, dispatch analyzer: + + * JavaScript: `IReachabilityAnalyzer` implementation for JS. + * Python: likewise. + * PHP: likewise. + * Binary: symbolizer + CFG. + +7. Collects call graphs from each analyzer and merges them into a single IR (or separate per-language graphs with shared IDs). + +8. Sends merged graphs + vuln list to **Reachability Engine** (Scanner.Reachability). + +--- + +## 5. Language Analyzers (JS / Python / PHP) + +All analyzers implement a common interface: + +```csharp +public interface IReachabilityAnalyzer +{ + string Language { get; } // "js", "python", "php" + + Task AnalyzeAsync(AnalyzerContext context, CancellationToken ct); +} + +public record AnalyzerContext( + string RootFsPath, + IReadOnlyList Components, + IReadOnlyList Vulnerabilities, + IReadOnlyDictionary Env, // container env, entrypoint, etc. + string? EntrypointCommand // container CMD/ENTRYPOINT +); +``` + +### 5.1 JavaScript (Node.js focus) + +**Inputs:** + +* `/app` tree inside container (or discovered via SBOM). +* `package.json` files. +* Container entrypoint (e.g., `["node", "server.js"]`). + +**Core steps:** + +1. Identify **app root**: + + * Heuristics: directory containing `package.json` that owns the entry script. +2. Parse: + + * All `.js`, `.mjs`, `.cjs` in app and `node_modules` for vulnerable PURLs. + * Use a parsing frontend (e.g., Tree-sitter via .NET binding, or Node+AST-as-JSON). +3. Build module graph: + + * `require`, `import`, `export`. +4. Function-level graph: + + * For each function/method, create `CallGraphNode`. + * For each `callExpression`, create `CallGraphEdge` (try to resolve callee). +5. Entrypoints: + + * Main script in CMD/ENTRYPOINT. + * HTTP route handlers (for express/koa) detected by patterns (e.g., `app.get("/...")`). +6. Map vulnerable symbols: + + * From `VulnerabilitySignature.TargetSymbolPatterns` (e.g., `express/lib/router/layer.js:handle_request`). + * Identify nodes whose `SymbolId` matches patterns. + +**Output:** + +* `CallGraph` for JS with: + + * `IsEntrypoint = true` for main and detected handlers. + * Node attributes include file path, line, component PURL. + +### 5.2 Python + +**Inputs:** + +* Site-packages paths from SBOM. +* Entrypoint script (CMD/ENTRYPOINT). +* Framework heuristics (Django, Flask) from environment variables or common entrypoints. + +**Core steps:** + +1. Discover Python interpreter chain: not needed for pure static, but useful for heuristics. +2. Parse `.py` files of: + + * App code. + * Vulnerable packages (per PURL). +3. Build module import graph (`import`, `from x import y`). +4. Function-level graph: + + * Nodes for functions, methods, class constructors. + * Edges for call expressions; conservative for dynamic calls. +5. Entrypoints: + + * Main script. + * WSGI callable (e.g., `application` in `wsgi.py`). + * Django URLconf -> view functions. +6. Map vulnerable symbols using `TargetSymbolPatterns` like `django.middleware.security.SecurityMiddleware.__call__`. + +### 5.3 PHP + +**Inputs:** + +* Web root (from container image or conventional paths `/var/www/html`, `/app/public`, etc.). +* Composer metadata (`composer.json`, `vendor/`). +* Web server config if present (optional). + +**Core steps:** + +1. Discover front controllers (e.g., `index.php`, `public/index.php`). +2. Parse PHP files (again, via Tree-sitter or any suitable parser). +3. Resolve include/require chains to build file-level inclusion graph. +4. Build function/method graph: + + * Functions, methods, class constructors. + * Calls with best-effort resolution for namespaced functions. +5. Entrypoints: + + * Front controllers and router entrypoints (e.g., Symfony, Laravel detection). +6. Map vulnerable symbols (e.g., functions in certain vendor packages, particular methods). + +--- + +## 6. Binary Analyzer & Symbolizer + +Project: `StellaOps.Scanner.Analyzers.Binary` + `Symbolization` + `Cfg`. + +### 6.1 Inputs + +* All binaries and shared libraries in: + + * `/usr/lib`, `/lib`, `/app/bin`, etc. +* SBOM link: each binary mapped to its component PURL when possible. +* Vulnerability signatures for native libs: function names, symbol names, fingerprints. + +### 6.2 Symbolization + +Module: `StellaOps.Scanner.Symbolization` + +* Detect format: ELF, PE, Mach-O. +* For ELF/Mach-O: + + * Parse symbol tables (`.symtab`, `.dynsym`). + * Parse DWARF (if present) to map functions to source files/lines. +* For PE: + + * Parse PDB (if present) or export table. +* For stripped binaries: + + * Run function boundary recovery (linear sweep + heuristic). + * Compute block/fn-level hashes for fingerprinting. + +Output: + +```csharp +public record ImageFunction( + string ImageId, // e.g., SHA256 of file + ulong StartVa, + uint Size, + string? SymbolName, // demangled if possible + string FnHash, // stable hash of bytes / CFG + string? SourceFile, + int? SourceLine); +``` + +### 6.3 CFG + Call graph + +Module: `StellaOps.Scanner.Cfg` + +* Disassemble `.text` using Capstone/Iced.x86. +* Build basic blocks and CFG. +* Identify: + + * Direct calls (resolved). + * PLT/IAT indirections to shared libraries. +* Build `CallGraph` for binary functions: + + * Entrypoints: `main`, exported functions, Go `main.main`, etc. + * Map application functions to library functions via PLT/IAT edges. + +### 6.4 Linking vulnerabilities + +* For each vulnerability affecting a native library (e.g., OpenSSL): + + * Map to candidate binaries via SBOM + PURL. + * Within library image, find `ImageFunction`s matching: + + * `SymbolName` patterns. + * `FnHash` / `BlockFingerprints` (for precise detection). +* Determine reachability: + + * Starting from application entrypoints, traverse call graph to see if calls to vulnerable library function occur. + +--- + +## 7. Reachability Engine & Lattice (Scanner.WebService) + +Project: `StellaOps.Scanner.Reachability` + +### 7.1 Inputs to engine + +* Combined `CallGraph[]` (per language + binary). +* Vulnerability list (CVE, GHSA, etc.) with affected PURLs. +* Vulnerability signatures. +* Entrypoint hints: + + * Container CMD/ENTRYPOINT. + * Detected HTTP handlers, WSGI/PSGI entrypoints, etc. + +### 7.2 Algorithm steps + +1. **Entrypoint expansion** + + * Identify all `CallGraphNode` with `IsEntrypoint=true`. + * Add language-specific “framework entrypoints” (e.g., Express route dispatch, Django URL dispatch) when detected. + +2. **Graph traversal** + + * For each entrypoint node: + + * BFS/DFS through edges. + * Maintain `reachable` bit on each node. + * For dynamic edges: + + * Conservative: if target cannot be resolved, mark affected path as partially unknown and downgrade confidence. + +3. **Vuln symbol resolution** + + * For each vulnerability: + + * For each vulnerable component PURL found in SBOM: + + * Find candidate nodes whose `SymbolId` matches `TargetSymbolPatterns` / binary fingerprints. + * If none found: + + * `FunctionNotPresent` (if component version range indicates vulnerable but we cannot find symbol – low confidence). + * If found: + + * Check `reachable` bit: + + * If reachable by at least one entrypoint, `PresentReachable`. + * Else, `PresentNotReachable`. + +4. **Confidence computation** + + * Start from: + + * `1.0` for direct match with explicit function name & static call. + * Lower for: + + * Heuristic framework entrypoints. + * Dynamic calls. + * Fingerprint-only matches on stripped binaries. + * Example rule-of-thumb: + + * direct static path only: 0.95–1.0. + * dynamic edges but symbol found: 0.7–0.9. + * symbol not found but version says vulnerable: 0.4–0.6. + +5. **Lattice merge** + + * Represent each CVE+component pair as a lattice element with states: `{affected, not_affected, unknown}`. + * Reachability engine produces a **local state**: + + * `PresentReachable` → candidate `affected`. + * `PresentNotReachable` or `FunctionNotPresent` → candidate `not_affected`. + * `Unknown` → `unknown`. + * Merge with: + + * Upstream vendor VEX (from Concelier). + * Policy overrides (e.g., “treat certain CVEs as affected unless vendor says otherwise”). + * Final state computed here (Scanner.WebService), not in Concelier or VEXer. + +6. **Evidence output** + + * For each vulnerability: + + * Emit `ReachabilityEvidence` with: + + * Status. + * Confidence. + * Method. + * Example entrypoint paths (for UX and audit). + * Persist this evidence alongside regular scan results. + +--- + +## 8. Integration with SBOM & VEX + +### 8.1 SBOM annotation + +* Extend SBOM documents (CycloneDX / SPDX) with extra properties: + + * CycloneDX: + + * `component.properties`: + + * `stellaops:reachability:status` = `present_reachable|present_not_reachable|function_not_present|unknown` + * `stellaops:reachability:confidence` = `0.0-1.0` + * SPDX: + + * `Annotation` or `ExternalRef` with similar metadata. + +### 8.2 OpenVEX generation + +Module: `StellaOps.Vexer.Adapter.Reachability` + +* For each `(vuln, component)` pair: + + * Map to VEX statement: + + * If `PresentReachable`: + + * `status: affected` + * `justification: component_not_fixed` or similar. + * If `PresentNotReachable`: + + * `status: not_affected` + * `justification: function_not_reachable` + * If `FunctionNotPresent`: + + * `status: not_affected` + * `justification: component_not_present` or `function_not_present` + * If `Unknown`: + + * `status: under_investigation` (configurable). + +* Attach evidence via: + + * `analysis` / `details` fields (link to internal evidence JSON or audit link). + +* VEXer does not recalculate reachability; it uses the already computed decision + evidence. + +--- + +## 9. Executable Containers & Offline Operation + +### 9.1 Executable containers + +* Analyzers run inside a dedicated Scanner worker container that has: + + * .NET 10 runtime. + * Language runtimes if needed for parsing (Node, Python, PHP), or Tree-sitter-based parsing. +* Target image filesystem is mounted read-only under `/mnt/rootfs`. +* No network access (offline/air-gap). +* This satisfies “we will use executable containers” while keeping separation between: + + * Target image (mount only). + * Analyzer container (StellaOps code). + +### 9.2 Offline signature bundles + +* Concelier periodically exports: + + * Vulnerability database (CSAF/NVD). + * Vulnerability Signature Bank. +* Bundles are: + + * DSSE-signed. + * Versioned (e.g., `signatures-2025-11-01.tar.zst`). +* Scanner uses: + + * The bundle digest as part of the **Scan Manifest** for deterministic replay. + +--- + +## 10. Determinism & Caching + +### 10.1 Layer-level caching + +* Key: `layerDigest + analyzerVersion + signatureBundleVersion`. +* Cache artifacts: + + * CallGraph(s) per layer (for JS/Python/PHP code present in that layer). + * Symbolization results per binary file hash. +* For images sharing layers: + + * Merge cached graphs instead of re-analyzing. + +### 10.2 Deterministic scan manifest + +For each scan, produce: + +```json +{ + "imageRef": "registry/app:1.2.3", + "imageDigest": "sha256:...", + "scannerVersion": "1.4.0", + "analyzerVersions": { + "js": "1.0.0", + "python": "1.0.0", + "php": "1.0.0", + "binary": "1.0.0" + }, + "signatureBundleDigest": "sha256:...", + "callGraphDigest": "sha256:...", // canonical JSON hash + "reachabilityEvidenceDigest": "sha256:..." +} +``` + +This manifest can be signed (Authority module) and used for audits and replay. + +--- + +## 11. Implementation Roadmap (Phased) + +### Phase 0 – Infrastructure & Binary presence + +**Duration:** 1 sprint + +* Set up `Scanner.Reachability` core types and interfaces. +* Implement: + + * Basic Symbolizer for ELF + DWARF. + * Binary function catalog without CFG. +* Link a small set of CVEs to binary function presence via `SymbolName`. +* Expose minimal evidence: + + * `PresentReachable`/`FunctionNotPresent` based only on presence (no call graph). +* Integrate with VEXer to emit `function_not_present` justifications. + +**Success criteria:** + +* For selected demo images with known vulnerable/ patched OpenSSL, scanner can: + + * Distinguish images where vulnerable function is present vs. absent. + * Emit OpenVEX with correct `not_affected` when patched. + +--- + +### Phase 1 – JS/Python/PHP call graphs & basic reachability + +**Duration:** 1–2 sprints + +* Implement: + + * `Scanner.Analyzers.JavaScript` with module + function call graph. + * `Scanner.Analyzers.Python` and `Scanner.Analyzers.Php` with basic graphs. +* Entrypoint detection: + + * JS: main script from CMD, basic HTTP handlers. + * Python: main script + Django/Flask heuristics. + * PHP: front controllers. +* Implement core reachability algorithm (BFS/DFS). +* Implement simple `VulnerabilitySignature` that uses function names and file paths. +* Hook lattice engine in Scanner.WebService and integrate with: + + * Concelier vulnerability feeds. + * VEXer. + +**Success criteria:** + +* For demo apps (Node, Django, Laravel): + + * Identify vulnerable functions and mark them reachable/unreachable. + * Demonstrate noise reduction (some CVEs flagged as `not_affected`). + +--- + +### Phase 2 – Binary CFG & Fingerprinting, Improved Confidence + +**Duration:** 1–2 sprints + +* Extend Symbolizer & CFG for: + + * Stripped binaries (function hashing). + * Shared libraries (PLT/IAT resolution). +* Implement `VulnerabilitySignature.BlockFingerprints` to distinguish patched vs vulnerable binary functions. +* Refine confidence scoring: + + * Use fingerprint match quality. + * Consider presence/absence of debug info. +* Expand coverage: + + * glibc, curl, zlib, OpenSSL, libxml2, etc. + +**Success criteria:** + +* For curated images: + + * Confirm ability to differentiate patched vs vulnerable versions even when binaries are stripped. + * Reachability reflects true call paths across app→lib boundaries. + +--- + +### Phase 3 – Runtime hooks (optional), UX, and Hardening + +**Duration:** 2+ sprints + +* Add opt-in runtime confirmation: + + * eBPF probes for function hits (Linux). + * Map runtime addresses back to `ImageFunction` via symbolization. +* Enhance console UX: + + * Path explorer UI: show entrypoint → … → vulnerable function path. + * Evidence view with hash-based proofs. +* Hardening: + + * Performance optimization for large images (parallel analysis, caching). + * Conservative fallbacks for dynamic language features. + +**Success criteria:** + +* For selected environments where runtime is allowed: + + * Static reachability is confirmed by runtime traces in majority of cases. + * No significant performance regression on typical images. + +--- + +## 12. How this satisfies your initial bullets + +From your initial requirements: + +1. **JavaScript, Python, PHP, binary** + → Dedicated analyzers per language + binary symbolization/CFG, unified in `Scanner.Reachability`. + +2. **Executable containers** + → Analyzers run inside Scanner’s worker container, mounting the target image rootfs; no network access. + +3. **Libraries usage call graph** + → Call graphs map from entrypoints → app code → library functions; SBOM + PURLs tie functions to libraries. + +4. **Reachability analysis** + → BFS/DFS from entrypoints over per-language and binary graphs, with lattice-based merging in `Scanner.WebService`. + +5. **JSON + PURLs** + → All evidence is JSON with PURL-tagged components; SBOM is annotated, and VEX statements reference those PURLs. + +--- + +If you like, next step can be: I draft concrete C# interface definitions (including some initial Tree-sitter integration stubs for JS/Python/PHP) and a skeleton of the `ReachabilityPlan` and `ReachabilityEngine` classes that you can drop into the monorepo. diff --git a/docs/product-advisories/18-Nov-2026 - 1 copy 5.md b/docs/product-advisories/18-Nov-2026 - 1 copy 5.md new file mode 100644 index 000000000..124ac5727 --- /dev/null +++ b/docs/product-advisories/18-Nov-2026 - 1 copy 5.md @@ -0,0 +1,719 @@ + +Here’s a crisp idea you can drop straight into Stella Ops: treat “unknowns” as first‑class data, not noise. + +--- + +# Unknowns Registry — turning uncertainty into signals + +**Why:** Scanners and VEX feeds miss things (ambiguous package IDs, unverifiable hashes, orphaned layers, missing SBOM edges, runtime-only artifacts). Today these get logged and forgotten. If we **structure** them, downstream agents can reason about risk and shrink blast radius proactively. + +**What it is:** A small service + schema that records every uncertainty with enough context for later inference. + +## Core model (v0) + +```json +{ + "unknown_id": "unk:sha256:…", + "observed_at": "2025-11-18T12:00:00Z", + "provenance": { + "source": "Scanner.Analyzer.DotNet|Sbomer|Signals|Vexer", + "host": "runner-42", + "scan_id": "scan:…" + }, + "scope": { + "artifact": { "type": "oci.image", "ref": "registry/app@sha256:…" }, + "subpath": "/app/bin/Contoso.dll", + "phase": "build|scan|runtime" + }, + "unknown_type": "identity_gap|version_conflict|hash_mismatch|missing_edge|runtime_shadow|policy_undecidable", + "evidence": { + "raw": "nuget id 'Serilog' but assembly name 'Serilog.Core'", + "signals": ["sym:Serilog.Core.Logger", "procopen:/app/agent"] + }, + "transitive": { + "depth": 2, + "parents": ["pkg:nuget/Serilog@?"], + "children": [] + }, + "confidence": { "p": 0.42, "method": "bayes-merge|rule" }, + "exposure_hints": { + "surface": ["logging pipeline", "startup path"], + "runtime_hits": 3 + }, + "status": "open|triaged|suppressed|resolved", + "labels": ["reachability:possible", "sbom:incomplete"] +} +``` + +## Categorize by three axes + +* **Provenance** (where it came from): Scanner vs Sbomer vs Vexer vs Signals. +* **Scope** (what it touches): image/layer/file/symbol/runtime‑proc/policy. +* **Transitive depth** (how far from an entry point): 0 = direct, 1..N via deps. + +## How agents use it + +* **Cartographer**: includes unknown edges in the graph with special weight; lets Policy/Lattice down‑rank vulnerable nodes near high‑impact unknowns. +* **Remedy Assistant (Zastava)**: proposes micro‑probes (“add EventPipe/JFR tap for X symbol”) or build‑time assertions (“pin Serilog>=3.1, regenerate SBOM”). +* **Scheduler**: prioritizes scans where unknown density × asset criticality is highest. + +## Minimal API (idempotent, additive) + +* `POST /unknowns/ingest` — upsert by `unknown_id` (hash of type+scope+evidence). +* `GET /unknowns?artifact=…&status=open` — list for a target. +* `POST /unknowns/:id/triage` — set status/labels, attach rationale. +* `GET /metrics` — density by artifact/namespace/unknown_type. + +*All additive; no versioning required. Repeat calls with the same payload are no‑ops.* + +## Scoring hook (into your lattice) + +* Add a **“Unknowns Pressure”** term: + `risk = base ⊕ (α * density_depth≤1) ⊕ (β * runtime_shadow) ⊕ (γ * policy_undecidable)` +* Gate “green” only if `density_depth≤1 == 0` **or** compensating controls active. + +## Storage & plumbing + +* **Store:** append‑only KV (Badger/Rocks) + Graph overlay (SQLite/Neo4j—your call). +* **Emit:** DSSE‑signed “Unknowns Attestation” per scan for replayable audits. +* **UI:** heatmap per artifact (unknowns by type × depth), drill‑down to evidence. + +## First 2‑day slice + +1. Define `unknown_type` enum + hashable `unknown_id`. +2. Wire Scanner/Sbomer/Vexer to emit unknowns (start with: identity_gap, missing_edge). +3. Persist + expose `/metrics` (density, by depth and type). +4. In Policy Studio, add the Unknowns Pressure term with default α/β/γ. + +If you want, I’ll draft the exact protobuf/JSON schema and drop a .NET 10 record types + EF model, plus a tiny CLI to query and a Grafana panel JSON. +I will treat “it” as the whole vision behind **Pushing Binary Reachability Toward True Determinism** inside Stella Ops: function-/symbol-level reachability for binaries and higher-level languages, wired into Scanner, Cartographer, Signals, and VEX. + +Below is an implementation-oriented architecture plan you can hand directly to agents. + +--- + +## 1. Scope, goals, and non-negotiable invariants + +### 1.1. Scope + +Deliver a deterministic reachability pipeline for containers that: + +1. Builds **call graphs** and **symbol usage maps** for: + + * Native binaries (ELF, PE, Mach-O) — primary for this branch. + * Scripted/VM languages later: JS, Python, PHP (as part of the same architecture). +2. Maps symbols and functions to: + + * Packages (purls). + * Vulnerabilities (CVE → symbol/function list via Concelier/VEX data). +3. Computes **deterministic reachability states** for each `(vulnerability, artifact)` pair. +4. Emits: + + * Machine-readable JSON (with `purl`s). + * Graph overlays for Cartographer. + * Inputs for the lattice/trust engine and VEXer/Excitor. + +### 1.2. Invariants + +* **Deterministic replay**: Given the same: + + * Image digest(s), + * Analyzer versions, + * Config + policy, + * Runtime trace inputs (if any), + the same reachability outputs must be produced, bit-for-bit. +* **Idempotent, additive APIs**: + + * No versioning of endpoints, only additive/optional fields. + * Same request = same response, no side effects besides storing/caching. +* **Lattice logic runs in `Scanner.WebService`**: + + * All “reachable/unreachable/unknown” and confidence merging lives in Scanner, not Concelier/Excitors. +* **Preserve prune source**: + + * Concelier and Excitors preserve provenance and do not “massage” reachability; they only consume it. +* **Offline, air-gap friendly**: + + * No mandatory external calls; dependency on local analyzers and local advisory/VEX cache. + +--- + +## 2. High-level pipeline + +From container image to reachability output: + +1. **Image enumeration** + `Scanner.WebService` receives an image ref or tarball and spawns an analysis run. +2. **Binary discovery & classification** + Binary analyzers detect ELF/PE/Mach-O + main interpreters (python, node, php) and scripts. +3. **Symbolization & call graph building** + + * For each binary/module, we produce: + + * Symbol table (exported + imported). + * Call graph edges (function-level where possible). + * For dynamic languages, we later plug in appropriate analyzers. +4. **Symbol→package mapping** + + * Match symbols to packages and `purl`s using: + + * Known vendor symbol maps (from Concelier / Feedser). + * Heuristics, path patterns, build IDs. +5. **Vulnerability→symbol mapping** + + * From Concelier/VEX/CSAF: map each CVE to the set of symbols/functions it affects. +6. **Reachability solving** + + * For each `(CVE, artifact)`: + + * Determine presence and reachability of affected symbols from known entrypoints. + * Merge static call graph and runtime signals (if available) via deterministic lattice. +7. **Output & storage** + + * Reachability JSON with purls and confidence. + * Graph overlay into Cartographer. + * Signals/events for downstream scoring. + * DSSE-signed reachability attestation for replay/audit. + +--- + +## 3. Component architecture + +### 3.1. New and extended services + +1. **`StellaOps.Scanner.WebService` (extended)** + + * Orchestration of reachability analyses. + * Lattice/merging engine. + * Idempotent reachability APIs. + +2. **`StellaOps.Scanner.Analyzers.Binary.*` (new)** + + * `…Binary.Discovery`: file type detection, ELF/PE/Mach-O parsing. + * `…Binary.Symbolizer`: resolves symbols, imports/exports, relocations. + * `…Binary.CallGraph.Native`: builds call graphs where possible (via disassembly/CFG). + * `…Binary.CallGraph.DynamicStubs`: heuristics for indirect calls, PLT/GOT, vtables. + +3. **`StellaOps.Scanner.Analyzers.Script.*` (future extension)** + + * `…Lang.JavaScript.CallGraph` + * `…Lang.Python.CallGraph` + * `…Lang.Php.CallGraph` + * These emit the same generic call-graph IR. + +4. **`StellaOps.Reachability.Engine` (within Scanner.WebService)** + + * Normalizes all call graphs into a common IR. + * Merges static and dynamic evidence. + * Computes reachability states and scores. + +5. **`StellaOps.Cartographer.ReachabilityOverlay` (new overlay module)** + + * Stores per-artifact call graphs and reachability tags. + * Provides graph queries for UI and policy tools. + +6. **`StellaOps.Signals` (extended)** + + * Ingests runtime call traces (e.g., from EventPipe/JFR/ebpf in other branches). + * Feeds function-hit events into the Reachability Engine. + +7. **Unknowns Registry integration (optional but recommended)** + + * Stores unresolved symbol/package mappings and incomplete edges as `unknowns`. + * Used to adjust risk scores (“Unknowns Pressure”) when binary analysis is incomplete. + +--- + +## 4. Detailed design by layer + +### 4.1. Static analysis layer (binaries) + +#### 4.1.1. Binary discovery + +Module: `StellaOps.Scanner.Analyzers.Binary.Discovery` + +* Inputs: + + * Per-image file list (from existing Scanner). + * Byte slices of candidate binaries. +* Logic: + + * Detect ELF/PE/Mach-O via magic bytes, not extensions. + * Classify as: + + * Main executable + * Shared library + * Plugin/module +* Output: + + * `binary_manifest.json` per image: + + ```json + { + "image_ref": "registry/app@sha256:…", + "binaries": [ + { + "id": "bin:elf:/usr/local/bin/app", + "path": "/usr/local/bin/app", + "format": "elf", + "arch": "x86_64", + "role": "executable" + } + ] + } + ``` + +#### 4.1.2. Symbolization + +Module: `StellaOps.Scanner.Analyzers.Binary.Symbolizer` + +* Uses: + + * ELF/PE/Mach-O parsers (internal or third-party), no external calls. +* Output per binary: + + ```json + { + "binary_id": "bin:elf:/usr/local/bin/app", + "build_id": "buildid:abcd…", + "exports": ["pkg1::ClassA::method1", "..."], + "imports": ["openssl::EVP_EncryptInit_ex", "..."], + "sections": { "text": { "va": "0x...", "size": 12345 } } + } + ``` +* Writes unresolved symbol sets to Unknowns Registry when: + + * Imports cannot be tied to known packages or symbols. + +#### 4.1.3. Call graph construction + +Module: `StellaOps.Scanner.Analyzers.Binary.CallGraph.Native` + +* Core tasks: + + * Build control-flow graphs (CFG) for each function via: + + * Disassembly. + * Basic block detection. + * Identify direct calls (`call func`) and indirect calls (function pointers, vtables). +* IR model: + + ```json + { + "binary_id": "bin:elf:/usr/local/bin/app", + "functions": [ + { "fid": "func:app::main", "va": "0x401000", "size": 128 }, + { "fid": "func:libssl::EVP_EncryptInit_ex", "external": true } + ], + "edges": [ + { "caller": "func:app::main", "callee": "func:app::init_config", "type": "direct" }, + { "caller": "func:app::main", "callee": "func:libssl::EVP_EncryptInit_ex", "type": "import" } + ] + } + ``` +* Edge confidence: + + * `type: direct|import|indirect|heuristic` + * Used later by the lattice. + +#### 4.1.4. Entry point inference + +* Sources: + + * ELF `PT_INTERP`, PE `AddressOfEntryPoint`. + * Application-level hints (known frameworks, service main methods). + * Container metadata (CMD, ENTRYPOINT). +* Output: + + ```json + { + "binary_id": "bin:elf:/usr/local/bin/app", + "entrypoints": ["func:app::main"] + } + ``` + +> Note: For JS/Python/PHP, equivalent analyzers will later define module entrypoints (`index.js`, `wsgi_app`, `public/index.php`). + +--- + +### 4.2. Symbol-to-package and CVE-to-symbol mapping + +#### 4.2.1. Symbol→package mapping + +Module: `StellaOps.Reachability.Mapping.SymbolToPurl` + +* Inputs: + + * Binary symbolization outputs. + * Local mapping DB in Concelier (vendor symbol maps, debug info, name patterns). + * File path + container context (`/usr/lib/...`, `/site-packages/...`). +* Output: + + ```json + { + "symbol": "libssl::EVP_EncryptInit_ex", + "purl": "pkg:apk/alpine/openssl@3.1.5-r2", + "confidence": 0.93, + "method": "vendor_map+path_heuristic" + } + ``` +* Unresolved / ambiguous symbols: + + * Stored as `unknowns` of type `identity_gap`. + +#### 4.2.2. CVE→symbol mapping + +Responsibility: Concelier + its advisory ingestion. + +* For each vulnerability: + + ```json + { + "cve_id": "CVE-2025-12345", + "purl": "pkg:apk/alpine/openssl@3.1.5-r2", + "affected_symbols": [ + "libssl::EVP_EncryptInit_ex", + "libssl::EVP_EncryptUpdate" + ], + "source": "vendor_vex", + "confidence": 1.0 + } + ``` +* Reachability Engine consumes this mapping read-only. + +--- + +### 4.3. Reachability Engine + +Module: `StellaOps.Reachability.Engine` (in Scanner.WebService) + +#### 4.3.1. Core data model + +Per `(artifact, cve, purl)`: + +```json +{ + "artifact": { "type": "oci.image", "ref": "registry/app@sha256:…" }, + "cve_id": "CVE-2025-12345", + "purl": "pkg:apk/alpine/openssl@3.1.5-r2", + "symbols": [ + { + "symbol": "libssl::EVP_EncryptInit_ex", + "static_presence": "present|absent|unknown", + "static_reachability": "reachable|unreachable|unknown", + "runtime_hits": 3, + "runtime_reachability": "observed|not_observed|unknown" + } + ], + "reachability_state": "confirmed_reachable|statically_reachable|present_not_reachable|not_present|unknown", + "confidence": { + "p": 0.87, + "evidence": ["static_callgraph", "runtime_trace", "symbol_map"], + "unknowns_pressure": 0.12 + } +} +``` + +#### 4.3.2. Lattice / state machine + +Define a deterministic lattice over states: + +* `NOT_PRESENT` +* `PRESENT_NOT_REACHABLE` +* `STATICALLY_REACHABLE` +* `RUNTIME_OBSERVED` + +And “unknown” flags overlayed when evidence is missing. + +Merging rules (simplified): + +* If `NOT_PRESENT` and no conflicting evidence → `NOT_PRESENT`. +* If at least one affected symbol is on a static path from any entrypoint → `STATICALLY_REACHABLE`. +* If symbol observed at runtime → `RUNTIME_OBSERVED` (top state). +* If symbol present in binary but not on any static path → `PRESENT_NOT_REACHABLE`, unless unknown edges exist near it (then downgrade with lower confidence). +* Unknowns Registry entries near affected symbols increase `unknowns_pressure` and may push from `NOT_PRESENT` to `UNKNOWN`. + +Implementation: pure functional merge functions inside Scanner.WebService: + +```csharp +ReachabilityState Merge(ReachabilityState a, ReachabilityState b); +ReachabilityState FromEvidence(StaticEvidence s, RuntimeEvidence r, UnknownsPressure u); +``` + +#### 4.3.3. Deterministic inputs + +To guarantee replay: + +* Build **Reachability Plan Manifest** per run: + + ```json + { + "plan_id": "reach:sha256:…", + "scanner_version": "1.4.0", + "analyzers": { + "binary_discovery": "1.0.0", + "binary_symbolizer": "1.1.0", + "binary_callgraph": "1.2.0" + }, + "inputs": { + "image_digest": "sha256:…", + "runtime_trace_files": ["signals:run:2025-11-18T12:00:00Z"], + "config": { + "assume_indirect_calls": "conservative", + "max_call_depth": 10 + } + } + } + ``` +* DSSE-sign the plan + result. + +--- + +### 4.4. Storage and graph overlay + +#### 4.4.1. Reachability store + +Backend: re-use existing Scanner/Cartographer storage stack (e.g., Postgres or SQLite + blob store). + +Tables/collections: + +* `reachability_runs` + + * `plan_id`, `image_ref`, `created_at`, `scanner_version`. + +* `reachability_results` + + * `plan_id`, `cve_id`, `purl`, `state`, `confidence_p`, `unknowns_pressure`, `payload_json`. + +* Indexes on `(image_ref, cve_id)`, `(image_ref, purl)`. + +#### 4.4.2. Cartographer overlay + +Edges: + +* `IMAGE` → `BINARY` → `FUNCTION` → `PACKAGE` → `CVE` +* Extra property on `IMAGE -[AFFECTED_BY]-> CVE`: + + * `reachability_state` + * `reachability_plan_id` + +Enables queries: + +* “Show me all CVEs with `STATICALLY_REACHABLE` in this namespace.” +* “Show me binaries with high density of reachable crypto CVEs.” + +--- + +### 4.5. APIs (idempotent, additive) + +#### 4.5.1. Trigger reachability + +`POST /reachability/runs` + +Request: + +```json +{ + "artifact": { "type": "oci.image", "ref": "registry/app@sha256:…" }, + "config": { + "include_languages": ["binary"], + "max_call_depth": 10, + "assume_indirect_calls": "conservative" + } +} +``` + +Response: + +```json +{ "plan_id": "reach:sha256:…" } +``` + +* Idempotent key: `(image_ref, config_hash)`. Subsequent calls return same `plan_id`. + +#### 4.5.2. Fetch results + +`GET /reachability/runs/:plan_id` + +```json +{ + "plan": { /* reachability plan manifest */ }, + "results": [ + { + "cve_id": "CVE-2025-12345", + "purl": "pkg:apk/alpine/openssl@3.1.5-r2", + "reachability_state": "static_reachable", + "confidence": { "p": 0.84, "unknowns_pressure": 0.1 } + } + ] +} +``` + +#### 4.5.3. Per-CVE view for VEXer/Excitor + +`GET /reachability/by-cve?artifact=…&cve_id=…` + +* Returns filtered result for downstream VEX creation. + +All APIs are **read-only** except for the side effect of storing/caching runs. + +--- + +## 5. Interaction with other Stella Ops modules + +### 5.1. Concelier + +* Provides: + + * CVE→purl→symbol mapping. + * Vendor VEX statements indicating affected functions. +* Consumes: + + * Nothing from reachability directly; Scanner/WebService passes reachability summary to VEXer/Excitor which merges with vendor statements. + +### 5.2. VEXer / Excitor + +* Input: + + * For each `(artifact, cve)`: + + * Reachability state. + * Confidence. +* Logic: + + * Translate states to VEX statements: + + * `NOT_PRESENT` → `not_affected` + * `PRESENT_NOT_REACHABLE` → `not_affected` (with justification “code not reachable according to analysis”) + * `STATICALLY_REACHABLE` → `affected` + * `RUNTIME_OBSERVED` → `affected` (higher severity) + * Attach determinism proof: + + * Plan ID + DSSE of reachability run. + +### 5.3. Signals + +* Provides: + + * Function hit events: `(binary_id, function_id, timestamp)` aggregated per image. +* Reachability Engine: + + * Marks `runtime_hits` and state `RUNTIME_OBSERVED` for symbols with hits. +* Unknowns: + + * If runtime sees hits in functions with no static edges to entrypoints (or unmapped symbols), these produce Unknowns and increase `unknowns_pressure`. + +### 5.4. Unknowns Registry + +* From reachability pipeline, create Unknowns when: + + * Symbol→package mapping is ambiguous. + * CVE→symbol mapping exists, but symbol cannot be found in binaries. + * Call graph has indirect calls that cannot be resolved. +* The “Unknowns Pressure” term is fed into: + + * Reachability confidence. + * Global risk scoring (Trust Algebra Studio). + +--- + +## 6. Implementation phases and engineering plan + +### Phase 0 – Scaffolding & manifests (1 sprint) + +* Create: + + * `StellaOps.Reachability.Engine` skeleton. + * Reachability Plan Manifest schema. + * Reachability Run + Result persistence. +* Add `/reachability/runs` and `/reachability/runs/:plan_id` endpoints, returning mock data. +* Wire DSSE attestation generation for reachability results (even if payload is empty). + +### Phase 1 – Binary discovery + symbolization (1–2 sprints) + +* Implement `Binary.Discovery` and `Binary.Symbolizer`. +* Feed symbol tables into Reachability Engine as “presence-only evidence”: + + * States: `NOT_PRESENT` vs `PRESENT_NOT_REACHABLE` vs `UNKNOWN`. +* Integrate with Concelier’s CVE→purl mapping (no symbol-level yet): + + * For CVEs affecting a package present in the image, mark as `PRESENT_NOT_REACHABLE`. +* Emit Unknowns for unresolved binary roles and ambiguous package mapping. + +Deliverable: package-level reachability with deterministic manifests. + +### Phase 2 – Binary call graphs & entrypoints (2–3 sprints) + +* Implement `Binary.CallGraph.Native`: + + * CFG + direct call edges. +* Implement entrypoint inference from binary + container ENTRYPOINT/CMD. +* Add static reachability algorithm: + + * DFS/BFS from entrypoints through call graph. + * Mark affected symbols as reachable if found on paths. +* Extend Concelier to ingest symbol-aware vulnerability metadata (for pilots; can be partial). + +Deliverable: function-level static reachability for native binaries where symbol maps exist. + +### Phase 3 – Runtime integration (2 sprints, may be in parallel workstream) + +* Integrate Signals runtime evidence: + + * Define schema for function hit events. + * Add ingestion path into Reachability Engine. +* Update lattice: + + * Promote symbols to `RUNTIME_OBSERVED` when hits exist. +* Extend DSSE attestation to reference runtime evidence URIs (hashes of trace inputs). + +Deliverable: static + runtime-confirmed reachability. + +### Phase 4 – Unknowns & pressure (1 sprint) + +* Wire Unknowns Registry: + + * Emit unknowns from Symbolizer and CallGraph (identity gaps, missing edges). + * Compute `unknowns_pressure` per `(artifact, cve)` as density of unknowns near affected symbols. +* Adjust confidence calculation in Reachability Engine. +* Expose unknowns metrics in API and Cartographer. + +Deliverable: explicit modelling of uncertainty, feeding into trust/lattice. + +### Phase 5 – Language extensions (JS/Python/PHP) (ongoing) + +* Implement per-language call-graph analyzers creating the same IR as binary. +* Extend symbol→purl mapping for these ecosystems (npm, PyPI, Packagist). +* Update reachability solver to include multi-language edges (e.g., Python calling into native modules). + +--- + +## 7. Minimal contracts for agents + +To hand off to agents, you can codify: + +1. **IR schemas** + + * Call graph IR. + * Reachability Result JSON. + * Reachability Plan Manifest. +2. **API contracts** + + * `POST /reachability/runs` + * `GET /reachability/runs/:plan_id` + * `GET /reachability/by-cve` +3. **Module boundaries** + + * `Scanner.Analyzers.Binary.*` produce IR only; NO network calls. + * `Reachability.Engine` is the only place where lattice logic lives. + * `Concelier` is read-only for reachability; no custom logic there. +4. **Determinism practices** + + * All algorithmic randomness is banned; where unavoidable, seed with values derived from plan_id. + * All external inputs must be listed in the Plan Manifest. + +If you like, next step I can draft: + +* Concrete C# record types for the IRs. +* A small pseudo-code implementation of the lattice functions and static reachability DFS. +* A proposed directory layout under `src/StellaOps.Scanner` and `src/StellaOps.Cartographer`. diff --git a/docs/product-advisories/18-Nov-2026 - 1 copy.md b/docs/product-advisories/18-Nov-2026 - 1 copy.md new file mode 100644 index 000000000..f353f445f --- /dev/null +++ b/docs/product-advisories/18-Nov-2026 - 1 copy.md @@ -0,0 +1,635 @@ + +Here’s a simple, cheap way to sanity‑check your vuln function recovery without fancy ground truth: **build “patch oracles.”** + +--- + +### What it is (in plain words) + +Take a known CVE and compile two **tiny** binaries from the same source: + +* **Vulnerable** commit/revision +* **Fixed** commit/revision + Then diff the discovered functions + call edges between the two. If your analyzer can’t see the symbol (or guard) the patch adds/removes/tightens, your recall is suspect. + +--- + +### Why it works + +Patches for real CVEs usually: + +* add/remove a **function** (e.g., `validate_len`) +* change a **call site** (new guard before `memcpy`) +* tweak **control flow** (early return on bounds check) + +Those are precisely the things your function recovery / call‑graph pass should surface—even on stripped ELFs. If they don’t move in your graph, you’ve got blind spots. + +--- + +### Minimal workflow (5 steps) + +1. **Pick a CVE** with a clean, public fix (e.g., OpenSSL/zlib/busybox). +2. **Isolate the patch** (git range or cherry‑pick) and craft a *tiny harness* that calls the affected code path. +3. **Build both** with the same toolchain/flags; produce **stripped** ELFs (`-s`) to mimic production. +4. **Run your discovery** on both: + + * function list, demangled where possible + * call edges (A→B), basic blocks (optional) +5. **Diff the graphs**: look for the new guard function, removed unsafe call, or altered edge count. + +--- + +### A tiny “oracle spec” (drop-in YAML for your test runner) + +```yaml +cve: CVE-YYYY-XXXX +target: libfoo 1.2.3 +build: + cc: clang + cflags: [-O2, -fno-omit-frame-pointer] + ldflags: [] + strip: true +evidence: + expect_functions_added: [validate_len] + expect_functions_removed: [unsafe_copy] # optional + expect_call_added: + - caller: foo_parse + callee: validate_len + expect_call_removed: + - caller: foo_parse + callee: memcpy +tolerances: + allow_unresolved_symbols: 0 + allow_extra_funcs: 2 +``` + +--- + +### Quick harness pattern (C) + +```c +// before: foo_parse -> memcpy(buf, src, len); +// after : foo_parse -> validate_len(len) -> memcpy(...) +extern int foo_parse(const char*); + +int main(int argc, char** argv) { + const char* in = argc > 1 ? argv[1] : "AAAA"; + return foo_parse(in); +} +``` + +--- + +### What to flag as a failure + +* Expected **function not discovered** (e.g., `validate_len` missing). +* Expected **edge not present** (`foo_parse → validate_len` absent). +* **No CFG change** where patch clearly adds a guard/early return. + +--- + +### Where this plugs into Stella Ops + +* Put these oracles under `Scanner/tests/patch-oracles/*` per language. +* Run them in CI for **.NET/JVM/C/C++/Go/Rust** analyzers. +* Use them to gate any changes to symbolization, demangling, or call‑graph building. +* Record per‑analyzer **recall deltas** when you tweak heuristics or switch disassemblers. + +--- + +If you want, I can scaffold the first three oracles (e.g., zlib overflow fix, OpenSSL length check, BusyBox `ash` patch) with ready‑to‑run Makefiles and expected graph diffs. +Understood — let us turn the “patch oracle” idea into something you can actually drop into the Stella Ops repo and CI. + +I will walk through: + +1. How to structure this inside the monorepo +2. How to build one oracle end-to-end (C/C++ example) +3. How to do the same for .NET/JVM +4. How to automate running and asserting them +5. Practical rules and pitfalls so these stay stable and useful + +--- + +## 1. Where this lives in Stella Ops + +A simple, language-agnostic layout that will scale: + +```text +src/ + StellaOps.Scanner/ + ... # your scanner code + StellaOps.Scanner.Tests/ # existing tests (if any) + PatchOracles/ + c/ + CVE-YYYY-XXXX-/ + src/ + build.sh + oracle.yml + README.md + cpp/ + ... + dotnet/ + CVE-YYYY-XXXX-/ + src/ + build.ps1 + oracle.yml + README.md + jvm/ + ... + go/ + ... + rust/ + ... + tools/ + scanner-oracle-runner/ # tiny runner (C# console or bash) +``` + +Key principles: + +* Each CVE/test case is **self-contained** (its own folder with sources, build script, oracle.yml). +* Build scripts produce **two binaries/artifacts**: `vuln` and `fixed`. +* `oracle.yml` describes: how to build, what to scan, and what differences to expect in Scanner’s call graph/function list. + +--- + +## 2. How to build a single patch oracle (C/C++) + +Think of a patch oracle as: “Given these two binaries, Scanner must see specific changes in functions and call edges.” + +### 2.1. Step-by-step workflow + +For one C/C++ CVE: + +1. **Pick & freeze the patch** + + * Choose a small, clean CVE in a library with easily buildable code (zlib, OpenSSL, BusyBox, etc.). + * Identify commit `A` (vulnerable) and commit `B` (fixed). + * Extract only the minimal sources needed to build the affected function + a harness into `src/`. + +2. **Create a minimal harness** + +Example: patch adds `validate_len` and guards a `memcpy` in `foo_parse`. + +```c +// src/main.c +#include + +int foo_parse(const char* in); // from the library code under test + +int main(int argc, char** argv) { + const char* in = (argc > 1) ? argv[1] : "AAAA"; + return foo_parse(in); +} +``` + +Under `src/`, you keep two sets of sources: + +```text +src/ + vuln/ + foo.c # vulnerable version + api.h + main.c + fixed/ + foo.c # fixed version (adds validate_len, changes calls) + api.h + main.c +``` + +3. **Provide a deterministic build script** + +Example `build.sh`: + +```bash +#!/usr/bin/env bash +set -euo pipefail + +CC="${CC:-clang}" +CFLAGS="${CFLAGS:- -O2 -fno-omit-frame-pointer -g0}" +LDFLAGS="${LDFLAGS:- }" + +build_one() { + local name="$1" # vuln or fixed + mkdir -p build + ${CC} ${CFLAGS} src/${name}/*.c ${LDFLAGS} -o build/${name} + # Strip symbols to simulate production + strip build/${name} +} + +build_one "vuln" +build_one "fixed" +``` + +Guidelines: + +* Fix the toolchain: either run this inside a Docker image (e.g., `debian:bookworm` with specific `clang` version) or at least document required versions in `README.md`. +* Always build both artifacts with **identical flags**; the only difference should be the code change. +* Use `strip` to ensure Scanner doesn’t accidentally rely on debug symbols. + +4. **Define the oracle (what must change)** + +You define expectations based on the patch: + +* Functions added/removed/renamed. +* New call edges (e.g., `foo_parse -> validate_len`). +* Removed call edges (e.g., `foo_parse -> memcpy`). +* Optionally: new basic blocks, conditional branches, or early returns. + +A practical `oracle.yml` for this case: + +```yaml +cve: CVE-YYYY-XXXX +name: zlib_len_guard_example +language: c +toolchain: + cc: clang + cflags: "-O2 -fno-omit-frame-pointer -g0" + ldflags: "" +build: + script: "./build.sh" + artifacts: + vulnerable: "build/vuln" + fixed: "build/fixed" + +scan: + scanner_cli: "dotnet run --project ../../StellaOps.Scanner.Cli" + # If you have a Dockerized scanner, you could do: + # scanner_cli: "docker run --rm -v $PWD:/work stellaops/scanner:dev" + args: + - "--format=json" + - "--analyzers=native" + timeout_seconds: 120 + +expectations: + functions: + must_exist_in_fixed: + - name: "validate_len" + must_not_exist_in_vuln: + - name: "validate_len" + calls: + must_add: + - caller: "foo_parse" + callee: "validate_len" + must_remove: + - caller: "foo_parse" + callee: "memcpy" + tolerances: + allow_unresolved_symbols: 0 + allow_extra_functions: 5 + allow_missing_calls: 0 +``` + +5. **Connect Scanner output to the oracle** + +Assume your Scanner CLI produces something like: + +```json +{ + "binary": "build/fixed", + "functions": [ + { "name": "foo_parse", "address": "0x401000" }, + { "name": "validate_len", "address": "0x401080" }, + ... + ], + "calls": [ + { "caller": "foo_parse", "callee": "validate_len" }, + { "caller": "validate_len", "callee": "memcpy" } + ] +} +``` + +Your oracle-runner will: + +* Run scanner on `vuln` → `vuln.json` +* Run scanner on `fixed` → `fixed.json` +* Compare each expectation in `oracle.yml` against `vuln.json` and `fixed.json` + +Pseudo-logic for a function expectation: + +```csharp +bool HasFunction(JsonElement doc, string name) => + doc.GetProperty("functions") + .EnumerateArray() + .Any(f => f.GetProperty("name").GetString() == name); + +bool HasCall(JsonElement doc, string caller, string callee) => + doc.GetProperty("calls") + .EnumerateArray() + .Any(c => + c.GetProperty("caller").GetString() == caller && + c.GetProperty("callee").GetString() == callee); +``` + +The runner will produce a small report, per oracle: + +```text +[PASS] CVE-YYYY-XXXX zlib_len_guard_example + + validate_len appears only in fixed → OK + + foo_parse → validate_len call added → OK + + foo_parse → memcpy call removed → OK +``` + +If anything fails, it prints the mismatches and exits with non-zero code so CI fails. + +--- + +## 3. Implementing the oracle runner (practical variant) + +You can implement this either as: + +* A standalone C# console (`StellaOps.Scanner.PatchOracleRunner`), or +* A set of xUnit tests that read `oracle.yml` and run dynamically. + +### 3.1. Console runner skeleton (C#) + +High-level structure: + +```text +src/tools/scanner-oracle-runner/ + Program.cs + Oracles/ + (symlink or reference to src/StellaOps.Scanner.Tests/PatchOracles) +``` + +Core responsibilities: + +1. Discover all `oracle.yml` files under `PatchOracles/`. +2. For each: + + * Run the `build` script. + * Run the scanner on both artifacts. + * Evaluate expectations. +3. Aggregate results and exit with appropriate status. + +Pseudo-code outline: + +```csharp +static int Main(string[] args) +{ + var root = args.Length > 0 ? args[0] : "src/StellaOps.Scanner.Tests/PatchOracles"; + var oracleFiles = Directory.GetFiles(root, "oracle.yml", SearchOption.AllDirectories); + var failures = new List(); + + foreach (var oracleFile in oracleFiles) + { + var result = RunOracle(oracleFile); + if (!result.Success) + { + failures.Add($"{result.Name}: {result.FailureReason}"); + } + } + + if (failures.Any()) + { + Console.Error.WriteLine("Patch oracle failures:"); + foreach (var f in failures) Console.Error.WriteLine(" - " + f); + return 1; + } + + Console.WriteLine("All patch oracles passed."); + return 0; +} +``` + +`RunOracle` does: + +* Deserialize YAML (e.g., via `YamlDotNet`). +* `Process.Start` for `build.script`. +* `Process.Start` for `scanner_cli` twice (vuln/fixed). +* Read/parse JSON outputs. +* Run checks `functions.must_*` and `calls.must_*`. + +This is straightforward plumbing code; once built, adding a new patch oracle is just adding a folder + `oracle.yml`. + +--- + +## 4. Managed (.NET / JVM) patch oracles + +Exact same concept, slightly different mechanics. + +### 4.1. .NET example + +Directory: + +```text +PatchOracles/ + dotnet/ + CVE-2021-XXXXX-systemtextjson/ + src/ + vuln/ + Example.sln + Api/... + fixed/ + Example.sln + Api/... + build.ps1 + oracle.yml +``` + +`build.ps1` (PowerShell, simplified): + +```powershell +param( + [string]$Configuration = "Release" +) + +$ErrorActionPreference = "Stop" + +function Build-One([string]$name) { + Push-Location "src/$name" + dotnet clean + dotnet publish -c $Configuration -p:DebugType=None -p:DebugSymbols=false -o ../../build/$name + Pop-Location +} + +New-Item -ItemType Directory -Force -Path "build" | Out-Null + +Build-One "vuln" +Build-One "fixed" +``` + +`oracle.yml`: + +```yaml +cve: CVE-2021-XXXXX +name: systemtextjson_escape_fix +language: dotnet +build: + script: "pwsh ./build.ps1" + artifacts: + vulnerable: "build/vuln/Api.dll" + fixed: "build/fixed/Api.dll" + +scan: + scanner_cli: "dotnet run --project ../../StellaOps.Scanner.Cli" + args: + - "--format=json" + - "--analyzers=dotnet" + timeout_seconds: 120 + +expectations: + methods: + must_exist_in_fixed: + - "Api.JsonHelper::EscapeString" + must_not_exist_in_vuln: + - "Api.JsonHelper::EscapeString" + calls: + must_add: + - caller: "Api.Controller::Handle" + callee: "Api.JsonHelper::EscapeString" + tolerances: + allow_missing_calls: 0 + allow_extra_methods: 10 +``` + +Scanner’s .NET analyzer should produce method identifiers in a stable format (e.g., `Namespace.Type::Method(Signature)`), which you then use in the oracle. + +### 4.2. JVM example + +Similar structure, but artifacts are JARs: + +```yaml +build: + script: "./gradlew :app:assemble" + artifacts: + vulnerable: "app-vuln.jar" + fixed: "app-fixed.jar" + +scan: + scanner_cli: "dotnet run --project ../../StellaOps.Scanner.Cli" + args: + - "--format=json" + - "--analyzers=jvm" +``` + +Expectations then refer to methods like `com.example.JsonHelper.escapeString:(Ljava/lang/String;)Ljava/lang/String;`. + +--- + +## 5. Wiring into CI + +You can integrate this in your existing pipeline (GitLab Runner / Gitea / etc.) as a separate job. + +Example CI job skeleton (GitLab-like YAML for illustration): + +```yaml +patch-oracle-tests: + stage: test + image: mcr.microsoft.com/dotnet/sdk:10.0 + script: + - dotnet build src/StellaOps.Scanner/StellaOps.Scanner.csproj -c Release + - dotnet build src/tools/scanner-oracle-runner/scanner-oracle-runner.csproj -c Release + - dotnet run --project src/tools/scanner-oracle-runner/scanner-oracle-runner.csproj -- \ + src/StellaOps.Scanner.Tests/PatchOracles + artifacts: + when: on_failure + paths: + - src/StellaOps.Scanner.Tests/PatchOracles/**/build + - oracle-results.log +``` + +You can also: + +* Tag the job (e.g., `oracle` or `reachability`) so you can run it nightly or on changes to Scanner analyzers. +* Pin Docker images with the exact C/C++/Java toolchains used by patch oracles so results are deterministic. + +--- + +## 6. Practical guidelines and pitfalls + +Here are concrete rules of thumb for making this robust: + +### 6.1. Choosing good CVE oracles + +Prefer cases where: + +* The patch clearly adds/removes a **function** or **method**, or introduces a separate helper such as `validate_len`, `check_bounds`, etc. +* The patch adds/removes a **call** that is easy to see even under optimization (e.g., non-inline, non-template). +* The project is easy to build and not heavily reliant on obscure toolchains. + +For each supported language in Scanner, target: + +* 3–5 small C or C++ oracles. +* 3–5 .NET or JVM oracles. +* 1–3 for Go and Rust once those analyzers exist. + +You do not need many; you want **sharp, surgical tests**, not coverage. + +### 6.2. Handle inlining and optimization + +Compilers may inline small functions; this can break naive “must have call edge” expectations. + +Mitigations: + +* Choose functions that are “large enough” or mark them `__attribute__((noinline))` (GCC/Clang) in your test harness code if necessary. +* Alternatively, relax expectations using `should_add` vs `must_add` for some edges: + +```yaml +calls: + must_add: [] + should_add: + - caller: "foo_parse" + callee: "validate_len" +``` + +In the runner, `should_add` failures can mark the oracle as “degraded” but not fatal, while `must_*` failures break the build. + +### 6.3. Keep oracles stable over time + +To avoid flakiness: + +* **Vendor sources** into the repo (or at least snapshot the patch) so upstream changes do not affect builds. +* Pin toolchain versions in Docker images for CI. +* Capture and pin scanner configuration: analyzers enabled, rules, version. If you support “deterministic scan manifests” later, these oracles are perfect consumers of that. + +### 6.4. What to assert beyond functions/calls + +When your Scanner gets more advanced, you can extend `oracle.yml`: + +```yaml +cfg: + must_increase_blocks: + - function: "foo_parse" + must_add_branch_on: + - function: "foo_parse" + operand_pattern: "len <= MAX_LEN" +``` + +Initially, I would keep it to: + +* Function presence/absence +* Call edges presence/absence + +and add CFG assertions only when your analyzers and JSON model for CFG stabilize. + +### 6.5. How to use failures + +When a patch oracle fails, it is a **signal** that either: + +* A change in Scanner or a new optimization pattern created a blind spot, or +* The oracle is too strict (e.g., relying on a call that got inlined). + +You then: + +1. Inspect the disassembly / Scanner JSON for `vuln` and `fixed`. +2. Decide if Scanner is wrong (fix analyzer) or oracle is too rigid (relax to `should_*`). +3. Commit both the code change and updated oracle (if needed) in the same merge request. + +--- + +## 7. Minimal checklist for adding a new patch oracle + +For your future self and your agents, here is a compressed checklist: + +1. Select CVE + patch; copy minimal affected sources into `src/…///src/{vuln,fixed}`. +2. Add a tiny harness that calls the patched code path. +3. Write `build.sh` / `build.ps1` to produce `build/vuln` and `build/fixed` artifacts, stripped/Release. +4. Run manual `scanner` on both artifacts once; inspect JSON to find real symbol names and call edges. +5. Create `oracle.yml` with: + + * `build.script` and `artifacts.*` paths + * `scan.scanner_cli` + args + * `expectations.functions.*` and `expectations.calls.*` +6. Run `scanner-oracle-runner` locally; fix any mismatches or over-strict expectations. +7. Commit and ensure CI job `patch-oracle-tests` runs and must pass on MR. + +If you wish, next step we can design the actual JSON schema that Scanner should emit for function/call graphs and write a first C# implementation of `scanner-oracle-runner` aligned with that schema. diff --git a/docs/product-advisories/18-Nov-2026 - 1.md b/docs/product-advisories/18-Nov-2026 - 1.md new file mode 100644 index 000000000..f594c37d5 --- /dev/null +++ b/docs/product-advisories/18-Nov-2026 - 1.md @@ -0,0 +1,784 @@ +Here’s a clean, air‑gap‑ready spine for turning container images into verifiable SBOMs and provenance—built to be idempotent and easy to slot into Stella Ops or any CI/CD. + +```mermaid +flowchart LR + A[OCI Image/Repo]-->B[Layer Extractor] + B-->C[Sbomer: CycloneDX/SPDX] + C-->D[DSSE Sign] + D-->E[in-toto Statement (SLSA Provenance)] + E-->F[Transparency Log Adapter] + C-->G[POST /sbom/ingest] + F-->H[POST /attest/verify] +``` + +### What this does (in plain words) + +* **Pull & crack the image** → extract layers, metadata (labels, env, history). +* **Build an SBOM** → emit **CycloneDX 1.6** and **SPDX 3.0.1** (pick one or both). +* **Sign artifacts** → wrap SBOM/provenance in **DSSE** envelopes. +* **Provenance** → generate **in‑toto Statement** with **SLSA Provenance v1** as the predicate. +* **Auditability** → optionally publish attestations to a transparency log (e.g., Rekor) so they’re tamper‑evident via Merkle proofs. +* **APIs are idempotent** → safe to re‑ingest the same image/SBOM/attestation without version churn. + +### Design notes you can hand to an agent + +* **Idempotency keys** + + * `contentAddress` = SHA256 of OCI manifest (or full image digest) + * `sbomHash` = SHA256 of normalized SBOM JSON + * `attHash` = SHA256 of DSSE payload (base64‑stable) + Store these; reject duplicates with HTTP 200 + `"status":"already_present"`. + +* **Default formats** + + * SBOM export: CycloneDX v1.6 (`application/vnd.cyclonedx+json`), SPDX 3.0.1 (`application/spdx+json`) + * DSSE envelope: `application/dsse+json` + * in‑toto Statement: `application/vnd.in-toto+json` with `predicateType` = SLSA Provenance v1 + +* **Air‑gap mode** + + * No external calls required; Rekor publish is optional. + * Keep a local Merkle log (pluggable) and allow later “sync‑to‑Rekor” when online. + +* **Transparency log adapter** + + * Interface: `Put(entry) -> {logIndex, logID, inclusionProof}` + * Backends: `rekor`, `local-merkle`, `null` (no‑op) + +### Minimal API sketch + +* `POST /sbom/ingest` + + * Body: `{ imageDigest, sbom, format, dsseSignature? }` + * Returns: `{ sbomId, status, sbomHash }` (status: `stored|already_present`) +* `POST /attest/verify` + + * Body: `{ dsseEnvelope, expectedSubjects:[{name, digest}] }` + * Verifies DSSE, checks in‑toto subject ↔ image digest, optionally records/logs. + * Returns: `{ verified:true, predicateType, logIndex?, inclusionProof? }` + +### CLI flow (pseudocode) + +```bash +# 1) Extract +stella-extract --image $IMG --out /work/extract + +# 2) SBOM (Cdx + SPDX) +stella-sbomer cdx --in /work/extract --out /work/sbom.cdx.json +stella-sbomer spdx --in /work/extract --out /work/sbom.spdx.json + +# 3) DSSE sign (offline keyring or HSM) +stella-sign dsse --in /work/sbom.cdx.json --out /work/sbom.cdx.dsse.json --key file:k.pem + +# 4) SLSA provenance (in‑toto Statement) +stella-provenance slsa-v1 --subject $IMG_DIGEST --materials /work/extract/manifest.json \ + --out /work/prov.dsse.json --key file:k.pem + +# 5) (optional) Publish to transparency log +stella-log publish --in /work/prov.dsse.json --backend rekor --rekor-url $REKOR +``` + +### Validation rules (quick) + +* **Subject binding**: in‑toto Statement `subject[].digest.sha256` must equal the OCI image digest you scanned. +* **Key policy**: enforce allowed issuers (Fulcio, internal CA, GOST/SM/EIDAS/FIPS as needed). +* **Normalization**: canonicalize JSON before hashing/signing to keep idempotency stable. + +### Why this matters + +* **Audit‑ready**: You can always prove *what* you scanned, *how* it was built, and *who* signed it. +* **Noise‑gated**: With deterministic SBOMs + provenance, downstream VEX/reachability gets much cleaner. +* **Drop‑in**: Works in harsh environments—offline, mirrors, sovereign crypto stacks—without changing your pipeline. + +If you want, I can generate: + +* a ready‑to‑use OpenAPI stub for `POST /sbom/ingest` and `POST /attest/verify`, +* C# (.NET 10) DSSE + in‑toto helpers (interfaces + test fixtures), +* or a Docker‑compose “air‑gap bundle” showing the full spine end‑to‑end. +Below is a full architecture plan you can hand to an agent as the “master spec” for implementing the SBOM & provenance spine (image → SBOM → DSSE → in-toto/SLSA → transparency log → REST APIs), with idempotent APIs and air-gap readiness. + +--- + +## 1. Scope and Objectives + +**Goal:** Implement a deterministic, air-gap-ready “SBOM spine” that: + +* Converts OCI images into SBOMs (CycloneDX 1.6 and SPDX 3.0.1). +* Generates SLSA v1 provenance wrapped in in-toto Statements. +* Signs all artifacts with DSSE envelopes using pluggable crypto providers. +* Optionally publishes attestations to transparency logs (Rekor/local-Merkle/none). +* Exposes stable, idempotent APIs: + + * `POST /sbom/ingest` + * `POST /attest/verify` +* Avoids versioning by design; APIs are extended, not versioned; all mutations are idempotent keyed by content digests. + +**Out of scope (for this iteration):** + +* Full vulnerability scanning (delegated to Scanner service). +* Policy evaluation / lattice logic (delegated to Scanner/Graph engine). +* Vendor-facing proof-market ledger and trust economics (future module). + +--- + +## 2. High-Level Architecture + +### 2.1 Logical Components + +1. **StellaOps.SupplyChain.Core (Library)** + + * Shared types and utilities: + + * Domain models: SBOM, DSSE, in-toto Statement, SLSA predicates. + * Canonicalization & hashing utilities. + * DSSE sign/verify abstractions. + * Transparency log entry model & Merkle proof verification. + +2. **StellaOps.Sbomer.Engine (Library)** + + * Image → SBOM functionality: + + * Layer & manifest analysis. + * SBOM generation: CycloneDX, SPDX. + * Extraction of metadata (labels, env, history). + * Deterministic ordering & normalization. + +3. **StellaOps.Provenance.Engine (Library)** + + * Build provenance & in-toto: + + * In-toto Statement generator. + * SLSA v1 provenance predicate builder. + * Subject and material resolution from image metadata & SBOM. + +4. **StellaOps.Authority (Service/Library)** + + * Crypto & keys: + + * Key management abstraction (file, HSM, KMS, sovereign crypto). + * DSSE signing & verification with multiple key types. + * Trust roots, certificate chains, key policies. + +5. **StellaOps.LogBridge (Service/Library)** + + * Transparency log adapter: + + * Rekor backend. + * Local Merkle log backend (for air-gap). + * Null backend (no-op). + * Merkle proof validation. + +6. **StellaOps.SupplyChain.Api (Service)** + + * The SBOM spine HTTP API: + + * `POST /sbom/ingest` + * `POST /attest/verify` + * Optionally: `GET /sbom/{id}`, `GET /attest/{id}`, `GET /image/{digest}/summary`. + * Performs orchestrations: + + * SBOM/attestation parsing, canonicalization, hashing. + * Idempotency and persistence. + * Delegation to Authority and LogBridge. + +7. **CLI Tools (optional but recommended)** + + * `stella-extract`, `stella-sbomer`, `stella-sign`, `stella-provenance`, `stella-log`. + * Thin wrappers over the above libraries; usable offline and in CI pipelines. + +8. **Persistence Layer** + + * Primary DB: PostgreSQL (or other RDBMS). + * Optional object storage: S3/MinIO for large SBOM/attestation blobs. + * Tables: `images`, `sboms`, `attestations`, `signatures`, `log_entries`, `keys`. + +### 2.2 Deployment View (Kubernetes / Docker) + +```mermaid +flowchart LR + subgraph Node1[Cluster Node] + A[StellaOps.SupplyChain.Api (ASP.NET Core)] + B[StellaOps.Authority Service] + C[StellaOps.LogBridge Service] + end + + subgraph Node2[Worker Node] + D[Runner / CI / Air-gap host] + E[CLI Tools\nstella-extract/sbomer/sign/provenance/log] + end + + F[(PostgreSQL)] + G[(Object Storage\nS3/MinIO)] + H[(Local Merkle Log\nor Rekor)] + + A --> F + A --> G + A --> C + A --> B + C --> H + E --> A +``` + +* **Air-gap mode:** + + * Rekor backend disabled; LogBridge uses local Merkle log (`H`) or `null`. + * All components run within the offline network. +* **Online mode:** + + * LogBridge talks to external Rekor instance using outbound HTTPS only. + +--- + +## 3. Domain Model and Storage Design + +Use EF Core 9 with PostgreSQL in .NET 10. + +### 3.1 Core Entities + +1. **ImageArtifact** + + * `Id` (GUID/ULID, internal). + * `ImageDigest` (string; OCI digest; UNIQUE). + * `Registry` (string). + * `Repository` (string). + * `Tag` (string, nullable, since digest is canonical). + * `FirstSeenAt` (timestamp). + * `MetadataJson` (JSONB; manifest, labels, env). + +2. **Sbom** + + * `Id` (string, primary key = `SbomHash` or derived ULID). + * `ImageArtifactId` (FK). + * `Format` (enum: `CycloneDX_1_6`, `SPDX_3_0_1`). + * `ContentHash` (string; normalized JSON SHA-256; UNIQUE with `TenantId`). + * `StorageLocation` (inline JSONB or external object storage key). + * `CreatedAt`. + * `Origin` (enum: `Generated`, `Uploaded`, `ExternalVendor`). + * Unique constraint: `(TenantId, ContentHash)`. + +3. **Attestation** + + * `Id` (string, primary key = `AttestationHash` or derived ULID). + * `ImageArtifactId` (FK). + * `Type` (enum: `InTotoStatement_SLSA_v1`, `Other`). + * `PayloadHash` (hash of DSSE payload, before envelope). + * `DsseEnvelopeHash` (hash of full DSSE JSON). + * `StorageLocation` (inline JSONB or object storage). + * `CreatedAt`. + * `Issuer` (string; signer identity / certificate subject). + * Unique constraint: `(TenantId, DsseEnvelopeHash)`. + +4. **SignatureInfo** + + * `Id` (GUID/ULID). + * `AttestationId` (FK). + * `KeyId` (logical key identifier). + * `Algorithm` (enum; includes PQ & sovereign algs). + * `VerifiedAt`. + * `VerificationStatus` (enum: `Valid`, `Invalid`, `Unknown`). + * `DetailsJson` (JSONB; trust-chain, error reasons, etc.). + +5. **TransparencyLogEntry** + + * `Id` (GUID/ULID). + * `AttestationId` (FK). + * `Backend` (enum: `Rekor`, `LocalMerkle`). + * `LogIndex` (string). + * `LogId` (string). + * `InclusionProofJson` (JSONB). + * `RecordedAt`. + * Unique constraint: `(Backend, LogId, LogIndex)`. + +6. **KeyRecord** (optional if not reusing Authority’s DB) + + * `KeyId` (string, PK). + * `KeyType` (enum). + * `Usage` (enum: `Signing`, `Verification`, `Both`). + * `Status` (enum: `Active`, `Retired`, `Revoked`). + * `MetadataJson` (JSONB; KMS ARN, HSM slot, etc.). + +### 3.2 Idempotency Keys + +* SBOM: + + * `sbomHash = SHA256(canonicalJson(sbom))`. + * Uniqueness enforced by `(TenantId, sbomHash)` in DB. +* Attestation: + + * `attHash = SHA256(canonicalJson(dsse.payload))` or full envelope. + * Uniqueness enforced by `(TenantId, attHash)` in DB. +* Image: + + * `imageDigest` is globally unique (per OCI spec). + +--- + +## 4. Service-Level Architecture + +### 4.1 StellaOps.SupplyChain.Api (.NET 10, ASP.NET Core) + +**Responsibilities:** + +* Expose HTTP API for ingest / verify. +* Handle idempotency logic & persistence. +* Delegate cryptographic operations to Authority. +* Delegate transparency logging to LogBridge. +* Perform basic validation against schemas (SBOM, DSSE, in-toto, SLSA). + +**Key Endpoints:** + +1. `POST /sbom/ingest` + + * Request: + + * `imageDigest` (string). + * `sbom` (raw JSON). + * `format` (enum/string). + * Optional: `dsseSignature` or `dsseEnvelope`. + * Behavior: + + * Parse & validate SBOM structure. + * Canonicalize JSON, compute `sbomHash`. + * If `sbomHash` exists for `imageDigest` and tenant: + + * Return `200` with `{ status: "already_present", sbomId, sbomHash }`. + * Else: + + * Persist `Sbom` entity. + * Optionally verify DSSE signature via Authority. + * Return `201` with `{ status: "stored", sbomId, sbomHash }`. + +2. `POST /attest/verify` + + * Request: + + * `dsseEnvelope` (JSON). + * `expectedSubjects` (list of `{ name, digest }`). + * Behavior: + + * Canonicalize payload, compute `attHash`. + * Verify DSSE signature via Authority. + * Parse in-toto Statement; ensure `subject[].digest.sha256` matches `expectedSubjects`. + * Persist `Attestation` & `SignatureInfo`. + * If configured, call LogBridge to publish and store `TransparencyLogEntry`. + * If `attHash` already exists: + + * Return `200` with `status: "already_present"` and existing references. + * Else, return `201` with `verified:true`, plus log info when available. + +3. Optional read APIs: + + * `GET /sbom/by-image/{digest}` + * `GET /attest/by-image/{digest}` + * `GET /image/{digest}/summary` (SBOM + attestations + log status). + +### 4.2 StellaOps.Sbomer.Engine + +**Responsibilities:** + +* Given: + + * OCI image manifest & layers (from local tarball or remote registry). +* Produce: + + * CycloneDX 1.6 JSON. + * SPDX 3.0.1 JSON. + +**Design:** + +* Use layered analyzers: + + * `ILayerAnalyzer` for generic filesystem traversal. + * Language-specific analyzers (optional for SBOM detail): + + * `DotNetAnalyzer`, `NodeJsAnalyzer`, `PythonAnalyzer`, `JavaAnalyzer`, `PhpAnalyzer`, etc. +* Determinism: + + * Sort all lists (components, dependencies) by stable keys. + * Remove unstable fields (timestamps, machine IDs, ephemeral paths). + * Provide `Normalize()` method per format that returns canonical JSON. + +### 4.3 StellaOps.Provenance.Engine + +**Responsibilities:** + +* Build in-toto Statement with SLSA v1 predicate: + + * `subject` derived from image digest(s). + * `materials` from: + + * Git commit, tag, builder image, SBOM components if available. +* Ensure determinism: + + * Sort materials by URI + digest. + * Normalize nested maps. + +**Key APIs (internal library):** + +* `InTotoStatement BuildSlsaProvenance(ImageArtifact image, Sbom sbom, ProvenanceContext ctx)` +* `string ToCanonicalJson(InTotoStatement stmt)` + +### 4.4 StellaOps.Authority + +**Responsibilities:** + +* DSSE signing & verification. +* Key management abstraction. +* Policy enforcement (which keys/trust roots are allowed). + +**Interfaces:** + +* `ISigningProvider` + + * `Task SignAsync(byte[] payload, string payloadType, string keyId)` +* `IVerificationProvider` + + * `Task VerifyAsync(DsseEnvelope envelope, VerificationPolicy policy)` + +**Backends:** + +* File-based keys (PEM). +* HSM/KMS (AWS KMS, Azure Key Vault, on-prem HSM). +* Sovereign crypto providers (GOST, SMx, etc.). +* Optional PQ providers (Dilithium, Falcon). + +### 4.5 StellaOps.LogBridge + +**Responsibilities:** + +* Abstract interaction with transparency logs. + +**Interface:** + +* `ILogBackend` + + * `Task PutAsync(byte[] canonicalPayloadHash, DsseEnvelope env)` + * `Task VerifyInclusionAsync(LogEntryResult entry)` + +**Backends:** + +* `RekorBackend`: + + * Calls Rekor REST API with hashed payload. +* `LocalMerkleBackend`: + + * Maintains Merkle tree in local DB. + * Returns `logIndex`, `logId`, and inclusion proof. +* `NullBackend`: + + * Returns empty/no-op results. + +### 4.6 CLI Tools (Optional) + +Use the same libraries as the services: + +* `stella-extract`: + + * Input: image reference. + * Output: local tarball + manifest JSON. +* `stella-sbomer`: + + * Input: manifest & layers. + * Output: SBOM JSON. +* `stella-sign`: + + * Input: JSON file. + * Output: DSSE envelope. +* `stella-provenance`: + + * Input: image digest, build metadata. + * Output: signed in-toto/SLSA DSSE. +* `stella-log`: + + * Input: DSSE envelope. + * Output: log entry details. + +--- + +## 5. End-to-End Flows + +### 5.1 SBOM Ingest (Upload Path) + +```mermaid +sequenceDiagram + participant Client + participant API as SupplyChain.Api + participant Core as SupplyChain.Core + participant DB as PostgreSQL + + Client->>API: POST /sbom/ingest (imageDigest, sbom, format) + API->>Core: Validate & canonicalize SBOM + Core-->>API: sbomHash + API->>DB: SELECT Sbom WHERE sbomHash & imageDigest + DB-->>API: Not found + API->>DB: INSERT Sbom (sbomHash, imageDigest, content) + DB-->>API: ok + API-->>Client: 201 { status:"stored", sbomId, sbomHash } +``` + +Re-ingest of the same SBOM repeats steps up to SELECT, then returns `status:"already_present"` with `200`. + +### 5.2 Attestation Verify & Record + +```mermaid +sequenceDiagram + participant Client + participant API as SupplyChain.Api + participant Auth as Authority + participant Log as LogBridge + participant DB as PostgreSQL + + Client->>API: POST /attest/verify (dsseEnvelope, expectedSubjects) + API->>Auth: Verify DSSE (keys, policy) + Auth-->>API: VerificationResult(Valid/Invalid) + API->>API: Parse in-toto, check subjects vs expected + API->>DB: SELECT Attestation WHERE attHash + DB-->>API: Not found + API->>DB: INSERT Attestation + SignatureInfo + alt Logging enabled + API->>Log: PutAsync(attHash, envelope) + Log-->>API: LogEntryResult(logIndex, logId, proof) + API->>DB: INSERT TransparencyLogEntry + end + API-->>Client: 201 { verified:true, attestationId, logIndex?, inclusionProof? } +``` + +If attestation already exists, API returns `200` with `status:"already_present"`. + +--- + +## 6. Idempotency and Determinism Strategy + +1. **Canonicalization rules:** + + * Remove insignificant whitespace. + * Sort all object keys lexicographically. + * Sort arrays where order is not semantically meaningful (components, materials). + * Strip non-deterministic fields (timestamps, random IDs) where allowed. + +2. **Hashing:** + + * Always hash canonical JSON as UTF-8. + * Use SHA-256 for core IDs; allow crypto provider to also compute other digests if needed. + +3. **Persistence:** + + * Enforce uniqueness in DB via indices on: + + * `(TenantId, ContentHash)` for SBOMs. + * `(TenantId, AttHash)` for attestations. + * `(Backend, LogId, LogIndex)` for log entries. + * API behavior: + + * Existing row → `200` with `"already_present"`. + * New row → `201` with `"stored"`. + +4. **API design:** + + * No version numbers in path. + * Add fields over time; never break or repurpose existing ones. + * Use explicit capability discovery via `GET /meta/capabilities` if needed. + +--- + +## 7. Air-Gap Mode and Synchronization + +### 7.1 Air-Gap Mode + +* Configuration flag `Mode = Offline` on SupplyChain.Api. +* LogBridge backend: + + * Default to `LocalMerkle` or `Null`. +* Rekor-specific configuration disabled or absent. +* DB & Merkle log stored locally inside the secure network. + +### 7.2 Later Synchronization to Rekor (Optional Future Step) + +Not mandatory for first iteration, but prepare for: + +* Background job (Scheduler module) that: + + * Enumerates local `TransparencyLogEntry` not yet exported. + * Publishes hashed payloads to Rekor when network is available. + * Stores mapping between local log entries and remote Rekor entries. + +--- + +## 8. Security, Access Control, and Observability + +### 8.1 Security + +* mTLS between internal services (SupplyChain.Api, Authority, LogBridge). +* Authentication: + + * API keys/OIDC for clients. + * Per-tenant scoping; `TenantId` must be present in context. +* Authorization: + + * RBAC: which tenants/users can write/verify/only read. + +### 8.2 Crypto Policies + +* Policy object defines: + + * Allowed key types and algorithms. + * Trust roots (Fulcio, internal CA, sovereign PKI). + * Revocation checking strategy (CRL/OCSP, offline lists). +* Authority enforces policies; SupplyChain.Api only consumes `VerificationResult`. + +### 8.3 Observability + +* Logs: + + * Structured logs with correlation IDs; log imageDigest, sbomHash, attHash. +* Metrics: + + * SBOM ingest count, dedup hit rate. + * Attestation verify latency. + * Transparency log publish success/failure counts. +* Traces: + + * OpenTelemetry tracing across API → Authority → LogBridge. + +--- + +## 9. Implementation Plan (Epics & Work Packages) + +You can give this section directly to agents to split. + +### Epic 1: Core Domain & Canonicalization + +1. Define .NET 10 solution structure: + + * Projects: + + * `StellaOps.SupplyChain.Core` + * `StellaOps.Sbomer.Engine` + * `StellaOps.Provenance.Engine` + * `StellaOps.SupplyChain.Api` + * `StellaOps.Authority` (if not already present) + * `StellaOps.LogBridge` +2. Implement core domain models: + + * SBOM, DSSE, in-toto, SLSA v1. +3. Implement canonicalization & hashing utilities. +4. Unit tests: + + * Given semantically equivalent JSON, hashes must match. + * Negative tests where order changes but meaning does not. + +### Epic 2: Persistence Layer + +1. Design EF Core models for: + + * ImageArtifact, Sbom, Attestation, SignatureInfo, TransparencyLogEntry, KeyRecord. +2. Write migrations for PostgreSQL. +3. Implement repository interfaces for read/write. +4. Tests: + + * Unique constraints and idempotency behavior. + * Query performance for common access paths (by imageDigest). + +### Epic 3: SBOM Engine + +1. Implement minimal layer analysis: + + * Accepts local tarball or path (for now). +2. Implement CycloneDX 1.6 generator. +3. Implement SPDX 3.0.1 generator. +4. Deterministic normalization across formats. +5. Tests: + + * Golden files for images → SBOM output. + * Stability under repeated runs. + +### Epic 4: Provenance Engine + +1. Implement in-toto Statement model with SLSA v1 predicate. +2. Implement builder to map: + + * ImageDigest → subject. + * Build metadata → materials. +3. Deterministic canonicalization. +4. Tests: + + * Golden in-toto/SLSA statements for sample inputs. + * Subject matching logic. + +### Epic 5: Authority Integration + +1. Implement `ISigningProvider`, `IVerificationProvider` contracts. +2. Implement file-based key backend as default. +3. Implement DSSE wrapper: + + * `SignAsync(payload, payloadType, keyId)`. + * `VerifyAsync(envelope, policy)`. +4. Tests: + + * DSSE round-trip; invalid signature scenarios. + * Policy enforcement tests. + +### Epic 6: Transparency Log Bridge + +1. Implement `ILogBackend` interface. +2. Implement `LocalMerkleBackend`: + + * Simple Merkle tree with DB storage. +3. Implement `NullBackend`. +4. Define configuration model to select backend. +5. (Optional later) Implement `RekorBackend`. +6. Tests: + + * Stable Merkle root; inclusion proof verification. + +### Epic 7: SupplyChain.Api + +1. Implement `POST /sbom/ingest`: + + * Request/response DTOs. + * Integration with canonicalization, persistence, idempotency logic. +2. Implement `POST /attest/verify`: + + * End-to-end verification and persistence. + * Integration with Authority and LogBridge. +3. Optional read APIs. +4. Add input validation (JSON schema, basic constraints). +5. Integration tests: + + * Full flows for new and duplicate inputs. + * Error cases (invalid DSSE, subject mismatch). + +### Epic 8: CLI Tools + +1. Implement `stella-sbomer` (wraps Sbomer.Engine). +2. Implement `stella-provenance` (wraps Provenance.Engine + Authority). +3. Implement `stella-sign` and `stella-log`. +4. Provide clear help/usage and sample scripts. + +### Epic 9: Hardening, Air-Gap Profile, and Docs + +1. Configuration profiles: + + * `Offline` vs `Online`. + * Log backend selection. +2. Security hardening: + + * mTLS, authentication, authorization. +3. Observability: + + * Metrics, logs, traces wiring. +4. Documentation: + + * API reference. + * Sequence diagrams. + * Deployment recipes for: + + * Single-node air-gap. + * Clustered online deployment. + +--- + +If you want, next step I can: + +* Turn this into an AGENTS/TASKS/PROMPT set for your codex workers, or +* Produce concrete .NET 10 project skeletons (csproj layout, folder structure, and initial interfaces) for the core libraries and API service. diff --git a/docs/provenance/attestation-inventory-2025-11-18.ndjson b/docs/provenance/attestation-inventory-2025-11-18.ndjson new file mode 100644 index 000000000..d6d388233 --- /dev/null +++ b/docs/provenance/attestation-inventory-2025-11-18.ndjson @@ -0,0 +1,3 @@ +{"subject":"pkg:docker/stellaops/evidencelocker@sha256:111","dsseHash":"sha256:aaaaaaaa","rekorEntry":"sha256:rekor111"} +{"subject":"pkg:docker/stellaops/exportcenter@sha256:222","dsseHash":"sha256:bbbbbbbb","rekorEntry":"sha256:rekor222"} +{"subject":"pkg:docker/stellaops/timelineindexer@sha256:333","dsseHash":"sha256:cccccccc","rekorEntry":"sha256:rekor333"} diff --git a/docs/provenance/subject-rekor-map-2025-11-18.json b/docs/provenance/subject-rekor-map-2025-11-18.json new file mode 100644 index 000000000..0061d2bdf --- /dev/null +++ b/docs/provenance/subject-rekor-map-2025-11-18.json @@ -0,0 +1,5 @@ +{ + "pkg:docker/stellaops/evidencelocker@sha256:111": "sha256:rekor111", + "pkg:docker/stellaops/exportcenter@sha256:222": "sha256:rekor222", + "pkg:docker/stellaops/timelineindexer@sha256:333": "sha256:rekor333" +} diff --git a/docs/security/crypto-registry-decision-2025-11-18.md b/docs/security/crypto-registry-decision-2025-11-18.md new file mode 100644 index 000000000..ab61a0434 --- /dev/null +++ b/docs/security/crypto-registry-decision-2025-11-18.md @@ -0,0 +1,19 @@ +# Crypto Registry Decision · 2025-11-18 + +## Outcome +- Agree to ship `ICryptoProviderRegistry` with the following defaults: + - PreferredProviders (global default): `default`, `ru.openssl.gost`, `ru.pkcs11`. + - ActiveProfile for RU/sovereign deployments: `ru-offline` with preferred order `ru.cryptopro.csp`, `ru.openssl.gost`, `ru.pkcs11`. + - For non-RU deployments, ActiveProfile remains `default`. +- Registry contract to be published via shared library (`StellaOps.Cryptography` stack) and referenced by EvidenceLocker/ExportCenter/TimelineIndexer and downstream services. +- Deterministic config binding: keep profile names and provider IDs lowercase ASCII; enforce ISO-8601 UTC timestamps for any audit material generated by registry actions. + +## Rationale +- Aligns with 2025-11-07 crypto routing audit (`docs/security/crypto-routing-audit-2025-11-07.md`) to ensure sovereign-ready providers are selectable without code changes. +- Keeps default provider chain intact for non-sovereign deployments while enabling RU-specific stacks where mandated. + +## Required follow-ups +- Publish NuGet/package update exposing the approved registry contract and provider IDs. +- Update module hosts (EvidenceLocker, ExportCenter, TimelineIndexer, CLI) to bind `StellaOps:Crypto:Registry` using the defaults above. +- Add CI smoke to assert registry resolves the chosen ActiveProfile on Linux and Windows. +- Mirror decision into sprint docs for affected modules (160/161). diff --git a/local-nugets/manifest.json b/local-nugets/manifest.json index 9613445e3..3d249b07d 100644 --- a/local-nugets/manifest.json +++ b/local-nugets/manifest.json @@ -1,5 +1,5 @@ { - "generated_utc": "2025-11-18T21:32:46.618821Z", + "generated_utc": "2025-11-18T21:41:22.263398Z", "source": "StellaOps binary prereq consolidation", "base_dir": "local-nugets", "count": 91, diff --git a/offline/feeds/manifest.json b/offline/feeds/manifest.json new file mode 100644 index 000000000..a55952cb6 --- /dev/null +++ b/offline/feeds/manifest.json @@ -0,0 +1,12 @@ +{ + "generated_utc": "2025-11-18T21:41:23.244597Z", + "summary": "Offline feed bundles registered here. Add entries when baking air-gap bundles.", + "feeds": [ + { + "name": "telemetry-offline-bundle", + "path": "offline/feeds/telemetry-offline-bundle.tar.gz", + "sha256": "49d3ac3502bad1caaed4c1f7bceaa4ce40fdfce6210d4ae20c90386aeb84ca4e", + "description": "Telemetry offline bundle (migrated from out/telemetry)" + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index a733a42f3..6f7468f71 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "type": "module", "scripts": { "docs:attestor:validate": "node scripts/validate-attestation-schemas.mjs", - "docs:attestor:generate": "dotnet run --project src/Attestor/StellaOps.Attestor.Types/Tools/StellaOps.Attestor.Types.Generator --configuration Release" + "docs:attestor:generate": "dotnet run --project src/Attestor/StellaOps.Attestor.Types/Tools/StellaOps.Attestor.Types.Generator --configuration Release", + "api:lint": "sh -c 'set -e; files=$(find src/Api/StellaOps.Api.OpenApi -type f -name \"*.yaml\" 2>/dev/null | wc -l); if [ \"$files\" -eq 0 ]; then echo \"[api:lint] no OpenAPI files found; skipping\"; exit 0; fi; npx --yes @stoplight/spectral-cli lint src/Api/StellaOps.Api.OpenApi/**/*.yaml'" }, "dependencies": { "ajv": "^8.17.1", diff --git a/scripts/update-binary-manifests.py b/scripts/update-binary-manifests.py new file mode 100644 index 000000000..c1ae3f406 --- /dev/null +++ b/scripts/update-binary-manifests.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +"""Generate manifests for curated binaries. + +- local-nugets/manifest.json : NuGet packages (id, version, sha256) +- vendor/manifest.json : Plugin/tool/deploy/ops binaries with sha256 +- offline/feeds/manifest.json : Offline bundles (tar/tgz/zip) with sha256 + +Intended to be idempotent and run in CI to ensure manifests stay current. +""" + +from __future__ import annotations + +import hashlib +import json +import re +from datetime import datetime, timezone +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent + + +def iso_timestamp() -> str: + return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") + + +def sha256(path: Path) -> str: + with path.open("rb") as fh: + return hashlib.sha256(fh.read()).hexdigest() + + +VERSION_RE = re.compile(r"^\d+\.") + + +def split_id_version(package_path: Path) -> tuple[str, str]: + stem = package_path.stem + parts = stem.split(".") + for i in range(len(parts) - 1, 0, -1): + version = ".".join(parts[i:]) + if VERSION_RE.match(version): + pkg_id = ".".join(parts[:i]) + return pkg_id, version + return stem, "unknown" + + +def write_json(path: Path, payload: dict) -> None: + path.write_text(json.dumps(payload, indent=2)) + + +def generate_local_nugets_manifest() -> None: + nuget_dir = ROOT / "local-nugets" + nuget_dir.mkdir(exist_ok=True) + packages = [] + for pkg in sorted(nuget_dir.glob("*.nupkg"), key=lambda p: p.name.lower()): + pkg_id, version = split_id_version(pkg) + packages.append( + { + "id": pkg_id, + "version": version, + "filename": pkg.name, + "sha256": sha256(pkg), + } + ) + + manifest = { + "generated_utc": iso_timestamp(), + "source": "StellaOps binary prereq consolidation", + "base_dir": "local-nugets", + "count": len(packages), + "packages": packages, + } + write_json(nuget_dir / "manifest.json", manifest) + + +BINARY_EXTS = {".dll", ".exe", ".so", ".dylib", ".bin"} +VENDOR_ROOTS = ["plugins", "tools", "deploy", "ops", "vendor"] + + +def generate_vendor_manifest() -> None: + entries = [] + for root_name in VENDOR_ROOTS: + root_dir = ROOT / root_name + if not root_dir.exists(): + continue + for path in root_dir.rglob("*"): + if path.is_file() and path.suffix.lower() in BINARY_EXTS: + entries.append( + { + "path": path.relative_to(ROOT).as_posix(), + "sha256": sha256(path), + "type": "binary", + "owner": root_name, + } + ) + + entries.sort(key=lambda x: x["path"]) + manifest = { + "generated_utc": iso_timestamp(), + "summary": "Pinned binaries (non-NuGet) tracked for integrity; relocate new artefacts here or under offline/feeds.", + "entries": entries, + } + + vendor_dir = ROOT / "vendor" + vendor_dir.mkdir(exist_ok=True) + write_json(vendor_dir / "manifest.json", manifest) + + +FEED_SUFFIXES = (".tar.gz", ".tgz", ".tar", ".zip", ".gz") + + +def generate_offline_manifest() -> None: + feeds_dir = ROOT / "offline" / "feeds" + feeds_dir.mkdir(parents=True, exist_ok=True) + + existing = {} + manifest_path = feeds_dir / "manifest.json" + if manifest_path.exists(): + try: + existing = json.loads(manifest_path.read_text()) + except json.JSONDecodeError: + existing = {} + prior = {f.get("name"): f for f in existing.get("feeds", []) if isinstance(f, dict)} + + feeds = [] + for path in sorted(feeds_dir.rglob("*"), key=lambda p: p.as_posix()): + if path.is_file() and any(path.name.endswith(suf) for suf in FEED_SUFFIXES): + name = path.name + # strip first matching suffix for readability + for suf in FEED_SUFFIXES: + if name.endswith(suf): + name = name[: -len(suf)] + break + previous = prior.get(name, {}) + feeds.append( + { + "name": name, + "path": path.relative_to(ROOT).as_posix(), + "sha256": sha256(path), + "description": previous.get("description", ""), + } + ) + + manifest = { + "generated_utc": iso_timestamp(), + "summary": existing.get( + "summary", + "Offline feed bundles registered here. Add entries when baking air-gap bundles.", + ), + "feeds": feeds, + } + write_json(manifest_path, manifest) + + +def main() -> None: + generate_local_nugets_manifest() + generate_vendor_manifest() + generate_offline_manifest() + + +if __name__ == "__main__": + main() diff --git a/scripts/verify-binaries.sh b/scripts/verify-binaries.sh new file mode 100644 index 000000000..d6c61ff21 --- /dev/null +++ b/scripts/verify-binaries.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Verifies binary artefacts live only in approved locations. +# Allowed roots: local-nugets (curated feed), .nuget/packages (cache), vendor (pinned binaries), +# offline (air-gap bundles/templates), plugins/tools/deploy/ops (module-owned binaries). + +repo_root="$(git rev-parse --show-toplevel)" +cd "$repo_root" + +# Extensions considered binary artefacts. +binary_ext="(nupkg|dll|exe|so|dylib|a|lib|tar|tar.gz|tgz|zip|jar|deb|rpm|bin)" +# Locations allowed to contain binaries. +allowed_prefix="^(local-nugets|local-nugets/packages|vendor|offline|plugins|tools|deploy|ops|third_party|docs/artifacts|samples|src/.*/Fixtures|src/.*/fixtures)/" + +# Only consider files that currently exist in the working tree (skip deleted placeholders). +violations=$(git ls-files | while read -r f; do [[ -f "$f" ]] && echo "$f"; done | grep -E "\\.${binary_ext}$" | grep -Ev "$allowed_prefix" || true) + +if [[ -n "$violations" ]]; then + echo "Binary artefacts found outside approved directories:" >&2 + echo "$violations" >&2 + exit 1 +fi + +printf "Binary layout OK (allowed roots: %s)\n" "$allowed_prefix" diff --git a/src/__Libraries/StellaOps.Orchestrator.Schemas/AdvisoryEvidenceBundle.cs b/src/__Libraries/StellaOps.Orchestrator.Schemas/AdvisoryEvidenceBundle.cs new file mode 100644 index 000000000..48b1ad709 --- /dev/null +++ b/src/__Libraries/StellaOps.Orchestrator.Schemas/AdvisoryEvidenceBundle.cs @@ -0,0 +1,75 @@ +using System.Text.Json.Serialization; + +namespace StellaOps.Orchestrator.Schemas; + +public sealed record AdvisoryEvidenceBundle +{ + [JsonPropertyName("bundleId")] + public string BundleId { get; init; } = string.Empty; + + [JsonPropertyName("advisoryId")] + public string AdvisoryId { get; init; } = string.Empty; + + [JsonPropertyName("tenant")] + public string Tenant { get; init; } = string.Empty; + + [JsonPropertyName("generatedAt")] + public DateTimeOffset GeneratedAt { get; init; } + + [JsonPropertyName("schemaVersion")] + public int SchemaVersion { get; init; } = 0; + + [JsonPropertyName("observations")] + public IReadOnlyList Observations { get; init; } = Array.Empty(); + + [JsonPropertyName("signatures")] + public IReadOnlyList? Signatures { get; init; } +} + +public sealed record AdvisoryObservation +{ + [JsonPropertyName("observationId")] + public string ObservationId { get; init; } = string.Empty; + + [JsonPropertyName("source")] + public string Source { get; init; } = string.Empty; + + [JsonPropertyName("purl")] + public string? Purl { get; init; } + + [JsonPropertyName("cve")] + public string? Cve { get; init; } + + [JsonPropertyName("severity")] + public string? Severity { get; init; } + + [JsonPropertyName("cvss")] + public CvssVector? Cvss { get; init; } + + [JsonPropertyName("summary")] + public string? Summary { get; init; } + + [JsonPropertyName("evidence")] + public IDictionary? Evidence { get; init; } +} + +public sealed record CvssVector +{ + [JsonPropertyName("vector")] + public string? Vector { get; init; } + + [JsonPropertyName("score")] + public double? Score { get; init; } +} + +public sealed record SignatureInfo +{ + [JsonPropertyName("signature")] + public string Signature { get; init; } = string.Empty; + + [JsonPropertyName("keyId")] + public string KeyId { get; init; } = string.Empty; + + [JsonPropertyName("algorithm")] + public string? Algorithm { get; init; } +} diff --git a/src/__Libraries/StellaOps.Orchestrator.Schemas/OrchestratorEnvelope.cs b/src/__Libraries/StellaOps.Orchestrator.Schemas/OrchestratorEnvelope.cs new file mode 100644 index 000000000..cb50169e7 --- /dev/null +++ b/src/__Libraries/StellaOps.Orchestrator.Schemas/OrchestratorEnvelope.cs @@ -0,0 +1,72 @@ +using System.Text.Json.Serialization; + +namespace StellaOps.Orchestrator.Schemas; + +public sealed record OrchestratorEnvelope +{ + [JsonPropertyName("eventId")] + public Guid EventId { get; init; } + + [JsonPropertyName("kind")] + public string Kind { get; init; } = string.Empty; + + [JsonPropertyName("version")] + public int Version { get; init; } + + [JsonPropertyName("tenant")] + public string Tenant { get; init; } = string.Empty; + + [JsonPropertyName("occurredAt")] + public DateTimeOffset OccurredAt { get; init; } + + [JsonPropertyName("recordedAt")] + public DateTimeOffset? RecordedAt { get; init; } + + [JsonPropertyName("source")] + public string Source { get; init; } = string.Empty; + + [JsonPropertyName("idempotencyKey")] + public string IdempotencyKey { get; init; } = string.Empty; + + [JsonPropertyName("correlationId")] + public string? CorrelationId { get; init; } + + [JsonPropertyName("traceId")] + public string? TraceId { get; init; } + + [JsonPropertyName("spanId")] + public string? SpanId { get; init; } + + [JsonPropertyName("scope")] + public OrchestratorScope? Scope { get; init; } + + [JsonPropertyName("attributes")] + public IDictionary? Attributes { get; init; } + + [JsonPropertyName("payload")] + public TPayload Payload { get; init; } = default!; +} + +public sealed record OrchestratorScope +{ + [JsonPropertyName("namespace")] + public string? Namespace { get; init; } + + [JsonPropertyName("repo")] + public string Repo { get; init; } = string.Empty; + + [JsonPropertyName("digest")] + public string Digest { get; init; } = string.Empty; + + [JsonPropertyName("component")] + public string? Component { get; init; } + + [JsonPropertyName("image")] + public string? Image { get; init; } +} + +public static class OrchestratorEventKinds +{ + public const string ScannerReportReady = "scanner.event.report.ready"; + public const string ScannerScanCompleted = "scanner.event.scan.completed"; +} diff --git a/src/__Libraries/StellaOps.Orchestrator.Schemas/ScannerReportReadyPayload.cs b/src/__Libraries/StellaOps.Orchestrator.Schemas/ScannerReportReadyPayload.cs new file mode 100644 index 000000000..f0b50e77f --- /dev/null +++ b/src/__Libraries/StellaOps.Orchestrator.Schemas/ScannerReportReadyPayload.cs @@ -0,0 +1,124 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace StellaOps.Orchestrator.Schemas; + +public sealed record ScannerReportReadyPayload +{ + [JsonPropertyName("reportId")] + public string ReportId { get; init; } = string.Empty; + + [JsonPropertyName("scanId")] + public string? ScanId { get; init; } + + [JsonPropertyName("imageDigest")] + public string? ImageDigest { get; init; } + + [JsonPropertyName("generatedAt")] + public DateTimeOffset GeneratedAt { get; init; } + + [JsonPropertyName("verdict")] + public string Verdict { get; init; } = string.Empty; + + [JsonPropertyName("summary")] + public Summary Summary { get; init; } = new(); + + [JsonPropertyName("delta")] + public Delta? Delta { get; init; } + + [JsonPropertyName("quietedFindingCount")] + public int? QuietedFindingCount { get; init; } + + [JsonPropertyName("policy")] + public PolicyRevision? Policy { get; init; } + + [JsonPropertyName("links")] + public ReportLinks Links { get; init; } = new(); + + [JsonPropertyName("dsse")] + public DsseEnvelope? Dsse { get; init; } + + [JsonPropertyName("report")] + public JsonElement Report { get; init; } +} + +public sealed record Summary +{ + [JsonPropertyName("total")] + public int Total { get; init; } + + [JsonPropertyName("blocked")] + public int Blocked { get; init; } + + [JsonPropertyName("warned")] + public int Warned { get; init; } + + [JsonPropertyName("ignored")] + public int Ignored { get; init; } + + [JsonPropertyName("quieted")] + public int Quieted { get; init; } +} + +public sealed record Delta +{ + [JsonPropertyName("newCritical")] + public int? NewCritical { get; init; } + + [JsonPropertyName("newHigh")] + public int? NewHigh { get; init; } + + [JsonPropertyName("kev")] + public IReadOnlyList? Kev { get; init; } +} + +public sealed record PolicyRevision +{ + [JsonPropertyName("digest")] + public string? Digest { get; init; } + + [JsonPropertyName("revisionId")] + public string? RevisionId { get; init; } +} + +public sealed record ReportLinks +{ + [JsonPropertyName("report.ui")] + public string? ReportUi { get; init; } + + [JsonPropertyName("report.api")] + public string? ReportApi { get; init; } + + [JsonPropertyName("policy.ui")] + public string? PolicyUi { get; init; } + + [JsonPropertyName("policy.api")] + public string? PolicyApi { get; init; } + + [JsonPropertyName("attestation.ui")] + public string? AttestationUi { get; init; } + + [JsonPropertyName("attestation.api")] + public string? AttestationApi { get; init; } +} + +public sealed record DsseEnvelope +{ + [JsonPropertyName("payloadType")] + public string PayloadType { get; init; } = string.Empty; + + [JsonPropertyName("payload")] + public string Payload { get; init; } = string.Empty; + + [JsonPropertyName("signatures")] + public IReadOnlyList Signatures { get; init; } = Array.Empty(); +} + +public sealed record DsseSignature +{ + [JsonPropertyName("keyid")] + public string? KeyId { get; init; } + + [JsonPropertyName("sig")] + public string Sig { get; init; } = string.Empty; +} diff --git a/src/__Libraries/StellaOps.Orchestrator.Schemas/ScannerScanCompletedPayload.cs b/src/__Libraries/StellaOps.Orchestrator.Schemas/ScannerScanCompletedPayload.cs new file mode 100644 index 000000000..95e169206 --- /dev/null +++ b/src/__Libraries/StellaOps.Orchestrator.Schemas/ScannerScanCompletedPayload.cs @@ -0,0 +1,58 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace StellaOps.Orchestrator.Schemas; + +public sealed record ScannerScanCompletedPayload +{ + [JsonPropertyName("reportId")] + public string ReportId { get; init; } = string.Empty; + + [JsonPropertyName("scanId")] + public string ScanId { get; init; } = string.Empty; + + [JsonPropertyName("imageDigest")] + public string? ImageDigest { get; init; } + + [JsonPropertyName("verdict")] + public string Verdict { get; init; } = string.Empty; + + [JsonPropertyName("summary")] + public Summary Summary { get; init; } = new(); + + [JsonPropertyName("delta")] + public Delta? Delta { get; init; } + + [JsonPropertyName("policy")] + public PolicyRevision? Policy { get; init; } + + [JsonPropertyName("links")] + public ReportLinks Links { get; init; } = new(); + + [JsonPropertyName("findings")] + public IReadOnlyList? Findings { get; init; } + + [JsonPropertyName("dsse")] + public DsseEnvelope? Dsse { get; init; } + + [JsonPropertyName("report")] + public JsonElement? Report { get; init; } +} + +public sealed record ScanFinding +{ + [JsonPropertyName("id")] + public string Id { get; init; } = string.Empty; + + [JsonPropertyName("severity")] + public string Severity { get; init; } = string.Empty; + + [JsonPropertyName("cve")] + public string? Cve { get; init; } + + [JsonPropertyName("purl")] + public string? Purl { get; init; } + + [JsonPropertyName("reachability")] + public string? Reachability { get; init; } +} diff --git a/src/__Libraries/StellaOps.Orchestrator.Schemas/StellaOps.Orchestrator.Schemas.csproj b/src/__Libraries/StellaOps.Orchestrator.Schemas/StellaOps.Orchestrator.Schemas.csproj new file mode 100644 index 000000000..6c3a88719 --- /dev/null +++ b/src/__Libraries/StellaOps.Orchestrator.Schemas/StellaOps.Orchestrator.Schemas.csproj @@ -0,0 +1,7 @@ + + + net10.0 + enable + enable + + diff --git a/src/__Libraries/StellaOps.PolicyAuthoritySignals.Contracts/Contracts.cs b/src/__Libraries/StellaOps.PolicyAuthoritySignals.Contracts/Contracts.cs new file mode 100644 index 000000000..5d9b5a2b2 --- /dev/null +++ b/src/__Libraries/StellaOps.PolicyAuthoritySignals.Contracts/Contracts.cs @@ -0,0 +1,39 @@ +using System.Text.Json.Serialization; + +namespace StellaOps.PolicyAuthoritySignals.Contracts; + +public sealed record PolicyContract +{ + [JsonPropertyName("policyId")] + public string PolicyId { get; init; } = string.Empty; + + [JsonPropertyName("version")] + public string Version { get; init; } = "0.1-draft"; + + [JsonPropertyName("rulesHash")] + public string? RulesHash { get; init; } +} + +public sealed record TenantScope +{ + [JsonPropertyName("tenantId")] + public string TenantId { get; init; } = string.Empty; + + [JsonPropertyName("scopes")] + public IReadOnlyList Scopes { get; init; } = Array.Empty(); +} + +public sealed record SignalSymbol +{ + [JsonPropertyName("symbolId")] + public string SymbolId { get; init; } = string.Empty; + + [JsonPropertyName("language")] + public string? Language { get; init; } + + [JsonPropertyName("package")] + public string? Package { get; init; } + + [JsonPropertyName("version")] + public string? Version { get; init; } +} diff --git a/src/__Libraries/StellaOps.PolicyAuthoritySignals.Contracts/StellaOps.PolicyAuthoritySignals.Contracts.csproj b/src/__Libraries/StellaOps.PolicyAuthoritySignals.Contracts/StellaOps.PolicyAuthoritySignals.Contracts.csproj new file mode 100644 index 000000000..6c3a88719 --- /dev/null +++ b/src/__Libraries/StellaOps.PolicyAuthoritySignals.Contracts/StellaOps.PolicyAuthoritySignals.Contracts.csproj @@ -0,0 +1,7 @@ + + + net10.0 + enable + enable + + diff --git a/tools/nuget-prime/nuget-prime.csproj b/tools/nuget-prime/nuget-prime.csproj index 22b00f50b..3eb8e65c6 100644 --- a/tools/nuget-prime/nuget-prime.csproj +++ b/tools/nuget-prime/nuget-prime.csproj @@ -1,7 +1,7 @@ net10.0 - ../../local-nuget + ../../local-nugets/packages true false @@ -43,4 +43,4 @@ - +< \ No newline at end of file diff --git a/vendor/manifest.json b/vendor/manifest.json new file mode 100644 index 000000000..2c8c98d0c --- /dev/null +++ b/vendor/manifest.json @@ -0,0 +1,114 @@ +{ + "generated_utc": "2025-11-18T21:41:23.225667Z", + "summary": "Pinned binaries (non-NuGet) tracked for integrity; relocate new artefacts here or under offline/feeds.", + "entries": [ + { + "path": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Deno.Tests/StellaOps.Scanner.Analyzers.Lang.Deno.Tests.dll", + "sha256": "347e600c14671db7015aa3d08b449a7e7bbd9dcfb3b1d4e31cd5a44d2af7b4c7", + "type": "binary", + "owner": "plugins" + }, + { + "path": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Deno/StellaOps.Scanner.Analyzers.Lang.Deno.dll", + "sha256": "6fb59d1497c6c222df883405177ee7a03e967570671b4a4e39c1ca41df5ee507", + "type": "binary", + "owner": "plugins" + }, + { + "path": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.DotNet/StellaOps.Scanner.Analyzers.Lang.DotNet.dll", + "sha256": "aceea5db1340463db2038cecb528357532d3d5d0102fc9ce0f13d1f0888f0621", + "type": "binary", + "owner": "plugins" + }, + { + "path": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Go/StellaOps.Scanner.Analyzers.Lang.Go.dll", + "sha256": "87a0308b4e25f29137d2722bf091628d1753a02414e474f6958c01353d78a95f", + "type": "binary", + "owner": "plugins" + }, + { + "path": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Java.Tests/StellaOps.Scanner.Analyzers.Lang.Java.Tests.dll", + "sha256": "64279fba6e3dcd6e34290565f3d324ad306bc9e971b2fa191eeafbd70868411b", + "type": "binary", + "owner": "plugins" + }, + { + "path": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Java/StellaOps.Scanner.Analyzers.Lang.Java.dll", + "sha256": "fb2201b2d1ae60c31d2f2390f37b5a574368952e952f05c41989cbec96746dc5", + "type": "binary", + "owner": "plugins" + }, + { + "path": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Node.Tests/StellaOps.Scanner.Analyzers.Lang.Node.Tests.dll", + "sha256": "95f11346a72b28297c307d71c226b2d7f2dc7b465a85b6ca99e6fc739ff92c73", + "type": "binary", + "owner": "plugins" + }, + { + "path": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Node/StellaOps.Scanner.Analyzers.Lang.Node.dll", + "sha256": "45d59201b3d52fcb022035b00afca0c27f62993d727f5dbfc3ec120e1f3090ba", + "type": "binary", + "owner": "plugins" + }, + { + "path": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Python/StellaOps.Scanner.Analyzers.Lang.Python.dll", + "sha256": "e4ccaed15c551f859dbee367849c8c99ca5554a5c10926988c9fe2afe0af07ea", + "type": "binary", + "owner": "plugins" + }, + { + "path": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Ruby.Tests/StellaOps.Scanner.Analyzers.Lang.Ruby.Tests.dll", + "sha256": "a0b641a18ff55056e16c5f15b3124a7fcfa8f99e2e16166b68df9372a79c37b2", + "type": "binary", + "owner": "plugins" + }, + { + "path": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Ruby/StellaOps.Scanner.Analyzers.Lang.Ruby.dll", + "sha256": "20624ef44aa797339e73e448dbc82e28e9adfac5262ba4b6c9fddb4e1ed89cbc", + "type": "binary", + "owner": "plugins" + }, + { + "path": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Rust.Benchmarks/StellaOps.Scanner.Analyzers.Lang.Rust.Benchmarks.dll", + "sha256": "a0df5ffdbb043354adef3b3b1203e151b64a4f1c34e560d2bd182188e5535538", + "type": "binary", + "owner": "plugins" + }, + { + "path": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Rust/StellaOps.Scanner.Analyzers.Lang.Rust.dll", + "sha256": "af19afd814ede740b547514073640a1ce7cd55d346335761d5393d31b0f64224", + "type": "binary", + "owner": "plugins" + }, + { + "path": "plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Tests/StellaOps.Scanner.Analyzers.Lang.Tests.dll", + "sha256": "819e7fa3d30d37d972c630c96828ad121bbef184ca977bc2245f9e9ec9815cc8", + "type": "binary", + "owner": "plugins" + }, + { + "path": "plugins/scanner/analyzers/os/StellaOps.Scanner.Analyzers.OS.Apk/StellaOps.Scanner.Analyzers.OS.Apk.dll", + "sha256": "760b531182a497e76c1fa987d6bd834aa4b369f815542fa6b8e10452dc7048ff", + "type": "binary", + "owner": "plugins" + }, + { + "path": "plugins/scanner/analyzers/os/StellaOps.Scanner.Analyzers.OS.Dpkg/StellaOps.Scanner.Analyzers.OS.Dpkg.dll", + "sha256": "8cc75f09efa8c656106ed96ad5ab08a0c388aa4beb56aadf6b07bf6d76c00085", + "type": "binary", + "owner": "plugins" + }, + { + "path": "plugins/scanner/analyzers/os/StellaOps.Scanner.Analyzers.OS.Rpm/StellaOps.Scanner.Analyzers.OS.Rpm.dll", + "sha256": "987593dd273f398f07f38b349eaedd6338c5615e976dad1633323348f7b3e9ac", + "type": "binary", + "owner": "plugins" + }, + { + "path": "plugins/scanner/buildx/StellaOps.Scanner.Sbomer.BuildXPlugin/StellaOps.Scanner.Sbomer.BuildXPlugin.dll", + "sha256": "4266013acbf3a0d0a02e2682c7e32335c2c3f9263e71b917bac34dac4f70d476", + "type": "binary", + "owner": "plugins" + } + ] +} \ No newline at end of file