Safeguard
Software Supply Chain•

How do you generate an SBOM for a Scala sbt project?

Generate the SBOM from the resolved dependency graph, not from build.sbt. What to capture for Scala, Java and cross-built artifacts, and how to check it.

Safeguard Security Team
4 min read

To generate an SBOM for a Scala sbt project, produce it from the resolved dependency graph after sbt has done resolution, in a standard format such as CycloneDX or SPDX, with exact versions of every direct and transitive component. Reading build.sbt alone misses transitive libraries and cross-build suffixes.

Why not read build.sbt?

The build file states what you asked for. It does not state what you got. The sbt documentation explains that dependencies are declared as group, artifact and revision, and that a double percent sign selects the artifact built for the Scala version you are using. It also says that if resolution fails, the dependency probably was not published for your Scala version. Two things follow for an SBOM.

First, the artifact name on disk differs from the name in the file. A dependency written with the double percent sign resolves to a name with a Scala binary version suffix, such as an underscore and 2.13. An SBOM that records the unsuffixed name will not match vulnerability data keyed on the published coordinates.

Second, transitive dependencies are decided by the resolver. The sbt documentation notes that sbt 1.3.0 and later uses Coursier for dependency management, where earlier versions used Apache Ivy. Eviction and version selection happen in that step, so only the resolved result tells you which version is on your classpath.

A constructed example: the build file lists a web framework at one version. Its transitive tree includes a JSON library at an older version, while another dependency requires a newer one. The resolver evicts the older. An SBOM from the declared list shows the old version and flags a vulnerability that is not present. One from the resolved graph shows the new version and the right result.

What formats and tools exist?

CycloneDX and SPDX are the two widely used formats. The CycloneDX tool center lists tools and filters them by language, and Scala appears among the supported languages. Check the tool center for a current sbt plugin or a build-agnostic scanner, read its documentation for your sbt version and test it on one project before you roll it out. Whatever you use, confirm that output includes the items below.

What should a good Scala SBOM contain?

  1. Every component from the resolved runtime classpath, with group, artifact and exact version, expressed as Maven coordinates or package URLs.
  2. Transitive dependencies, including Java libraries, since most Scala applications depend heavily on the Java ecosystem.
  3. The Scala binary version of each cross-built artifact, so names match published coordinates.
  4. Separation of runtime from test and build-only dependencies, or at least a scope label, so you can focus on what ships.
  5. Hashes where the tool can provide them, and licenses.
  6. Metadata about the build: the project name, version, commit and time.

If you ship a fat jar or a container image, also scan the final artifact, since shading can bundle classes that no longer appear as separate libraries.

How do you check the result?

Compare the SBOM against the resolved tree printed by sbt for one module. Spot-check five components for exact version and coordinates. Run a vulnerability match and confirm that a known-vulnerable test dependency, added on purpose in a scratch branch, appears. Regenerate on every release build and store the file next to the release artifact, so you can answer later what shipped.

Safeguard can ingest SBOMs and scan dependencies, and its reachability analysis helps separate vulnerable libraries your code calls from those it does not. The free scan at gold.safeguard.sh/scan covers up to 100 dependencies if you want to try the idea on one project.

What to do

  1. Pick one module and generate an SBOM from the resolved graph in your CI, not on a laptop.
  2. Verify it against sbt's own view of the dependency tree.
  3. Add scope labels, hashes and licenses if missing.
  4. Run it for every release and archive it with the artifact.
  5. Scan the SBOM and the final jar or image for vulnerabilities.
  6. Repeat for each module or aggregate them into one product SBOM.

Quick check

Why do Scala artifact names end with a version suffix?

Libraries are built per Scala binary version. The sbt double percent syntax selects the artifact built for your Scala version, so the published name includes that suffix.

Is the declared dependency list enough for an SBOM?

No. It omits transitive libraries and does not show which versions the resolver actually chose.

Sources

  1. sbt: Library management
  2. CycloneDX Tool Center
  3. CycloneDX specification overview

Last reviewed: 18 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.