Files
HiveGrid-Wireframes/hivegrid-cloud/docs/security/sbom.md
T

193 lines
10 KiB
Markdown

# Software Bill of Materials (SBOM)
This document is the canonical SBOM workflow for the platform. It explains what a Software Bill of Materials is, why we ship one, how it is generated, how it is distributed, and how it is consumed by vulnerability scanners.
> Cross-references:
> - Generator: [`scripts/generate-sbom.mjs`](../../scripts/generate-sbom.mjs)
> - Threat model component coverage: [`threat-model.md`](./threat-model.md)
> - Incident-response forensics: [`incident-response.md`](./incident-response.md) §4 "Forensics"
## 1. What is an SBOM?
A Software Bill of Materials is a machine-readable inventory of every software component that ships in a build artifact. For this platform it includes:
- Every npm/pnpm workspace package shipped in the runtime images (Web, API, Worker)
- Every transitive dependency resolved by pnpm (including dev-only and production-only)
- The version, license (when declared), and resolved tarball hash of each dependency
- The git commit and build identifier of the repository at the time of build
An SBOM is not a vulnerability report. It is the input to a vulnerability scanner — the scanner correlates the SBOM against a CVE feed (NIST NVD, GHSA, OSV) and produces the actual vulnerability report.
## 2. Why we ship one
- **Customer procurement** — enterprise procurement teams require an SBOM with every release. Without one, the platform cannot be evaluated by a regulated customer.
- **Executive Order 14028 / NIST SP 800-218 (SSDF)** — US federal procurement requires SBOMs for software shipped to federal customers. Even if the operator is not a US federal supplier today, the SBOM lowers the bar to that market.
- **EU Cyber Resilience Act (CRA)** — once the CRA's reporting requirements bite, an SBOM is the input to the operator's CRA-mandated vulnerability handling process.
- **Internal vulnerability handling** — when a new CVE lands, the operator can answer "are we affected?" in minutes by querying the SBOM, not by re-resolving the lockfile under time pressure.
- **Incident forensics** — when investigating an incident potentially caused by a third-party library, the SBOM is the canonical record of what shipped.
## 3. Format
The repository currently ships a **pnpm-native dependency manifest** rather than a strict CycloneDX or SPDX document. The reason is documented at the top of `scripts/generate-sbom.mjs`:
- `@cyclonedx/cyclonedx-npm` and SPDX-tooling for npm tend to mis-handle pnpm workspaces (`apps/*`, `packages/*`), producing partial trees or wrongly-attributed transitive edges.
- pnpm's own resolver is the source of truth for what ships in our images, so we capture its `pnpm list -r --json --depth=Infinity` output verbatim.
- The artifact is consumed for **audit traceability** ("which version of which package shipped in this image"), not for CycloneDX-validator gating.
The output JSON has this shape:
```json
{
"schema": "toolctrl.sbom-pnpm/v1",
"generatedAt": "<ISO timestamp>",
"generator": "scripts/generate-sbom.mjs",
"source": "pnpm list -r --json --depth=Infinity",
"repo": { "name": "<root pkg name>", "version": "<root pkg version>" },
"workspaces": [ /* pnpm list output */ ]
}
```
The file is named `sbom-pnpm.json` and is committed at the **build artifact level only** — it is generated at build time and shipped inside the runtime images. It is **not** committed to the source repository.
### 3.1 Migration path to CycloneDX
When a customer procurement step requires a strict CycloneDX 1.4+ document, the operator can:
1. Run `pnpm dlx @cyclonedx/cyclonedx-npm --output-format JSON --output-file sbom.cdx.json --omit dev`
2. Validate with `pnpm dlx @cyclonedx/cyclonedx-cli validate --input-file sbom.cdx.json`
3. Diff against `sbom-pnpm.json` to confirm component coverage
The operator-side conversion is **out of band** from the build; it does not gate releases. If/when a future task makes CycloneDX a build gate, this section becomes the migration target.
## 4. When the SBOM is generated
The Dockerfile attempts to generate the SBOM at **every Docker build**. It invokes `scripts/generate-sbom.mjs` in the `build` stage and copies the resulting `sbom-pnpm.json` into each runtime stage (Web, API, Worker). SBOM generation is intentionally skip-safe in the Dockerfile: a generator failure logs `SBOM generation skipped` instead of failing the image build, because the SBOM is an audit-traceability artifact rather than a deploy gate.
Each runtime image therefore carries the same repository dependency inventory for the built revision. Treat it as the npm/pnpm component record for that build, then pair it with an image scanner such as Trivy for OS packages and runtime binaries.
Manual local generation (for example, to answer "are we affected by CVE-X?" before a release):
```bash
pnpm exec node scripts/generate-sbom.mjs
# Output: ./sbom-pnpm.json
```
The script reads `SBOM_OUTPUT_PATH` for a non-default destination; it is otherwise side-effect free.
## 5. Distribution
### 5.1 Inside runtime images
Every container image carries `/myapp/sbom-pnpm.json`. Operators can inspect it with:
```bash
docker run --rm <image> cat /myapp/sbom-pnpm.json | jq '.workspaces | length'
```
### 5.2 Per release
Release owners may publish the SBOM as a release asset named `sbom-pnpm-v<version>.json` when a customer or procurement process requires a standalone artifact. The baseline deploy workflow always embeds the SBOM in the runtime images; standalone release-asset publication is operator-owned unless a release workflow explicitly adds that upload step.
### 5.3 To customers
Customers who need an SBOM under their procurement agreement receive:
- The `sbom-pnpm.json` for the version they run
- On request: a converted CycloneDX 1.4 JSON (see [§3.1](#31-migration-path-to-cyclonedx))
- On request: a signed manifest (see [§6](#6-signing))
The operator does **not** publish SBOMs for unreleased branches or pre-release tags. Customers running staging or pre-release builds receive the SBOM bundled with the build.
### 5.4 To researchers
The SBOM is **not** published publicly by default. It is included in the artifact pack provided to penetration testers under engagement (see [`pen-test-scope.md`](./pen-test-scope.md) §10) and is provided to coordinated-disclosure reporters who request it.
Rationale: SBOM transparency is a security trade-off. The SBOM lets a scanner correlate against CVE feeds, which is good for defence; it also tells an attacker exactly which library versions to target. NIST EO-14028 and the EU CRA accept the trade-off; the operator accepts the same trade-off for procurement customers but does not publish to the open internet without a reason.
## 6. Signing
When the operator's threat model requires it (typically: enterprise customers requiring tamper-evident SBOM delivery), the SBOM is signed with [Cosign](https://github.com/sigstore/cosign):
```bash
cosign sign-blob \
--key cosign.key \
--output-signature sbom-pnpm-v<version>.json.sig \
sbom-pnpm-v<version>.json
```
Customers verify with:
```bash
cosign verify-blob \
--key cosign.pub \
--signature sbom-pnpm-v<version>.json.sig \
sbom-pnpm-v<version>.json
```
The signing key is held in the operator's password manager. The public key is published on the operator's security page. Rotation cadence: every 12 months or on suspected compromise.
## 7. Vulnerability scanner integration
The SBOM is the input to whichever scanner the operator uses. Documented integrations:
### 7.1 Snyk
```bash
snyk test --file=sbom-pnpm.json --package-manager=npm
```
Snyk consumes the dependency tree and reports CVE matches.
### 7.2 Trivy
```bash
trivy sbom sbom-pnpm.json
```
Trivy is the recommended **per-image** scanner: it can also scan the OS-layer of the runtime image (Alpine packages) which the pnpm SBOM does not cover.
### 7.3 Dependency-Track
The operator's preferred long-term SBOM-management tool is [Dependency-Track](https://dependencytrack.org/). Workflow:
1. Convert `sbom-pnpm.json` → CycloneDX (see [§3.1](#31-migration-path-to-cyclonedx))
2. Upload the CycloneDX document to the Dependency-Track project for this release
3. Dependency-Track watches the OSV / NVD / GHSA feeds and notifies the operator when a new CVE matches a shipped component
### 7.4 GitHub Dependabot / Gitea Renovate
These tools operate on the **source repository's lockfile**, not on the shipped SBOM. They are complementary — Dependabot/Renovate raises PRs to upgrade dependencies; the SBOM proves which version actually shipped.
## 8. Incident workflow
When a new CVE is announced that may affect a shipped dependency, the [incident-response runbook](./incident-response.md) Phase 1 ("Detection") and Phase 2 ("Triage") apply. The SBOM-specific steps are:
1. Identify the affected component name and version range from the CVE record
2. Query the SBOM for the affected component across **every shipped image**:
```bash
jq -r '.workspaces[] | .. | objects | select(.name=="<component>") | "\(.name)@\(.version)"' sbom-pnpm.json | sort -u
```
3. Decide impact: is the affected codepath invoked by our code? (Mapping to call-sites is operator engineering.)
4. If yes, the incident enters Phase 3 ("Containment") as documented in [`incident-response.md`](./incident-response.md) §3.
## 9. Limitations
The current SBOM:
- **Covers npm/pnpm dependencies only.** It does not include OS-level packages (Alpine apk), runtime binaries (node itself, ClamAV daemon, ffmpeg, etc.), nor Rust/Go native modules compiled into Prisma / sharp / similar. Trivy on the image fills that gap.
- **Does not include declared licenses** in a normalised form. The pnpm-list output includes whatever the package metadata declares; license-compliance work is operator-side.
- **Is generated at build time, not at run time.** If an operator hot-patches a node_module inside a running container (which is forbidden by the deployment policy but possible), the SBOM does not reflect that mutation.
## 10. Maintenance
This document is reviewed:
- When the SBOM generator script changes
- When the format requirement from a procurement customer changes
- When the operator adds or replaces the vulnerability scanner integration
- At every major release tag
Update the "Last review" line below as part of the review.
_Last review: docs/improvement-plan/10-compliance-polish/security-docs.md task #69._