diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 7376a2a..4f2833f 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -406,8 +406,8 @@ | task | CUST-WP-0063-T03 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md | | task | CUST-WP-0063-T04 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md | | task | CUST-WP-0063-T05 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md | -| task | CUST-WP-0064-T01 | progress | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md | -| task | CUST-WP-0064-T02 | wait | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md | +| task | CUST-WP-0064-T01 | done | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md | +| task | CUST-WP-0064-T02 | progress | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md | | task | CUST-WP-0064-T03 | wait | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md | | task | CUST-WP-0064-T04 | wait | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md | | intake | CUST-IN-0001 | closed | green | intake-legacy-suggestions-migration.md | diff --git a/docs/sbom-controlled-scan-input-contract-v1.md b/docs/sbom-controlled-scan-input-contract-v1.md new file mode 100644 index 0000000..fdd216f --- /dev/null +++ b/docs/sbom-controlled-scan-input-contract-v1.md @@ -0,0 +1,186 @@ +# Controlled SBOM scan input contract v1 + +**Status:** selected for implementation +**Decision date:** 2026-08-22 +**Coordination:** `CUST-WP-0064` +**Owners:** Repo Manager, SBOM Nexus, Activity Core, `rapp-sbom-nexus` + +## Decision + +Authoritative daily catch-up will scan a Forgejo source archive addressed by a +full, immutable Git commit SHA. Repo Manager projects repository identity and +an observed default-branch revision; SBOM Nexus validates, fetches, extracts, +scans, persists provenance, and removes the source; Activity Core freezes the +source reference with the already-bounded target set; `rapp-sbom-nexus` +provides only the runtime filesystem and network boundary. + +The initial provider is the public Coulomb Forgejo organization. The fetch +path is equivalent to: + +```text +forgejo:coulomb/@<40-lowercase-hex-commit> + -> /coulomb//archive/.tar.gz +``` + +The runtime constructs the fetch URL from configured Forgejo base and the +validated identity. The ingest API does not accept an arbitrary URL. This +prevents the scan path from becoming an SSRF surface or a general downloader. + +An anonymous full-SHA archive fetch was proved on 2026-08-22 for +`coulomb/sbom-nexus@b95fba9a9f489b70e2640b62a0b3317583be742d`. No source +credential is required for v1. In particular, the Forgejo administrator PAT +must not be reused. A private repository remains unsupported until a separate, +least-privilege read identity and custody lane is approved. + +## Wire contract + +Repo Manager's projection supplies: + +```json +{ + "repo_slug": "sbom-nexus", + "source_ref": { + "kind": "forgejo-archive-v1", + "repository": "coulomb/sbom-nexus", + "revision": "b95fba9a9f489b70e2640b62a0b3317583be742d", + "observed_ref": "refs/heads/main", + "observed_at": "2026-08-22T21:38:58Z" + } +} +``` + +`revision` is always a full 40-character lowercase Git SHA. `repository` must +match the canonical Forgejo remote owned by Repo Manager and, for v1, must be +exactly `coulomb/`. Repo Manager resolves the default-branch head +from Forgejo and records when it was observed; workstation paths and local +working-tree state are never used as production source. + +SBOM Nexus stores the source reference in its repository projection and +returns it with each selected entry from `GET /sbom/catch-up`. Activity Core +records the exact returned reference in Temporal history together with the +fixed, at-most-N target set. Its ingest call is: + +```http +POST /sbom//ingest +Idempotency-Key: +X-Activity-Core-Operation-ID: +Content-Type: application/json + +{"source_ref": { ...the selected source reference... }} +``` + +Retries reuse the same repository, source reference, and operation key even if +the default branch advances. SBOM Nexus rejects a source identity/slug +mismatch and a reused operation key with a different request fingerprint. +Successful replay returns the original terminal response and never creates a +second snapshot. This is the mutation-boundary requirement tracked by +`CUST-IN-0013`. + +## Fetch and extraction boundary + +SBOM Nexus is the only component that fetches or scans source. For each request +it creates a fresh directory on a dedicated ephemeral volume, streams the +archive to disk, computes its SHA-256, safely extracts one top-level directory, +scans it, commits the snapshot and operation receipt transactionally, and +removes the request directory in a `finally` path. + +The v1 limits are: + +| Control | Limit | +| --- | ---: | +| Concurrent controlled-source scans per Nexus pod | 1 | +| Fetch timeout | 120 seconds | +| Scan timeout | 120 seconds | +| Compressed archive | 100 MiB | +| Expanded files | 512 MiB total | +| Archive members | 100,000 | +| Ephemeral volume | 768 MiB | +| Redirects | same configured Forgejo host only | + +Extraction rejects absolute paths, `..` traversal, links, devices, FIFOs, and +members outside the single archive root. The implementation streams bytes and +does not load the complete archive into memory. The application container +keeps its read-only root filesystem; only the bounded source volume is +writable. Logs may contain repository, revision, byte counts, durations, +outcome, and operation-key hash, but never archive content or credential data. + +The package permits egress only to the Forgejo HTTP workload on port 3000 and +cluster DNS needed to resolve it, in addition to the existing PostgreSQL +egress. The configured runtime base is the cluster-local Forgejo service; the +canonical provenance identity remains `forgejo:coulomb/@`. + +## Snapshot provenance and outcomes + +An authoritative snapshot records at least: + +- repository slug and canonical Forgejo repository identity; +- immutable Git revision and observed branch metadata; +- downloaded archive SHA-256 and byte count; +- manifest-relative paths and their existing content SHA-256 values; +- operation id, scanner version, start/end time, and terminal outcome. + +The archive does not contain `.git`, so the scanner receives the validated +revision explicitly instead of attempting to infer it from the extracted +directory. + +Terminal outcomes remain explicit: + +| Condition | Outcome | +| --- | --- | +| Valid source, supported evidence, clean parse | `ingested` | +| Valid source, no supported evidence | `no-manifest` | +| Supported evidence with parser errors | `ingest-error` | +| No eligible Forgejo source reference or revision cannot be fetched | `source-unavailable` | +| Identity, redirect, archive, or extraction policy violation | `source-rejected` | + +`source-unavailable` and `source-rejected` are additive skip reasons. Activity +Core must accept and report them without selecting a replacement repository in +the same fire. Only `ingested` advances `last_success_at`; every committed +terminal outcome advances `last_attempt_at` and preserves queue fairness. + +## Ownership + +| Owner | Required change | +| --- | --- | +| Repo Manager | Normalize the canonical Forgejo repository, observe its default-branch full SHA, and project the structured source reference. Missing, private, non-Coulomb, or unresolvable repositories have no v1 source reference. | +| SBOM Nexus | Add source-reference projection, controlled fetch/extraction, explicit revision override, snapshot provenance, new skip reasons, and durable operation receipts. It remains the only snapshot writer. | +| Activity Core | Freeze `source_ref` with the original bounded target set, submit it on ingest, preserve the operation key across retries, and report the additive skip reasons. It must not fetch source itself. | +| `rapp-sbom-nexus` | Add the size-limited ephemeral volume, Forgejo/DNS egress, configuration and resource controls, and live cleanup/rollback checks. It owns no scan semantics. | + +## Unsupported and failure behavior + +A missing source reference is visible in catch-up and becomes one terminal +`source-unavailable` outcome when selected. HTTP not-found, an unavailable +pinned commit, or a private repository without an approved v1 access path is +also `source-unavailable`. Timeout, oversize content, unsafe archive members, +an off-host redirect, identity mismatch, or malformed source reference is +`source-rejected`. A transport ambiguity after a committed write is resolved +only by replaying the same operation key; Activity Core must not synthesize a +skip or move to another target. + +## Rollout and rollback + +Roll out dark behind `SBOM_NEXUS_CONTROLLED_SOURCE_ENABLED=false`, migrate the +projection/provenance/idempotency schema, enable the source path for an +attended one-repository canary, then enable it for the existing daily bounded +definition. No increase to `catch_up_limit` is part of this change. + +Rollback order is: + +1. disable the Activity Core daily schedule and allow or cancel the one + in-flight bounded run; +2. set controlled source disabled and roll back the Nexus/package image digest; +3. remove Forgejo/DNS egress and the ephemeral volume if reverting the package; +4. retain all snapshots, operation receipts, and additive database columns. + +Rollback never deletes SBOM history and never re-enables the retired weekly +task-flood rule. Local Nexus CLI scans remain non-authoritative preview work; +workstation checkouts are not a production fallback. + +## Acceptance evidence + +The implementation proof must show one attended and one normally scheduled +fire with the original target bound, stable operation ids and revisions across +retry, at least one real `ingested` snapshot, archive and manifest provenance, +zero spawned tasks, empty transient-source storage after completion, and a +rollback control that disables new source fetches without erasing history. diff --git a/workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md b/workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md index aa1bc20..a5d2bab 100644 --- a/workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md +++ b/workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md @@ -44,7 +44,7 @@ the deployment package owns the runtime/network boundary. ```task id: CUST-WP-0064-T01 -status: progress +status: done priority: high state_hub_task_id: "02ac7278-8536-5ce8-9027-39345aab0539" ``` @@ -58,20 +58,25 @@ behavior, cleanup, and the boundary between preview and authoritative ingest. Done when the four owning repos have one reviewable contract and rollback; do not enable source transfer from a prose-only assumption. -**Started (2026-08-22):** live verification confirmed the private Nexus pod +**Done (2026-08-22):** live verification confirmed the private Nexus pod cannot reach projected workstation checkout paths and that queue fairness is therefore advancing through `no-checkout` outcomes. The Custodian fixed the non-negotiable boundary—revision-pinned input, no workstation mount, bounded credentials/egress/time/size, provenance, cleanup, and rollback—and routed owner participation to SBOM Nexus (`95c1b226`), Repo Manager (`075e21de`), Activity Core (`7233d2d1`), and the package owner (`e0af24b1`). Selection of -the concrete transfer mechanism remains in progress. +the concrete transfer mechanism is recorded in +`docs/sbom-controlled-scan-input-contract-v1.md`: a public Coulomb Forgejo +archive addressed by a full commit SHA, fetched and safely scanned by Nexus +from a bounded ephemeral volume. A live full-SHA archive fetch succeeded; v1 +therefore needs no source credential and explicitly forbids reuse of the +Forgejo administrator PAT. ## Implement the Nexus-owned authoritative scan path ```task id: CUST-WP-0064-T02 -status: wait +status: progress priority: high state_hub_task_id: "2029e525-0573-5fea-881c-d3a418b91c9d" ``` @@ -81,6 +86,11 @@ consume the selected input, scan at a pinned revision, persist provenance, and remove temporary source material. Preserve Nexus as the only snapshot writer and enforce `CUST-IN-0013` operation idempotency on the mutation boundary. +**Started (2026-08-22):** the selected v1 contract fixes the application and +package changes, additive skip reasons, safety limits, durable replay rule, +and rollback sequence. Implementation handoffs now carry one shared contract +instead of separate mechanism proposals. + ## Retarget bounded catch-up without widening it ```task