---
title: SBOM
---
```js
const API = "http://127.0.0.1:8000";
```
```js
// Fetch SBOM data on load
let _entries = [], _report = {groups: [], copyleft_direct_count: 0}, _repos = [];
try {
[_entries, _report, _repos] = await Promise.all([
fetch(`${API}/sbom/`).then(r => r.ok ? r.json() : []),
fetch(`${API}/sbom/report/licences/`).then(r => r.ok ? r.json() : {groups:[], copyleft_direct_count: 0}),
fetch(`${API}/repos/`).then(r => r.ok ? r.json() : []),
]);
} catch {}
```
```js
const entries = _entries ?? [];
const report = _report ?? {groups: [], copyleft_direct_count: 0};
const repos = _repos ?? [];
const groups = report.groups ?? [];
const riskCount = report.copyleft_direct_count ?? 0;
```
# SBOM
## Licence Risk
```js
const riskBadge = riskCount === 0
? html`✓ No copyleft in direct prod deps`
: html`⚠ ${riskCount} direct prod dep(s) with copyleft licence`;
display(html`
Total Packages
${entries.length}
Repos Scanned
${new Set(entries.map(e => e.repo_id)).size}
Licence Risk
${riskCount}
${riskBadge}
Unique Licences
${groups.length}
`);
```
## Licence Distribution
```js
import * as Plot from "npm:@observablehq/plot";
if (groups.length === 0) {
display(html`No SBOM data ingested yet. Run make ingest-sbom REPO=<slug>.
`);
} else {
const plotData = groups.slice(0, 15).map(g => ({
licence: g.license_spdx ?? "(unknown)",
count: g.count,
copyleft: g.is_copyleft,
}));
display(Plot.plot({
x: {label: "Packages"},
y: {label: null, domain: plotData.map(d => d.licence)},
color: {domain: [false, true], range: ["steelblue", "#e53935"], legend: true, tickFormat: d => d ? "Copyleft" : "Permissive"},
marks: [
Plot.barX(plotData, {y: "licence", x: "count", fill: "copyleft", tip: true}),
Plot.ruleX([0]),
],
marginLeft: 130,
height: Math.max(80, plotData.length * 30 + 50),
width: 600,
}));
}
```
## Copyleft Risk Detail
```js
const copyleftGroups = groups.filter(g => g.is_copyleft);
if (copyleftGroups.length === 0) {
display(html`✓ No copyleft packages found.
`);
} else {
display(html`
${copyleftGroups.map(g => html`
${g.license_spdx ?? "unknown"}
${g.count} package(s)
${g.repos.join(", ")}
`)}
`);
}
```
## Package Table
```js
// Filters
const ecoFilter = Inputs.select(["all", "python", "node", "rust", "go", "java", "other"], {label: "Ecosystem", value: "all"});
const directOnly = Inputs.toggle({label: "Direct deps only", value: false});
const prodOnly = Inputs.toggle({label: "Prod deps only (no dev)", value: false});
display(html`
${ecoFilter}${directOnly}${prodOnly}
`);
```
```js
// Build repo_id → slug lookup
const repoById = Object.fromEntries(_repos.map(r => [r.id, r.slug]));
const filteredEntries = entries.filter(e =>
(ecoFilter.value === "all" || e.ecosystem === ecoFilter.value) &&
(!directOnly.value || e.is_direct) &&
(!prodOnly.value || !e.is_dev)
);
display(Inputs.table(filteredEntries.map(e => ({
Package: e.package_name,
Version: e.package_version ?? "—",
Ecosystem: e.ecosystem,
Licence: e.license_spdx ?? "—",
Repo: repoById[e.repo_id] ?? e.repo_id?.slice(0, 8) ?? "—",
Direct: e.is_direct ? "✓" : "",
Dev: e.is_dev ? "✓" : "",
})), {maxWidth: 900}));
```