generated from coulomb/repo-seed
docs(sbom): add SBOM reference page + withDocHelp on SBOM dashboard
- docs/sbom.md: what SBOM is, lockfile semantics, 5-level maturity standard, gap types A–E, per-ecosystem guidance, Syft OSS tooling, inter-repo task communication convention, ingest commands, compliance check commands - sbom.md: wire withDocHelp(h1, "/docs/sbom") — ? button on page title - observablehq.config.js: add SBOM entry to Reference nav section EP-CUST-002 registered: Syft-based comprehensive SBOM generation Task 5f8cade5 created: [repo:railiance-bootstrap] Add Ansible lockfile Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ export default {
|
||||
{ name: "Decisions", path: "/docs/decisions" },
|
||||
{ name: "Decision Health", path: "/docs/decisions-kpi" },
|
||||
{ name: "Progress Log", path: "/docs/progress-log" },
|
||||
{ name: "SBOM", path: "/docs/sbom" },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
210
dashboard/src/docs/sbom.md
Normal file
210
dashboard/src/docs/sbom.md
Normal file
@@ -0,0 +1,210 @@
|
||||
---
|
||||
title: SBOM — Reference
|
||||
---
|
||||
|
||||
# Software Bill of Materials (SBOM)
|
||||
|
||||
This page defines what an SBOM is, why it matters, what the Custodian SBOM
|
||||
standard requires of registered repos, and how to bring a repo into compliance.
|
||||
|
||||
---
|
||||
|
||||
## What is an SBOM?
|
||||
|
||||
An SBOM (Software Bill of Materials) is an inventory of every component a
|
||||
piece of software depends on — direct and transitive, runtime and build-time.
|
||||
|
||||
For software projects this means: every library installed via pip, npm, cargo,
|
||||
or any other package manager. For infrastructure repos it means: Ansible itself,
|
||||
Terraform providers, system tools the playbooks invoke. For container images: OS
|
||||
packages, base image layers, language runtimes.
|
||||
|
||||
The key question an SBOM answers is: **"what exactly is running, and at which
|
||||
version?"**
|
||||
|
||||
---
|
||||
|
||||
## What a lockfile is — and why it matters
|
||||
|
||||
A **lockfile** is the machine-generated, committed answer to that question for
|
||||
one package manager. When you run `uv lock`, `npm install`, or `terraform init`,
|
||||
the tool resolves all transitive dependencies, pins them to exact versions, and
|
||||
writes those pins to a lockfile (`uv.lock`, `package-lock.json`,
|
||||
`.terraform.lock.hcl`).
|
||||
|
||||
Without a lockfile:
|
||||
|
||||
| Problem | Consequence |
|
||||
|---------|-------------|
|
||||
| Versions are not pinned | Different machines or CI runs get different versions — one may work, another may not |
|
||||
| No transitive inventory | You know you depend on `ansible`, but not which version of `paramiko` or `cryptography` it pulls in |
|
||||
| Vulnerability scanning is imprecise | CVE databases require exact versions; a range like `ansible>=8` can't be scanned |
|
||||
| Licence auditing is impossible | You can't know the licence of every transitive dependency |
|
||||
| Reproducibility breaks | Debugging a production incident requires knowing the exact versions in use |
|
||||
|
||||
The lockfile is the **unit of SBOM evidence** for package-managed dependencies.
|
||||
The State Hub ingests lockfiles to populate the SBOM store.
|
||||
|
||||
---
|
||||
|
||||
## The Custodian SBOM Standard
|
||||
|
||||
Every registered repo is assessed against five maturity levels. A repo must
|
||||
reach **Level 3** to be considered SBOM-compliant.
|
||||
|
||||
| Level | Name | Criterion |
|
||||
|-------|------|-----------|
|
||||
| **0** | Registered | Repo appears in the State Hub `/repos/` |
|
||||
| **1** | Manifested | For every ecosystem in use, a **manifest file** exists and is committed (`pyproject.toml`, `package.json`, `Cargo.toml`, `go.mod`, `ansible/requirements.yml`, etc.) |
|
||||
| **2** | Locked | Every manifest file has a corresponding **lockfile** committed to the repo |
|
||||
| **3** | Ingested | `last_sbom_at` is not null; the ingested packages cover all detected ecosystems |
|
||||
| **4** | Current | `last_sbom_at` is within 30 days, or since the last lockfile change |
|
||||
| **5** | Clean | No unreviewed copyleft flags in direct prod dependencies; no unknown licences in direct deps |
|
||||
|
||||
### SBOM gap types
|
||||
|
||||
**Type A — Missing manifest**: dependencies exist but nothing declares them.
|
||||
Example: Ansible is installed on the control node but there is no `pyproject.toml`
|
||||
declaring `ansible` as a dependency. Fix: create the manifest.
|
||||
|
||||
**Type B — Manifest without lockfile**: a `pyproject.toml` or `package.json`
|
||||
exists but no lockfile has been generated. Fix: run `uv lock` / `npm install`.
|
||||
|
||||
**Type C — Lockfile not ingested**: lockfile exists but `make ingest-sbom` has
|
||||
not been run, so the State Hub has no record. Fix: run `make ingest-sbom`.
|
||||
|
||||
**Type D — Stale ingest**: lockfile exists and was ingested, but has since been
|
||||
updated (new deps added) without a fresh ingest. Fix: re-run `make ingest-sbom`.
|
||||
|
||||
**Type E — Ecosystem not supported**: the repo uses an ecosystem the ingest
|
||||
script doesn't yet parse (Go, Java, Ruby, Ansible Galaxy collections). The
|
||||
SBOM gap is expected until support is added. Register a contribution (FR) if
|
||||
the ecosystem is important for your domain.
|
||||
|
||||
---
|
||||
|
||||
## Per-ecosystem guidance
|
||||
|
||||
### Python (uv)
|
||||
```bash
|
||||
uv init --no-workspace # creates pyproject.toml if absent
|
||||
uv add ansible # adds dep + resolves transitive tree
|
||||
uv lock # generates or updates uv.lock
|
||||
git add pyproject.toml uv.lock && git commit
|
||||
```
|
||||
Then ingest: `make ingest-sbom REPO=<slug> SCAN=1 REPO_PATH=<path>`
|
||||
|
||||
### Node / npm
|
||||
`package-lock.json` is generated automatically by `npm install`. Commit it.
|
||||
Licence metadata is embedded per package — the State Hub reads it directly.
|
||||
|
||||
### Rust
|
||||
`Cargo.lock` is generated automatically by `cargo build` or `cargo check`.
|
||||
Commit it (for binaries; libraries typically do not commit it, but SBOM
|
||||
ingestion requires it).
|
||||
|
||||
### Terraform
|
||||
Run `terraform init` in each module directory — this generates
|
||||
`.terraform.lock.hcl`. The `--scan` mode on `make ingest-sbom` finds it
|
||||
automatically.
|
||||
|
||||
### Ansible (Galaxy collections)
|
||||
If your playbooks use roles or collections from Ansible Galaxy, add them to
|
||||
`ansible/requirements.yml`:
|
||||
```yaml
|
||||
collections:
|
||||
- name: community.general
|
||||
version: ">=9.0"
|
||||
roles: []
|
||||
```
|
||||
*Note: `requirements.yml` does not include version pins for Ansible itself.
|
||||
Use `pyproject.toml` + `uv.lock` to pin the `ansible` pip package.*
|
||||
|
||||
### Infra-only repos (Ansible, shell, no Galaxy collections)
|
||||
The minimum expectation is still a `pyproject.toml` declaring the
|
||||
control-node pip dependencies (at least `ansible`). This enables:
|
||||
- Pinning the Ansible version (reproducibility)
|
||||
- SBOM ingestion (licence + vulnerability auditing)
|
||||
- A machine-readable baseline for future syft-based assessment
|
||||
|
||||
---
|
||||
|
||||
## OSS tooling — Syft (recommended for comprehensive assessment)
|
||||
|
||||
The State Hub's current ingest relies on hand-rolled lockfile parsers.
|
||||
A more powerful alternative is **[Syft](https://github.com/anchore/syft)**
|
||||
(Anchore, Apache 2.0):
|
||||
|
||||
- Scans a directory and detects **50+ ecosystems** automatically
|
||||
- Works even when lockfiles are absent (uses manifest files to derive deps)
|
||||
- Outputs standard **SPDX** or **CycloneDX** JSON
|
||||
- Handles: Python, Node, Rust, Go, Java, Ruby, PHP, .NET, Ansible collections,
|
||||
Terraform providers, OS packages in container images
|
||||
|
||||
```bash
|
||||
# Install
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
|
||||
|
||||
# Scan a directory
|
||||
syft dir:/home/worsch/railiance-bootstrap -o cyclonedx-json > sbom.json
|
||||
```
|
||||
|
||||
Syft integration is tracked as **EP-CUST-002** in the Extension Points
|
||||
catalogue. When implemented, `make ingest-sbom-syft` will replace the
|
||||
hand-rolled parsers for comprehensive coverage.
|
||||
|
||||
---
|
||||
|
||||
## Inter-repo task communication
|
||||
|
||||
When the State Hub or custodian identifies a compliance gap in a registered repo,
|
||||
the task is communicated through two channels:
|
||||
|
||||
1. **State Hub task** — created in the relevant domain workstream with
|
||||
`[repo:<slug>]` in the title. Visible via `get_state_summary()` at the
|
||||
start of any domain session.
|
||||
|
||||
2. **Workplan file** — a `workplans/<ID>-<slug>.md` file is created in the
|
||||
target repo itself (ADR-001 convention). When you open that repo in Claude
|
||||
Code, the session protocol surfaces it.
|
||||
|
||||
When working in a registered repo, always run `get_state_summary()` at session
|
||||
start — the state hub surfaces pending tasks for your domain automatically.
|
||||
|
||||
---
|
||||
|
||||
## Ingest commands
|
||||
|
||||
```bash
|
||||
# Auto-detect lockfile at repo root
|
||||
make ingest-sbom REPO=<slug> REPO_PATH=/path/to/repo
|
||||
|
||||
# Scan entire tree — required for multi-ecosystem repos
|
||||
make ingest-sbom REPO=<slug> SCAN=1 REPO_PATH=/path/to/repo
|
||||
|
||||
# Explicit lockfile
|
||||
make ingest-sbom REPO=<slug> LOCKFILE=/path/to/uv.lock
|
||||
|
||||
# Dry run (parse but do not submit)
|
||||
.venv/bin/python scripts/ingest_sbom.py --repo <slug> --scan --repo-path /path --dry-run
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checking compliance
|
||||
|
||||
```bash
|
||||
# View all repos and their SBOM status
|
||||
# Dashboard → Repos (http://127.0.0.1:3000/repos)
|
||||
|
||||
# API: check last_sbom_at per repo
|
||||
curl -s http://127.0.0.1:8000/repos/ | python3 -c "
|
||||
import json, sys
|
||||
for r in json.load(sys.stdin):
|
||||
status = r['last_sbom_at'] or '⚠ NOT INGESTED'
|
||||
print(f'{r[\"slug\"]:30} {status}')
|
||||
"
|
||||
|
||||
# API: licence risk summary
|
||||
curl -s http://127.0.0.1:8000/sbom/report/licences/ | python3 -m json.tool
|
||||
```
|
||||
@@ -40,6 +40,12 @@ const isCopyleft = spdx => spdx && COPYLEFT_KW.some(k => spdx.toUpperCase().inc
|
||||
|
||||
# SBOM
|
||||
|
||||
```js
|
||||
import {withDocHelp} from "./components/doc-overlay.js";
|
||||
const _h1 = document.querySelector("#observablehq-main h1");
|
||||
if (_h1) { _h1.style.position = "relative"; withDocHelp(_h1, "/docs/sbom"); }
|
||||
```
|
||||
|
||||
## Overview
|
||||
|
||||
```js
|
||||
|
||||
Reference in New Issue
Block a user