Pants is one of the build systems that gets less attention than Bazel or Buck2, and that is a shame because it is the most approachable of the three for Python-heavy codebases. I have been running Pants in production for a Django and Flask polyrepo since the 1.x days, and we migrated to Pants 2.18 in early 2024. What follows is an honest look at the supply chain properties of Pants -- what it does well, where the rough edges are, and how it compares to its competitors.
Pants 2.x Is a Different Tool
If you last looked at Pants during the 1.x era, the current product is effectively a different project. Pants 2.0 was released in October 2020 as a ground-up rewrite in Rust (the engine) with plugins in Python (the rule logic). The 1.x codebase was a JVM-centric system that lost focus as it tried to serve both Twitter's and Foursquare's diverging needs. Pants 2.x dropped the JVM focus, embraced Python as the plugin language, and adopted a rule-based execution model inspired by Bazel.
For supply chain, the Rust engine matters because it provides the sandbox. Every build action in Pants 2.x runs in a process with restricted filesystem visibility, using a combination of unshare (Linux) and chroot-style jails. The engine enforces that actions only see declared inputs. On macOS, sandboxing is weaker -- Pants relies on path restriction rather than OS-level namespace isolation.
Lockfiles: Pants' Supply Chain Foundation
The most important supply chain feature in Pants is generate-lockfiles. Pants manages Python third-party dependencies via resolves -- named dependency sets defined in pants.toml:
[python]
enable_resolves = true
interpreter_constraints = ["CPython==3.11.*"]
default_resolve = "python-default"
[python.resolves]
python-default = "3rdparty/python/default.lock"
data-science = "3rdparty/python/data-science.lock"
ml-training = "3rdparty/python/ml-training.lock"
Each resolve has a lockfile generated by pants generate-lockfiles --resolve=python-default. The lockfile is a PEP 665-style manifest with pinned versions and integrity hashes. Unlike raw pip install, Pants enforces the lockfile at build time: if a requirements.txt changes without a lockfile regeneration, the build fails.
This is the single biggest supply chain win in Pants 2.x over the 1.x days. In 1.x, dependency resolution happened at runtime with no deterministic pin. A developer could add a new Flask version to a BUILD file and CI would silently pull whatever was latest-compatible. 2.x closes that hole.
The gotcha: generate-lockfiles itself needs to run in a trusted environment. Generating a lockfile is an implicit act of trust -- you are saying "this set of transitive versions is acceptable." Treat lockfile changes as security-sensitive. We require two reviewers on any PR that modifies 3rdparty/python/*.lock and run a vulnerability scan on the diff before merge.
Pexes: Deployment Artifacts with a Supply Chain Twist
Pants' canonical Python deployment artifact is a PEX -- the Python Executable format. A PEX is a zip file containing the Python source plus all third-party dependencies, with a __main__.py that sets up sys.path correctly. From a supply chain perspective, PEXes have two important properties:
- They are self-contained. All dependencies ship inside the PEX, so there is no runtime pip install.
- They are deterministic if built with
--pex-build-isolated-python=trueandSOURCE_DATE_EPOCHset.
We build PEXes with:
pants package --pex-binary-layout=zipapp \
--pex-build-isolated-python \
src/python/payments:api_pex
The resulting PEX can be scanned, signed, and attestation-wrapped as a single artifact. Our CI signs every PEX with cosign and uploads the attestation alongside the artifact. Deployment verifies the signature before execution. This gives a cleaner story than "pip install in a Docker container" because the artifact boundary is unambiguous.
Third-Party for Non-Python
Pants handles Go, Shell, Docker, and several other languages in addition to Python. The supply chain story varies significantly by backend:
- Go: Uses
go.mod/go.sumdirectly. Pants delegates to the Go toolchain for resolution, so you get the Go proxy's integrity guarantees. - JavaScript/Node.js: Experimental support via
pants-plugins. Lockfile handling is not production-grade as of Pants 2.18. - Docker: Pants builds OCI images via a shim to
docker buildorbuildah. Hermeticity depends on the underlying builder. - Shell scripts: No inherent integrity checking. Shell scripts are run as-is from the repo.
The uneven coverage means a mixed-language monorepo inherits the weakest link. If you run a Pants monorepo with Python, Go, and some custom shell glue, the shell scripts are the supply chain weak point. We mitigate with pre-commit hooks that require shellcheck and signed commits for any .sh file.
pants.toml and Environment Isolation
Pants 2.x introduced the concept of "environments" in 2.14 -- named execution targets that can be local, remote, or Docker-based. The relevant pants.toml section:
[environments-preview.names]
local = "//:local_environment"
linux_ci = "//:linux_ci_environment"
docker_build = "//:docker_build_environment"
[python-bootstrap]
search_path = ["<PYENV_LOCAL>"]
Environments matter for supply chain because they let you run untrusted builds (open-source fork PRs, for example) in an isolated Docker environment that has no access to production credentials or internal mirrors. A docker_environment with a pinned image digest gives you a controlled trust boundary that is stronger than a raw local build.
The common mistake is defining environments and then never using them. Audit your pants.toml to confirm that CI targets are actually scheduled onto the intended environment.
Remote Execution and Caching
Pants supports REAPI-compatible remote execution and caching. Enable it with:
[GLOBAL]
remote_execution = true
remote_store_address = "grpcs://buildbarn.internal:8980"
remote_executor_address = "grpcs://buildbarn.internal:8980"
remote_ca_certs_path = "/etc/ssl/internal-ca.pem"
remote_store_headers = { "authorization" = "Bearer {env.BUILDBARN_TOKEN}" }
Same caveats as other REAPI-speaking build systems: the remote cache is a write-through attack surface, and the executor cluster is a production-grade dependency. Rotate credentials, separate buckets by trust tier, and monitor for unexpected action digests.
Pants' remote caching has historically been less battle-tested than Bazel's. We hit two bugs in 2.16 where cached outputs were served for actions whose inputs had changed, because of a subtle hash collision in the Python interpreter constraint logic. Both were fixed in 2.17.2, but the incident taught us to run PR validation builds with --no-remote-cache-read for the critical packaging steps.
What Pants Gets Right
The lockfile-first model is Pants' strongest supply chain property. Every resolve has a committed lockfile, lockfile generation is explicit and reviewable, and the build fails if the lockfile drifts from the BUILD files. This is a better default than pip install -r requirements.txt and more approachable than Bazel's pip_parse.
PEX artifacts give a clean attestation boundary. Cosign and Rekor-signed PEXes with SLSA provenance form a coherent artifact supply chain from source to deployment.
Where It Falls Short
Cross-language supply chain is uneven. Python is first-class; Go is good; JavaScript and shell are weaker. A monorepo with serious JavaScript content should probably use a JavaScript-native tool or accept that Pants is not covering that surface well.
The plugin API is Python, which means plugin authors write code that runs inside the build engine. A malicious plugin has the same supply chain risk as a malicious build rule elsewhere. Vet any plugin from outside the Pants core organization.
How Safeguard Helps
Safeguard ingests Pants-generated lockfiles and PEX artifacts to produce CycloneDX 1.5 SBOMs with full Python, Go, and native-dependency visibility across every resolve. Policy gates can block PRs that modify 3rdparty/python/*.lock without pulling known-safe versions, and attestation workflows verify that PEX artifacts reaching production match the provenance emitted at build time. For teams running Pants remote execution, Safeguard correlates REAPI action digests with source commits to detect cache poisoning or unexpected action outputs. The result is a supply chain view that extends Pants' lockfile model with the vulnerability enrichment and governance the tool does not provide on its own.