Source vs Build SBOMs
Scala’s primary build tool, sbt (Scala Build Tool), manages dependencies through build.sbt. Unlike some other ecosystems, sbt doesn’t use a traditional lockfile by default, which creates challenges for SBOM generation.
- Source SBOMs are generated from
build.sbtand resolved dependencies - Build SBOMs analyze compiled artifacts in the
target/directory
For accurate SBOMs, you need to resolve dependencies first using sbt plugins.
Dependency Declaration
build.sbt
Dependencies are declared in build.sbt:
name := "my-app"
version := "1.0.0"
scalaVersion := "3.3.1"
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.10.0",
"io.circe" %% "circe-core" % "0.14.6",
"io.circe" %% "circe-generic" % "0.14.6",
"io.circe" %% "circe-parser" % "0.14.6",
"org.http4s" %% "http4s-ember-server" % "0.23.24",
"org.http4s" %% "http4s-circe" % "0.23.24",
"org.http4s" %% "http4s-dsl" % "0.23.24",
// Test dependencies
"org.scalatest" %% "scalatest" % "3.2.17" % Test,
"org.scalamock" %% "scalamock" % "5.2.0" % Test
)
Understanding %% vs %
// %% appends Scala version (cross-compiled)
"org.typelevel" %% "cats-core" % "2.10.0"
// Resolves to: cats-core_3 or cats-core_2.13
// % uses artifact as-is (Java libraries)
"org.postgresql" % "postgresql" % "42.7.0"
Cross-Compilation
Scala libraries are often published for multiple Scala versions:
// project/build.properties
sbt.version=1.9.8
// build.sbt
crossScalaVersions := Seq("2.13.12", "3.3.1")
scalaVersion := "3.3.1"
This means dependencies resolve to different artifacts based on Scala version:
cats-core_2.13for Scala 2.13cats-core_3for Scala 3
Your SBOM should reflect the specific Scala version used for your build.
sbt-dependency-graph Plugin
For detailed dependency analysis, use the sbt-dependency-graph plugin:
// project/plugins.sbt
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.10.0-RC1")
Generate dependency information:
# ASCII tree
sbt dependencyTree
# Export to file
sbt "dependencyList" > dependencies.txt
# Generate DOT graph
sbt dependencyDot
sbt-dependency-lock Plugin
For lockfile-based reproducibility:
// project/plugins.sbt
addSbtPlugin("io.github.davidgregory084" % "sbt-dependency-lock" % "1.1.0")
# Generate lockfile
sbt dependencyLockWrite
# Verify against lockfile
sbt dependencyLockCheck
This creates build.sbt.lock:
# This file was generated by sbt-dependency-lock
org.scala-lang:scala-library:2.13.12
org.typelevel:cats-core_2.13:2.10.0
org.typelevel:cats-kernel_2.13:2.10.0
Generating an SBOM
SBOM generation is the first step in the SBOM lifecycle. After generation, you typically need to enrich your SBOM with package metadata and augment it with your organization’s details.
Using sbomify GitHub Action (Recommended)
The sbomify GitHub Action is a swiss army knife for SBOMs that automatically selects the best generation tool for your ecosystem, enriches the output with package metadata, and optionally augments it with your business information—all in one step.
For Scala, sbomify uses cdxgen under the hood.
Standalone (no account needed):
- uses: sbomify/github-action@master
env:
LOCK_FILE: build.sbt
OUTPUT_FILE: sbom.cdx.json
COMPONENT_NAME: my-scala-app
COMPONENT_VERSION: ${{ github.ref_name }}
ENRICH: true
UPLOAD: false
Using github.ref_name automatically captures your git tag (e.g., v1.2.3) as the SBOM version. For rolling releases without tags, use github.sha instead. See our SBOM versioning guide for best practices.
With sbomify platform (adds augmentation and upload):
- uses: sbomify/github-action@master
env:
TOKEN: ${{ secrets.SBOMIFY_TOKEN }}
COMPONENT_ID: my-component-id
LOCK_FILE: build.sbt
OUTPUT_FILE: sbom.cdx.json
AUGMENT: true
ENRICH: true
Alternative Tools
If you prefer to run SBOM generation tools manually:
cdxgen (recommended for manual use):
npm install -g @cyclonedx/cdxgen
cdxgen -t sbt -o sbom.cdx.json
sbt-sbom plugin:
// project/plugins.sbt
addSbtPlugin("com.github.sbt" % "sbt-sbom" % "0.4.0")
sbt sbom
When using these tools directly, you’ll need to handle enrichment and augmentation separately.
GitLab CI
generate-sbom:
image: sbomifyhub/sbomify-action
variables:
LOCK_FILE: build.sbt
OUTPUT_FILE: sbom.cdx.json
UPLOAD: "false"
ENRICH: "true"
script:
- /sbomify.sh
artifacts:
paths:
- sbom.cdx.json
Multi-Module Projects
Scala projects often have multiple modules:
// build.sbt
lazy val root = (project in file("."))
.aggregate(core, api, web)
lazy val core = (project in file("core"))
.settings(
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.10.0"
)
)
lazy val api = (project in file("api"))
.dependsOn(core)
.settings(
libraryDependencies ++= Seq(
"io.circe" %% "circe-core" % "0.14.6"
)
)
lazy val web = (project in file("web"))
.dependsOn(api)
.settings(
libraryDependencies ++= Seq(
"org.http4s" %% "http4s-ember-server" % "0.23.24"
)
)
Generate SBOMs per module or aggregate:
# All modules
sbt dependencyTree
# Specific module
sbt "project api" dependencyTree
Handling Special Cases
Resolvers and Private Repositories
resolvers ++= Seq(
"Sonatype Releases" at "https://oss.sonatype.org/content/repositories/releases/",
"Private Repo" at "https://repo.example.com/maven2"
)
credentials += Credentials(Path.userHome / ".sbt" / ".credentials")
Exclusions and Overrides
libraryDependencies ++= Seq(
"org.example" %% "library" % "1.0.0" exclude("commons-logging", "commons-logging")
)
// Force specific version
dependencyOverrides += "com.google.guava" % "guava" % "32.1.3-jre"
Provided Dependencies
libraryDependencies ++= Seq(
"javax.servlet" % "javax.servlet-api" % "4.0.1" % Provided
)
Provided dependencies should be documented but noted as provided by the runtime environment.
Compile-time Only
libraryDependencies ++= Seq(
"org.wartremover" %% "wartremover" % "3.1.6" % "compile->compile"
)
Best Practices
- Use dependency locking - Install sbt-dependency-lock for reproducibility
- Pin Scala version - Be explicit about scalaVersion
- Document cross-compilation - Note which Scala version your SBOM represents
- Resolve before generating - Run
sbt updatebefore SBOM generation - Separate test dependencies - Use
% Testscope appropriately - Check for updates - Use
sbt dependencyUpdateswith sbt-updates plugin
Security Scanning
Use sbt-dependency-check for vulnerability scanning:
// project/plugins.sbt
addSbtPlugin("net.vonbuchholtz" % "sbt-dependency-check" % "5.1.0")
# Run vulnerability check
sbt dependencyCheck
# Generate report
sbt dependencyCheckReport
Combine with SBOM generation:
- name: Security check
run: sbt dependencyCheck
- name: Generate SBOM
uses: sbomify/github-action@master
env:
LOCK_FILE: 'build.sbt'
OUTPUT_FILE: 'sbom.cdx.json'
Mill Build Tool
For projects using Mill instead of sbt:
// build.sc
import mill._
import mill.scalalib._
object myapp extends ScalaModule {
def scalaVersion = "3.3.1"
def ivyDeps = Agg(
ivy"org.typelevel::cats-core:2.10.0",
ivy"io.circe::circe-core:0.14.6"
)
}
Mill has different tooling but similar SBOM approaches:
mill show myapp.ivyDepsTree
Further Resources
For more SBOM tools and resources, see our SBOM Resources page, which includes general SBOM utilities for generation, distribution, and analysis.