the-custodian/docs/sbom-controlled-scan-input-contract-v1.md
codex cb62de1b7e
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
docs: select controlled SBOM source contract
Assistant: codex
2026-08-22 23:57:53 +02:00

8.5 KiB

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:

forgejo:coulomb/<repo-slug>@<40-lowercase-hex-commit>
  -> /coulomb/<repo-slug>/archive/<commit>.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:

{
  "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_slug>. 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:

POST /sbom/<repo_slug>/ingest
Idempotency-Key: <stable per-workflow-run/per-repository key>
X-Activity-Core-Operation-ID: <same value>
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/<slug>@<revision>.

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.