Secure Scala dependency management with sbt means three habits. Declare exact versions instead of dynamic ones, restrict which repositories sbt may resolve from, and inspect the fully resolved tree, including evicted versions, rather than trusting what build.sbt appears to say. Then scan that resolved set on every build.
What does sbt actually resolve?
Your build.sbt lists direct dependencies, usually as "group" %% "artifact" % "version". sbt resolves transitive ones, picks a winner when versions collide, and downloads the result. Since sbt 1.3.0, Coursier does this work. Before that it was Apache Ivy.
The default conflict rule is to take the latest revision. That is convenient and also a security fact: a library you never chose can end up on your classpath at a version you never reviewed, and a vulnerable older version can be selected if a build forces it.
Why are dynamic versions a risk?
sbt can accept revision constraints such as latest.integration or a range, as the sbt library management page documents. The build then changes without any commit. Two developers, or a developer and CI, can resolve different jars on the same day.
A constructed example. A service declares "com.example" %% "codec" % "2.+". On Monday it resolves 2.4.1. On Tuesday the maintainer's account is abused and a 2.4.2 is published containing code that reads environment variables. The next CI run picks it up with no pull request, no review and no diff. A fixed version would have held the build at 2.4.1 until someone chose to move.
What about the %% operator and artifact names?
For Scala libraries the artifact name carries a binary version suffix such as _2.13 or _3. The %% operator appends it for you. Typosquatting and confusion attacks use the same idea in reverse: a name that looks right but is a different group or a near-identical artifact id. When you add a dependency, copy the coordinates from the project's own documentation and check the group id matches the organization that owns the project.
How do resolvers change the trust model?
sbt uses the standard Maven repository by default, and resolvers += name at location adds others. Each extra repository is another place a name can be satisfied. If an internal artifact name is also published publicly, whichever repository answers first may win, which is the setup for dependency confusion.
Reduce that exposure:
- Route all resolution through one internal proxy repository and block direct access from CI.
- Use HTTPS locations only.
- Avoid snapshot repositories in release builds, since snapshots are mutable by design.
- Treat the local Maven repository as untrusted on shared build machines.
How do you review what changed?
Print the dependency tree and the update report. Recent sbt versions include a dependencyTree task, and the update report shows evicted modules and the reason. An eviction from 2.1.4 to 2.3.7 means two versions of one library were requested, and sbt documents that binary compatibility between them is not guaranteed, which is a correctness issue as well as a security one.
Commit a record of resolved versions. Community plugins can write a lock file, or you can store the tree output and diff it in pull requests. The goal is that any change in the resolved set shows up in review.
What to do
- Replace every dynamic revision with an exact version.
- Check each new dependency's group id against the project's own docs.
- Send all resolution through one controlled proxy and use HTTPS.
- Generate an SBOM from the resolved tree in CI, not from build.sbt alone.
- Diff the resolved tree in every pull request that touches dependencies.
- Scan the resolved set for known vulnerabilities and fail on agreed severity.
- Upgrade on a schedule so security fixes do not wait for a crisis.
Safeguard's CLI and dependency scanning read the resolved set and use reachability analysis to flag which vulnerable methods your code can actually call, which helps rank a long sbt report.
Quick check
Is a version range in sbt a vulnerability?
Not by itself. It removes your control over when new code enters the build, so a bad release can arrive unreviewed. Exact versions plus scheduled upgrades give you that control back.
Does the Scala binary suffix affect security?
Indirectly. It is part of the artifact name, so copying coordinates carefully matters, and mixing suffixes can pull two builds of one library onto the classpath.
Sources
- sbt reference: Library Management
- NIST SP 800-218: Secure Software Development Framework
- CycloneDX specification overview
Last reviewed: 18 October 2026.