Skip to main content

SBOM

The portal generates Software Bill of Materials (SBOM) artifacts from the latest successful scan. Four interchange formats are supported, plus an attribution NOTICE file.

Project detail — SBOM tab with format selector and last-scan summary

Audience

Engineers shipping releases, compliance leads filing artifacts, customers fulfilling SBOM requests under EO 14028. Read access via team membership.

Supported formats

FormatQuery value (format=)MIMEUse case
CycloneDX 1.6 (JSON)cyclonedx-jsonapplication/vnd.cyclonedx+jsonModern de-facto standard for SCA tooling. Includes VEX.
CycloneDX 1.6 (XML)cyclonedx-xmlapplication/vnd.cyclonedx+xmlSame data; XML for legacy tooling.
SPDX 2.3 (JSON)spdx-jsonapplication/spdx+jsonNTIA minimum elements; broadly accepted in regulated industries.
SPDX 2.3 (Tag-Value)spdx-tvtext/spdxThe original SPDX line-based format.

Both formats are produced from the same internal model, so component lists are identical (modulo format-specific fields).

What's included per component

Each component carries its name, version, Package URL (PURL), and the licenses detected for it:

  • Licenses — populated from the scan's license findings. CycloneDX uses the per-component licenses array (preferring the concluded verdict, then declared, then detected); SPDX fills licenseDeclared and licenseConcluded as SPDX license expressions. Components with no detected license — and licenses with no SPDX identifier (ORT LicenseRef-*) — emit the spec sentinel NOASSERTION in SPDX (CycloneDX still carries the license name). copyrightText is currently always NOASSERTION.
  • Top-level versionmetadata.component.version reflects the scanned release: if the scan was submitted with a release label (e.g. v1.2.3), that label is used; otherwise the scan id is used as a stable fallback.

Byte-stable output

All four exports are byte-stable: re-exporting the same scan produces identical bytes. This makes diffing, signing, and caching trivial.

