Safeguard
Supply Chain•

How do you pin build toolchains in a monorepo? A checklist

Pin the build tool, the language runtime and the download sources, then block silent auto-downloads in CI. A checklist with Bazel and Gradle examples.

Safeguard Security Team
4 min read

Pin three layers: the build tool itself, the language toolchain it uses, and the places those are downloaded from. Then make CI fail when anything drifts. A pinned dependency list means little if the compiler and the build tool can change between two runs.

Why toolchains count as dependencies

Bazel's hermeticity guidance says hermetic builds treat tools as source code and depend on specific versions of build tools such as compilers. The remote caching page adds a warning: Bazel currently does not track tools outside a workspace, so two users with different compilers installed can wrongly share cache hits. A compiler is an input. Unpinned inputs make caches unreliable and builds unreviewable.

The three layers

LayerExampleHow to pin
Build toolBazel, Gradle, Nx, TurborepoVersion file plus a launcher or wrapper, checked in
Language toolchainJDK, Node, Go, PythonDeclared in the build, not read from the host
Download sourcesToolchain repositories, registriesFixed URLs, checksums, controlled mirrors

Layer one: the build tool

For Bazel, the lockfile documentation recommends using a launcher such as bazelisk and checking in a Bazel version file. Because Bazel is itself a dependency, the lockfile is specific to the Bazel version and can change even between compatible releases. For Gradle, use the wrapper and verify it, as covered in the post on checking the Gradle wrapper before running it. For Node based tools, pin them in the repository's package manifest and lockfile.

Layer two: the language toolchain

Gradle supports Java toolchains, declared with a language version in the java block. Its documentation says building with different Java versions on different machines can cause unexpected issues, and that toolchains improve reproducibility. It also warns that if no toolchain is defined, Gradle may use the IDE's Java setting, which can lead to non-reproducible behavior. For strict cross compilation, the release option controls how the compiler treats the source but does not choose which JDK runs.

Layer three: download sources

Gradle can auto-provision a JDK when none matches, if a toolchain download repository is configured. Installed JDKs land in the Gradle user home, and auto-provisioning never updates an existing one. You can disable it with a property set to false, then ensure the required JDK is already installed. For a locked down CI, disable auto-download and bake the toolchain into the runner image, or provision it from a source you control.

# gradle.properties
org.gradle.java.installations.auto-download=false

The checklist

  1. Check in the build tool version file and use the launcher or wrapper everywhere.
  2. Declare language toolchains in the build, with no reliance on host paths.
  3. Disable silent auto-downloads in CI, or restrict them to a known repository.
  4. Keep a document of every toolchain download URL and its checksum.
  5. Build in a clean container with only the source and an explicit list of host tools.
  6. Fail CI if the lockfile or toolchain declarations change without review.
  7. Scan the toolchain versions for advisories, because compilers and runtimes have vulnerabilities too.
  8. Rebuild on a schedule so drift shows up as a failure and not a surprise.

Where Safeguard fits

Safeguard can inventory the dependencies your repository declares through SBOM and dependency scanning, and reachability analysis helps you decide which findings matter first. You can try a free scan on a repository to see what it finds. Check coverage for your specific build files before relying on it.

Try it yourself

Open the demo and look at how a project's inventory lists components. Toolchain and runtime versions deserve the same visibility.

Click through it at your own pace, or open it full screen.

Quick check

Is pinning the JDK in Gradle enough to pin the build tool?

No. The toolchain controls the Java used for compiling and running project code. Gradle itself still needs its own pinned version through the wrapper.

Does a hermetic build need pinned toolchains?

Yes. Using specific versions of build tools is part of what makes a build hermetic.

Sources

Last reviewed: 19 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.