SBOMs (Software Bill of Materials)
An SBOM (Software Bill of Materials) is the per-build ingredient list — every direct and transitive dependency baked into an artifact, with versions and hashes. fremforge stores SBOMs your CI produces so the inventory survives across builds, image rotations, and dependency drift.
When SBOMs are generated automatically
There is nothing to install. fremforge generates a CycloneDX 1.6 SBOM centrally for every pull request, on its own runners, and binds that SBOM to a release when you publish one — any tag, including the usual semver / calver shapes (v1.2.3, v2026.06).
We deliberately do not re-scan at release time. An SBOM is a function of the source tree, and the tree you release has already been scanned as the head of the pull request that merged it, so generating a second one would produce a byte-identical file. Publishing a release instead marks the SBOM already held for that exact commit as the release SBOM. Two consequences worth knowing:
- It is immediate. No build runs, so the release SBOM is available as soon as the release is published.
- The binding is server-side. fremforge resolves your tag to its commit through Forgejo and matches on that commit, so nothing in a build can claim a release SBOM it did not earn.
If you publish a release for a commit that never went through a pull request — a tag on a very old commit, or a repository whose history predates scanning — there is no SBOM to bind. That release is reported as uncovered rather than silently skipped; re-run a scan on the commit to cover it.
Trigger an SBOM by tagging a release
From the command line:
git tag v1.2.3
git push origin v1.2.3From the Forgejo UI: open the repo → Releases → New release — set a tag name starting with v (e.g. v1.2.3) and click Publish.
A one-off SBOM for an arbitrary commit is also available via the Actions tab → SBOM (Syft) → Run workflow.
Why tag-only? Generating SBOMs on every commit produces dozens of nearly-identical manifests that nobody reads — auditors want the SBOM “at the moment we shipped release X”. Tag-only keeps the SBOM count proportional to your actual release cadence. If your team genuinely needs per-commit SBOMs (high-frequency release trains), edit the workflow’s
on:block in your repo and addpush: { branches: [main] }.
What fremforge accepts
fremforge generates CycloneDX 1.6 SBOMs and accepts both CycloneDX and SPDX at ingest:
- CycloneDX JSON (1.6) — preferred; this is what Syft on the runner emits. The wider tooling ecosystem (Trivy, Syft, anchore) emits CycloneDX natively.
- SPDX 2.x / 3.x (JSON) — also accepted at ingest. The ingest validates
spdxVersionbefore storing.
Each SBOM is stored with:
- The SHA-256 digest of the artifact it describes (so the SBOM provably belongs to that build).
- An optional in-toto attestation bundle (JSONL) — Cosign-signed when your workflow signs the SBOM. The ingest validates the JSONL shape + per-line in-toto fields before storing.
- The artifact name + version, exactly as your workflow names it. Used for the de-duplication key (same digest + name + version = one row, not many).
5 MiB hard cap per SBOM. The ingest endpoint returns 413 payload-too-large above that — chunk large monorepo SBOMs by module if you hit it.
How to push an SBOM from CI
The fremforge runner image ships Syft and Trivy, so most workflows are a 3-line snippet:
- name: Generate + push SBOM
run: |
syft packages dir:. --output cyclonedx-json=sbom.json
DIGEST=$(sha256sum dist/myapp | cut -d' ' -f1)
curl -sS --fail \
-H "Authorization: Bearer ${{ secrets.FREMFORGE_API_TOKEN }}" \
-F "sbom=@sbom.json" \
-F "artifact_name=myapp" \
-F "artifact_version=${GITHUB_SHA}" \
-F "sha256_digest=${DIGEST}" \
-F "format=cyclonedx-json" \
"https://frem.sh/_app/api/v1/orgs/${ORG}/sboms"The PAT needs the sboms:write scope; mint one from Admin → API tokens with the SBOM-push scope checked.
Where SBOMs surface in the admin UI
Admin → Security → SBOMs lists every ingested SBOM, grouped by artifact. Each row shows:
- Artifact name + version + SHA-256 digest
- Format (CycloneDX / SPDX) + size
- Ingested timestamp + the user/PAT that pushed it
- “Verify signature” link when an attestation is attached
How SBOMs interact with other security signals
- Scorecard — drives the Dependencies + Vulnerabilities scores on the OpenSSF Scorecard computation.
- OSV recommendations — when an OSV advisory matches a component listed in an SBOM, the recommendation surfaces with the SBOM-anchored impact (“this affects build
myapp@v1.2.3from 2026-05-12”). - Cosign — the SBOM-signing flow uses the same Cosign trust root as image signing. See Cosign for the verification path.
Retention
SBOMs follow the per-tenant audit retention policy (90 / 180 / 365 / 730 days configurable). The SHA-256 digest of every SBOM stays in the hash-chained event index forever for tamper-evidence; the JSON payload itself ages out per your policy.
See also
- Cosign — image + SBOM signing.
- OSV recommendations — vulnerabilities matched to SBOM components.
- Scorecard — SBOMs drive the dependency + vulnerability scores.
- API reference —
POST /api/v1/orgs/{slug}/sbomsingest endpoint.