The portal achieves byte-stability by:

  • Sorting components by purl (lexicographic).
  • Sorting license expressions alphabetically within each component.
  • Pinning serialNumber (CycloneDX) / documentNamespace (SPDX) to a deterministic value derived from (project_id, scan_id).
  • Omitting timestamps from the body (the SBOM's metadata records the scan finish time, which is stable per scan).

Download from the UI

  1. Open the project.
  2. Click the SBOM tab.
  3. Click one of the four format buttons (CycloneDX JSON, CycloneDX XML, SPDX JSON, SPDX Tag-Value) to download.

SBOM tab — four format download buttons (CycloneDX JSON/XML, SPDX JSON/Tag-Value)

The file name is sbom-<project-slug>.<ext>.

Download from the API

The API serves the SBOM in CycloneDX JSON:

…and in SPDX JSON (same endpoint, different format):

# CycloneDX JSON
curl -sS -L -OJ \
-H "Authorization: Bearer ${TRUSTEDOSS_API_KEY}" \
"https://trustedoss.example.com/v1/projects/${PROJECT_ID}/sbom?format=cyclonedx-json"

# SPDX JSON
curl -sS -L -OJ \
-H "Authorization: Bearer ${TRUSTEDOSS_API_KEY}" \
"https://trustedoss.example.com/v1/projects/${PROJECT_ID}/sbom?format=spdx-json"

format accepts: cyclonedx-json, cyclonedx-xml, spdx-json, spdx-tv.

Endpoint always exports the latest succeeded scan's SBOM; pinning to a specific historical scan id is on the roadmap.

Audit evidence — pin scans externally

The SBOM export always reflects the latest succeeded scan. External auditors typically ask for the SBOM at a specific release point (e.g. "what shipped on 2026-01-15?"). Until historical-scan pinning lands, capture the SBOM artifact at each release boundary and store it in your release archive. Treat the portal as the current SBOM, not the historical one.

NOTICE file

For Apache-2.0 §4(d) compliance and similar attribution obligations, the portal auto-generates a NOTICE attribution body from the project's latest scan.

The file contains:

  • A header with the project name and generation timestamp.
  • One section per detected license, listing the components (name @ version) under that license.
  • Each license section's attribution obligations (e.g. attribution, no-endorsement) with a short description and a policy reference link.

Per-component copyright statements are not included yet — copyright capture (and a manual override in the component drawer) is on the roadmap. Until then, fulfil copyright-notice obligations from the upstream package contents directly.

Supported formats

The NOTICE endpoint accepts a format query value (default text):

FormatQuery value (format=)MIMEExtensionUse case
Plain texttexttext/plain.txtDrop into a release tarball's NOTICE file. The default.
Markdownmarkdowntext/markdown.mdRender in a docs site or PR description.
HTMLhtmltext/html.htmlA self-contained document (inline <style>, no scripts) for an attribution page.

The output is byte-stable across exports for a given scan and format — diffable across releases.

Download

  • UI: Project → Obligations tab → pick a format (text or HTML) → Download NOTICE. The browser saves NOTICE-<project>.<ext>. The markdown variant is available from the API.

  • API:

    # Plain text (default)
    curl -sS -L -OJ \
    -H "Authorization: Bearer ${TRUSTEDOSS_API_KEY}" \
    "https://trustedoss.example.com/v1/projects/${PROJECT_ID}/notice?format=text&download=true"

    # HTML
    curl -sS -L -OJ \
    -H "Authorization: Bearer ${TRUSTEDOSS_API_KEY}" \
    "https://trustedoss.example.com/v1/projects/${PROJECT_ID}/notice?format=html&download=true"

    format accepts text, markdown, html. Pass download=true so the response carries Content-Disposition: attachment and -OJ saves it under the server-supplied file name (NOTICE-<project>.<ext>); omit it to stream the body inline.

VEX exports

CycloneDX SBOMs include the project's VEX state for every finding. SPDX does not have a native VEX representation, so SPDX exports omit per-finding state; pair an SPDX export with a separate CycloneDX VEX document if your downstream consumer expects it.

Each entry in the SBOM's vulnerabilities[] array carries the CVE id, its source database, the VEX analysis, and affects[].ref pointing at the affected component's bom-ref within the same document (so consumers can join findings to components without parsing PURLs).

The VEX states map directly to CycloneDX's analysis.state; the analyst's free-text note (when present) is carried in analysis.detail:

Portal stateCycloneDX VEX stateanalysis.detail
Newin_triage(none)
Analyzingin_triageanalyst note
Exploitableexploitableanalyst note
Not affectednot_affectedanalyst note
False positivefalse_positiveanalyst note
Suppressednot_affectedanalyst note
Fixedresolvedanalyst note

The closed CycloneDX analysis.justification enum (code_not_present, …) is never emitted: its members have a precise meaning that cannot be inferred from free-form analyst prose, so the note stays in analysis.detail.

Verify it worked

  1. The downloaded SBOM passes a validator — for CycloneDX, run cyclonedx validate:

    cyclonedx validate --input-file checkout-service.sbom.json
  1. SPDX validates with spdx-tools:

    pyspdxtools -i checkout-service.sbom.json
  1. Re-downloading the same scan produces a byte-identical file:

    sha256sum checkout-service.sbom.json checkout-service.sbom.json.again
    # → identical hashes

Troubleshooting

Empty SBOM when no scan has succeeded yet

If the project has no succeeded scan yet, the export still returns a valid SBOM document with empty components/packages lists (HTTP 200) so downstream tooling can parse it.

422 from /sbom?format=…

The query string used a value the API does not accept. Use one of the four canonical query values from the table above — in particular, the SPDX Tag-Value format is spdx-tv (not spdx-tag-value).

404 for a project you cannot access

The SBOM and NOTICE endpoints existence-hide: a caller who is not a member of the project's team receives 404 (not 403), the same response as a project id that does not exist. This is deliberate — unlike the project-detail endpoint (which returns 403), the SBOM/NOTICE bodies expose structural detail (component names, versions), so the endpoints refuse to confirm a project even exists to a non-member. Join the owning team to get access.

The NOTICE file does not include per-component copyright statements in this release — it lists components and their attribution obligations grouped by license. Copyright capture (and a manual override in the component drawer) is on the roadmap; SPDX exports carry copyrightText: NOASSERTION for the same reason.

Compliance evidence trail

External auditors typically ask portal operators five questions. This table tells you which are answerable today and which require workarounds.

Auditor questionv0.10.0 answer sourceLimitation
"Show me the SBOM as of release X"Manual archive; portal only retains latestHistorical pinning on the roadmap
"Who downloaded the SBOM / NOTICE in the last quarter?"structlog (Loki / journald) — not audit_logsAudit-row promotion on the roadmap
"Show me when GPL was first detected on project X"audit_logs on scans.create + per-scan vulnerability_findings.createYes — full evidence chain
"Show me every approval verdict in 2026 Q1"audit_logs on component_approvals.update + decision_noteYes — full evidence chain
"Prove no audit row was tampered with"Append-only trigger (migration 0012)Super-admin role still has bypass — review audit-log hardening

Supplier submission compatibility

The export satisfies common corporate supplier SBOM requirements (e.g. SK Telecom's supplier guide): standard format/version (CycloneDX, SPDX 2.3), ISO-8601 timestamp, tool metadata, per-component name + version + PURL, licenses, and transitive dependencies (when the scanned source includes or can resolve lockfiles).

Two caveats to be aware of before submitting:

  • pkg:generic/ PURLs are rejected by some programs. A generic PURL means the scanner could not classify the component's ecosystem; supply lockfiles / build artifacts so cdxgen can assign an ecosystem-specific type.
  • Licenses without an SPDX id appear as NOASSERTION in SPDX expressions (the CycloneDX license.name still carries the label).

Roadmap

Items the manual previously promised that are not in this release; tracked for later releases.

  • The vulnerability PDF report is implemented in this release — see Vulnerabilities → Download a PDF report (GET /v1/projects/{id}/vulnerability-report.pdf). Still not implemented: the Excel reports (Components Excel, Vulnerabilities Excel) and the Compliance PDF; there are no /v1/projects/{id}/reports/... endpoints for those, and they will land in a later release. Stakeholders who need a tabular view today should consume the SBOM (CycloneDX JSON) via their preferred tooling.
  • Manual copyright override in the component drawer for NOTICE assembly — planned.
  • Historical-scan pinning on the SBOM and NOTICE exports — planned.
  • Promote SBOM / NOTICE downloads from structlog events to audit_logs rows — planned.

See also