Files
moloapp/plans/15-supply-chain.md
T
retoor 3c4334cec9 feat: implement MoloApp plans 01-15 with integration
Full C++23 framework tree: runtime, observability, security,
data, app, realtime, supervisor, clients, CLI, example plus
enterprise tenancy, audit, operability, governance,
supplychain. Postgres via docker compose for tests.
2026-10-23 18:20:04 +02:00

16 KiB

15 - Supply Chain and Trust

File: plans/15-supply-chain.md Scope: plan.md sections 4 (wolfSSL exception), 9 (autonomous releases). Topics: SBOM generation, signed release artifacts, dependency policy and wolfSSL exception handling, vulnerability-scan pipeline hook, provenance and release attestation.

1. Purpose and philosophy

MoloApp ships almost no supply chain: stdlib plus OS syscalls plus wolfSSL as the sole third-party dependency. This file makes that claim verifiable. Every release carries a machine-readable SBOM, a signed provenance statement describing where, when and how it was built, and detached signatures over all artifacts, so a consumer can answer "what is inside this binary and who built it" without trusting the download server.

Philosophy:

  • Minimal surface, maximal proof. One allowed exception (wolfSSL) is pinned, hashed and attested; everything else in-house is versioned by git commit.
  • Proof over promise. SBOM, provenance and signatures are generated by the release pipeline in 10-workflow-example.md, never hand-edited.
  • Standards first. SPDX and CycloneDX for SBOM, in-toto plus SLSA for provenance, Sigstore/cosign for signing, NIST SSDF for practice grouping.
  • Closed by default. A new dependency, an unsigned artifact, or a failing vuln gate blocks the release. Overrides are explicit, logged and expire.

