2.3.2 Sandboxing

Sandboxing is Bazel's execution-time reality check for the principle from 2.3.1 Hermeticity. Before an action runs, Bazel prepares a working directory for that one action, stages the inputs Bazel knows about, runs the tool there, and keeps only the declared outputs. That is how Bazel turns "declared inputs only" from a design goal into something the local machine can actually enforce.1,2,3

What does Bazel put inside one action's sandbox?
Bazel stages declared inputs into a per-action directory, runs the tool there, and keeps the declared outputs.
Declared Inputs
Bazel already knows these files
Source, tools, runtime data
srcs tools data
execroot inputs
pkg/foo.cc
tools/my_codegen
config/schema.json
These are the inputs Bazel can hash, cache, and ship elsewhere.
Sandbox
The action runs in a prepared directory
Undeclared paths are not staged in
sandbox/.../execroot
pkg/foo.cc
tools/my_codegen
config/schema.json
bazel-out/pkg/foo.o
processwrapper-sandbox builds the directory and cleans it up.
linux-sandbox / darwin-sandbox add host-side restrictions.
Result
Only the declared outputs come back
Good for caching and remote execution
outputs declared output
bazel-out/...
pkg/foo.o
pkg/foo.d
Hidden host-tool leaks show up here before a bad result spreads through the cache.
local skips the middle isolation step. sandboxed picks the best available sandbox for the platform.

What Bazel isolates for an action

Sandboxing lives in 2.2.1 Loading, Analysis & Execution because execution is the first phase where real tools do normal file I/O. At that moment, Bazel gives the action a prepared directory containing the source files, tools, and other inputs the action declared, plus a place to write its outputs.2,4 The first line of defense is that undeclared files are not staged into that working directory, so ordinary cwd-relative access fails immediately. On Linux and macOS, the OS-specific sandboxes then restrict access outside that staged view more aggressively.2,4

That is what makes sandboxing valuable for correctness. An undeclared header, helper script, code generator, or runtime file stops being an invisible local convenience and becomes an immediate build failure. Bazel would rather surface that mismatch now than let the action succeed once, cache the result, and quietly reuse a build whose real causes were never modeled.2,3,5

Not all sandbox strategies are equally strict

The common idea across Bazel's local sandbox strategies is the same prepared per-action directory, but the enforcement strength differs. Using the sandboxed strategy lets Bazel pick the strongest implementation it has on the current platform, whereas local (also called standalone) skips sandboxing and runs directly in the normal execroot.2,4

processwrapper-sandbox is the portable baseline. It builds a sandbox directory as a symlink forest, runs the command there, moves declared outputs back, and then removes the sandbox.2,4,6

linux-sandbox adds Linux namespaces on top of that setup. Outside the sandbox directory, the host filesystem becomes read-only, stray daemons can be cleaned up reliably, and network access can optionally be blocked.2 darwin-sandbox uses Apple's sandbox-exec to provide a similar host-side restriction on macOS.2,4

This extra setup is not free. Sandbox setup and teardown have a cost, and the overhead becomes especially visible on macOS or on actions with very large input trees.2,6 The full mechanics and performance trade-offs belong in 5.8 Sandboxing. Level 2 only needs the main consequence: stricter isolation usually catches more real problems, but it can cost more local wall time.

Why sandbox failures are useful

A sandbox failure usually means Bazel just exposed a real modeling bug: a missing data file, a tool found through $PATH, an absolute-path dependency on the host machine, or a local JDK/tool leaking into the build.3 Those are exactly the bugs that tend to pass on one laptop, fail on CI, or break the moment you try remote execution.2,3

The smallest action-level version is a genrule that reads a source-tree path without declaring the file in srcs:

Won't build
genrule(
    name = "bad_relative_path",
    outs = ["bad_relative_path.txt"],
    cmd = "cat config/input.txt > $@",
)
Use --sandbox_debug to see verbose messages from the sandbox and retain the sandbox build root for debugging
cat: config/input.txt: No such file or directory
Target //config:bad_relative_path failed to build
ERROR: Build did NOT complete successfully