2. Best practices (from research)

  1. SLSA provenance is verifiable information about where, when and how an artifact was produced, defined as an in-toto attestation predicate with predicateType https://slsa.dev/provenance/v1, where the build platform attests it produced artifacts by executing a buildDefinition (Source: https://slsa.dev/spec/v1.0/provenance, retrieved 2026-10-23; triangulated by https://slsa.dev/spec/v1.0/about, retrieved 2026-10-23).
  2. The in-toto attestation framework is the envelope: a Statement binds subjects (artifact digests) to a predicateType plus predicate, and SLSA provenance is one predicate type inside it (Source: https://github.com/in-toto/attestation, retrieved 2026-10-23; triangulated by SLSA provenance spec above).
  3. SPDX (Linux Foundation, ISO/IEC 5962:2021 for SPDX 2.2.1) and CycloneDX (OWASP, ECMA-424) are the two interoperable machine-readable SBOM formats satisfying the NTIA minimum elements: supplier name, component name, version, other unique identifiers, dependency relationship, SBOM author, timestamp (Source: https://spdx.dev/learn/overview/, retrieved 2026-10-23; Source: https://cyclonedx.org/specification/overview/, retrieved 2026-10-23; NTIA element list triangulated by https://github.com/spdx/ntia-conformance-checker, retrieved 2026-10-23). MoloApp emits SPDX JSON as canonical and CycloneDX JSON as secondary.
  4. Sigstore cosign keyless blob signing (cosign sign-blob without --key) obtains a short-lived certificate from Fulcio via OIDC identity and records the signature in the Rekor transparency log, producing a .sigstore.json bundle verified against the expected signer identity (--certificate-identity) and OIDC issuer (Source: https://docs.sigstore.dev/cosign/signing/signing_with_blobs/, retrieved 2026-10-23; triangulated by https://docs.sigstore.dev/about/overview/, retrieved 2026-10-23).
  5. NIST SP 800-218 v1.1 (SSDF, Feb 2022) defines outcome-based secure development practices in four groups: Prepare the Organization (PO), Protect the Software (PS), Produce Well-Secured Software (PW), Respond to Vulnerabilities (RV); it mandates outcomes, not tools (Source: https://csrc.nist.gov/pubs/sp/800/218/final, retrieved 2026-10-23).
  6. Triangulated release rule from (1)+(4)+(5): a trustworthy release needs all three of SBOM (PW.4.1 lineage), signed provenance from the build platform (PS.3, SLSA Build L2+), and a vulnerability response hook (RV) that blocks or discloses known CVEs before ship. Missing any one fails the gate.

3. Structure

Directories (one class or concern per file, deep over flat):

tools/supply/                 release-time only, never linked into the framework
  SbomGenerator.hpp/.cpp      molo::supply::sbom::Generator (inventory assembly)
  SpdxEmitter.hpp/.cpp        molo::supply::sbom::SpdxEmitter (SPDX 2.3 JSON)
  CycloneDxEmitter.hpp/.cpp   molo::supply::sbom::CycloneDxEmitter (CycloneDX 1.6 JSON)
  SbomCheck.hpp/.cpp          molo::supply::sbom::ConformanceCheck (NTIA elements)
include/molo/supply/
src/supply/
  policy/DependencyPolicy.hpp molo::supply::policy::DependencyPolicy (allow-list)
  policy/WolfPin.hpp/.cpp     molo::supply::policy::WolfPin (version, digest, license receipt)
  attest/ProvenanceBuilder.hpp molo::supply::attest::ProvenanceBuilder (SLSA v1 predicate)
  attest/Attestor.hpp/.cpp    molo::supply::attest::Attestor (in-toto Statement envelope)
  attest/Signer.hpp/.cpp      molo::supply::attest::Signer (cosign wrapper, keyless first)
  attest/Verify.hpp/.cpp      molo::supply::attest::Verifier (bundle + digest verify)
  scan/VulnHook.hpp/.cpp      molo::supply::scan::VulnHook (SBOM in, verdict out)
  scan/OsdvClient.hpp/.cpp    molo::supply::scan::OsvClient (OSV API query, capped)
sbom/                         generated per release, committed as tag asset only
specs/supply.md               normative spec mirror (owned by 10-workflow-example.md template)

Namespaces: molo::supply::sbom, ::policy, ::attest, ::scan. No other namespace signs, attests or scans. Signing keys never enter src/; only digests and bundle paths cross into tools/release/ from 10-workflow-example.md.

4. Rules (hard, numbered)

R15-1: wolfSSL is the sole third-party dependency. Any second dependency blocks the build until this spec is amended and the SBOM schema covers it. R15-2: Every tagged release ships four artifacts minimum: binary or tarball, sbom.spdx.json, sbom.cdx.json, provenance.intoto.jsonl plus one .sigstore.json bundle per signed file. Missing file blocks the tag. R15-3: SPDX JSON is canonical and must pass NTIA minimum-element conformance; CycloneDX JSON must describe the same component set (same name/version/digest triples). R15-4: Provenance predicateType is exactly https://slsa.dev/provenance/v1 inside an in-toto Statement; subject digests are SHA-256 over released bytes. R15-5: Signing is Sigstore keyless first (OIDC identity of the release workflow); long-lived keys are forbidden unless a human-approved exception with expiry exists. R15-6: The vuln-scan hook runs on the final SBOM before signing; a CRITICAL with a fix available blocks release, others attach as disclosed advisories. R15-7: wolfSSL is pinned by exact version plus SHA-256 of the vendored tarball plus license receipt path; any mismatch fails the build loudly. R15-8: Reproducibility: same git commit plus same toolchain record plus same wolfSSL tarball yields byte-identical SBOM component section (timestamps only in metadata, never in digests). R15-9: No network fetch during normal build. The only network allowed is the release pipeline calling OSV, Fulcio and Rekor, each behind timeout, retry cap and offline fail-closed behavior. R15-10: Verification is public and offline-capable: molo verify checks digests, bundle chain and provenance subject match using only release assets plus Sigstore roots. R15-11: All caps in section 6 are static constants. No silent growth of SBOM size, scan window or signature count. R15-12: This file never duplicates sibling scope: no TLS config (03-security.md), no git/tag mechanics (10-workflow-example.md), no CLI UX (09-cli-ops.md), no audit log schema (12-audit-compliance.md). It provides SBOM, policy verdicts, provenance and bundles those siblings consume.

5. Development guidelines

  • Build order follows section 7: policy pin first, then SBOM, then scan hook, then provenance, then signing, then verify. Each step lands with its spec delta and template output checked in as golden files.
  • Generators are pure functions over (git commit, toolchain file, wolfSSL pin, file manifest). No timestamps inside hashed content; timestamps live only in SBOM metadata and provenance buildMetadata.finishedOn.
  • Treat the SBOM as code: diff it in review, fail CI on unexpected component delta, keep golden SBOMs for the last three releases in sbom/golden/.
  • Wrap external CLIs (cosign) behind Signer with a fake for tests; never shell out from framework code, only from tools/supply/.
  • Log every policy decision with reason code (allow, pin-mismatch, vuln-block, exception-expired) through 02-observability.md; denials are loud, never silent skips.
  • License hygiene: wolfSSL commercial license receipt path is recorded in the SBOM supplier field and provenance materials; receipt missing blocks release.

6. Specifications (concrete behaviors, caps, formats)

Dependency policy:

  • Allow-list file supply.allow.json: exactly one entry {name: wolfssl, version: X.Y.Z, sha256: hex, source: vendored-tarball-url-or-path, license: commercial, receipt: path}. Unknown entry or version drift fails make supply-check.
  • Toolchain record supply.toolchain.json: compiler id plus version, linker, CMake or Make version, build flags hash. Max 100 entries, max 64 KB file.

SBOM:

  • Formats: SPDX 2.3 JSON (sbom.spdx.json) canonical, CycloneDX 1.6 JSON (sbom.cdx.json) secondary. Both UTF-8, LF, sorted keys, 2-space indent.
  • Components: moloapp (supplier MoloApp, version from VERSION, id git commit), wolfssl (supplier wolfSSL Inc, pinned version, externalRefs: url plus purl pkg:generic/wolfssl@X.Y.Z), toolchain (supplier recorded, version string). Each carries SHA-256 checksum, license declared plus concluded, downloadLocation or NOASSERTION with justification.
  • NTIA fields mandatory on every component: supplier, name, version, unique id (SPDXID plus purl/CPE where known), relationship (DEPENDS_ON from moloapp), author (MoloApp release pipeline), timestamp (RFC 3339 UTC).
  • Caps: max 64 components, max 256 relationships, SBOM JSON max 1 MB per format, component name max 256 chars, version max 64 chars.

Vuln-scan hook:

  • Input: canonical SPDX JSON. Query OSV API https://api.osv.dev/v1/query per component (purl plus version), timeout 10 s per request, max 64 queries per release, 1 retry, offline yields unknown verdict which blocks signing until a human override with 7-day expiry.
  • Verdicts: clean, disclosed (non-critical or no fix, attached as advisories.json), blocked (critical CVSS >= 9.0 with fix, or wolfSSL CVE with fix regardless of score). Blocked stops the pipeline before provenance generation.

Provenance and signing:

  • Provenance: in-toto Statement v1, predicateType: https://slsa.dev/provenance/v1, subjects are {name, digest: {sha256: hex}} per released file, buildDefinition.buildType is https://moloapp.dev/build/v1, externalParameters holds git commit, toolchain hash, wolfSSL pin digest; runDetails.builder.id is the release workflow identity URI; runDetails.metadata.invocationId is the pipeline run id.
  • Signing: cosign sign-blob --yes --bundle <file>.sigstore.json <file> per artifact; verify with cosign verify-blob --bundle --certificate-identity <workflow-id> --certificate-oidc-issuer https://token.actions.githubusercontent.com <file>. SHA-256SUMS file signed as one blob additionally.
  • Layout per tag vX.Y.Z: molo-X.Y.Z.tar.gz, SHA256SUMS, sbom.spdx.json, sbom.cdx.json, advisories.json, provenance.intoto.jsonl, plus .sigstore.json per file. Max 32 signed files per release.
  • molo verify <dir>: recompute SHA-256, check bundle chain, check provenance subjects match digests, check SBOM component digests match wolfSSL pin; exit 0 verified, 1 mismatch (first mismatch quoted), 2 usage error. No network required.

7. Execution steps (in build order)

E15-1: DependencyPolicy plus WolfPin: allow-list schema, pin check in make supply-check, golden fixtures (good pin, drifted version, bad digest). E15-2: SbomGenerator plus SpdxEmitter: inventory assembly, canonical JSON, sorted output, NTIA conformance self-check. E15-3: CycloneDxEmitter plus equivalence test: same triples as SPDX, golden pair diff in CI. E15-4: VulnHook plus OsvClient: query, verdict mapping, advisories.json writer, offline fail-closed path. E15-5: ProvenanceBuilder plus Attestor: SLSA v1 predicate, in-toto Statement envelope, subject digest binding. E15-6: Signer plus Verifier: cosign keyless wrapper, bundle layout, offline molo verify command core. E15-7: Pipeline wiring into 10-workflow-example.md release order (suite green, SBOM, scan, provenance, sign, tag) and 09-cli-ops.md molo release surface. E15-8: Hardening: golden SBOMs for three releases, tamper drills (bit-flip, pin drift, expired exception), docs and living-example supply page.

8. Validation (how to verify each step)

V15-1: Pin fixtures: correct tarball passes, one-byte change fails with digest quoted, version bump without allow-list edit fails, missing license receipt fails. V15-2: SPDX golden test: regenerate twice, byte-identical except timestamp; NTIA checker passes; component count is exactly 3 (moloapp, wolfssl, toolchain) on a clean tree. V15-3: Equivalence test: extract (name, version, sha256) triples from both SBOMs, sets equal; CycloneDX validates against its JSON schema. V15-4: Scan matrix: clean SBOM yields clean; fixture with known-fixed critical yields blocked; fixture with no-fix advisory yields disclosed plus advisories.json entry; network cut yields unknown and signing refused. V15-5: Provenance test: subjects match sha256sum of release files; predicateType exact; builder id equals workflow identity; tampered artifact fails subject match. V15-6: Sign and verify round-trip against local Sigstore stub plus one manual live run: valid bundle verifies, wrong identity fails, bit-flipped file fails, offline verify works with cached roots. V15-7: End-to-end dry run on fixture repo: full order executes, a blocked verdict stops before signing, a green run produces all files in the layout with caps respected. V15-8: Drill suite: expired exception blocks, oversized SBOM (>1 MB) rejected, 33rd signed file rejected, living-example supply page shows SBOM plus verify instructions on a small-phone viewport.

9. References (sibling files this depends on or feeds)

  • Depends on 03-security.md: wolfSSL version and TLS posture, hash and RNG primitives for digests, secret redaction in SBOM and provenance.
  • Depends on 09-cli-ops.md: molo release, molo verify, molo version UX, embedder manifest hash reuse, exit-code mapping.
  • Depends on 10-workflow-example.md: release pipeline order, tag gating, VERSION and CHANGELOG linkage, spec and test templates.
  • Depends on 02-observability.md: structured logs and metrics for policy verdicts and scan outcomes.
  • Feeds 12-audit-compliance.md: SBOM, provenance, bundles and advisories as audit evidence; retention pointers.
  • Feeds 14-api-governance.md: SBOM and provenance as governed release contracts (formats versioned, breaking changes major).
  • Feeds 11-tenancy.md and 13-operability.md: per-tenant build identity inputs and SBOM exchange notes (formats defined here, transport there).
  • Never duplicates: 10-workflow-example.md owns git mechanics, 09-cli-ops.md owns CLI flags, 12-audit-compliance.md owns audit retention.

10. Open questions

Q1: SLSA Build L3 (hardened builder, non-falsifiable provenance) in v1 or L2 first with a documented L3 path? Proposal: L2 now, L3 tracked as gap. Q2: OSV only, or OSV plus a second feed (wolfSSL security list, CISA KEV) for the vuln hook? Proposal: OSV plus wolfSSL advisory check, KEV later. Q3: Rekor transparency proof required at verify time, or bundle signature plus identity sufficient for v1? Proposal: bundle sufficient, checkpoint proof optional flag. Q4: SPDX 3.0 migration timeline? Proposal: stay on SPDX 2.3 JSON until tooling converges, track 3.0 as gap.

11. Sources (exact URLs with retrieval dates)