These selected verbatim lines were captured from Bazel 9.0.0 by running bazel build --spawn_strategy=sandboxed --action_env=SANDBOX_REPRO=sandboxed //config:bad_relative_path in the linked snippet. The fixed action-environment value keeps this failing action key separate from the successful local comparison, because strategy choice itself is not part of the action's cache identity. Analysis succeeded, then the Genrule action failed because its sandbox did not contain the undeclared file. Declaring input.txt in srcs and addressing it through $(location :input.txt) makes Bazel stage the input.

Reproduce this sandbox error

That is why sandboxing is best treated as a local rehearsal for remote execution. If the build works only with local but not with sandboxing, the usual lesson is not "the sandbox is too strict." The usual lesson is that the build description is still incomplete.2,3

For an upstream escalation route, Bazel's focused sandboxing_test.sh keeps regressions for missing inputs, writable paths, sandbox-debug output, and strategy-specific behavior in one place.7 Start with the public sandbox contract and the small reproduction above. Use that suite to distinguish a known Bazel boundary from a modeling error before entering the private sandbox implementation.

For the smallest useful rerun and the role of --verbose_failures, --sandbox_debug, and --subcommands, continue to 2.3.4 First-Response Debugging Flags.2

Sandboxing is necessary, not sufficient

Sandboxing checks input visibility, but it does not make an action deterministic by itself. A tool can still embed timestamps, rely on randomness, or otherwise produce different outputs from the same declared inputs. That limit is why 2.3.3 Non-Determinism Sources comes immediately after this article.5

It is also only one member of Bazel's larger execution-strategy vocabulary. How Bazel chooses between local, sandboxed, worker, and remote execution belongs to 2.5.1 Strategies Abstraction.4

key takeaway

Use sandboxing as a simple question: would this action still work if Bazel gave it only the world it declared? If the answer is yes, cache hits and remote execution become much more trustworthy. If the answer is no, the build is still depending on luck from the host machine.1,2,3

Check your understanding · 3 questions

1.Match each local sandbox strategy to its key characteristic:

Drag each answer onto the matching prompt, or click an answer and then click a prompt

Answers
local (standalone)
processwrapper-sandbox
linux-sandbox

2.A sandboxed action fails with a missing helper file, but the same action succeeds when run with --spawn_strategy=local. What is the most likely lesson?

Select one answer

3.True or false: how the local sandbox strategies actually work.

Choose True or False for each sentence

linux-sandbox makes the host filesystem read-only outside the prepared per-action sandbox directory and can optionally block network access.
The local (also called standalone) strategy still prepares a per-action symlink forest. It just skips OS-level namespace isolation.
Sandbox setup and teardown is essentially free — its overhead is rarely visible in total wall time, even on macOS or with very large input trees.
0 of 3 answered

Footnotes

  1. Hermeticity — sandboxing as the execution-time companion to the declared-inputs model 1 2

  2. Sandboxing — per-action directory setup, strategy types, downsides, and debugging flags 1 2 3 4 5 6 7 8 9 10 11 12 13

  3. Troubleshooting Bazel Remote Execution with Docker Sandbox — missing declared inputs, PATH leaks, local tool leaks, and sandboxing as remote-execution rehearsal 1 2 3 4 5 6

  4. What are Bazel's strategies?sandboxed vs local and the sandbox lifecycle 1 2 3 4 5 6

  5. Artifact-Based Build Systems — isolation as part of correct incremental builds and the broader limit that hermetic builds still need deterministic actions 1 2

  6. Whatever happened to sandboxfs? — symlink forests, execroot preparation, and why sandbox performance is especially noticeable on macOS 1 2

  7. Bazel repository mapsrc/test/shell/integration/sandboxing_test.sh is the focused executable specification for sandbox setup and failure boundaries.