Your build runs other people's code. With your keys.

Any Rust dependency can ship a build.rs: a setup script that runs before anything is compiled, on your laptop and in CI, with your SSH agent, your cloud credentials and your CI secrets. buildclamp runs each one inside limits you wrote down, so a script that reaches for the network or writes outside its own output folder fails the build instead of succeeding quietly. The report names the kernel mechanism that held it.

cargo buildclamp build
$ cargo buildclamp build
   Compiling proc-macro1 v1.0.107
error: failed to run custom build command
       for `proc-macro1 v1.0.107`

  buildclamp: the build script for `proc-macro1@1.0.107`
              asked to reach the network and the policy
              does not grant it.
    it tried to: connect 23.254.165.112:9089
    its envelope: fs-write, unix, exec
                  (from the defaults; no grant matched)
    and 2 further ungranted request(s)
    to allow it, add a grant with a reason:
        [grants."proc-macro1"]
        capabilities = ["net", "unix", "fs-write", "exec"]
        why = "..."
      or run cargo buildclamp explain proc-macro1
      to see the whole envelope.
Linux only One static binary One TOML file Apache-2.0 v0.1.0 · not yet released

What changes, in one look

A build script is a program a stranger wrote that runs on your machine before any of their library code does. Today it gets everything you have. Held, it gets what you granted, and the rest is refused and written down.

Today: plain cargo build

  • Can open a socket to any address on the internet.
  • Can write anywhere you can, including ~/.config/autostart.
  • Sees AWS_SECRET_ACCESS_KEY, GITHUB_TOKEN, every credential in your environment.
  • Can read cargo's own environment through /proc. Yours too.
  • Nobody is told. There is no report.

This is the default. It is not a bug in cargo. It is how build scripts work.

Held: cargo buildclamp build

  • Network: denied, by an empty network namespace (a private network stack the script can't see out of).
  • Writes: only OUT_DIR (the one folder a build script is meant to write into) and a private temp dir. The rest of the disk is read-only.
  • Secrets: credential-shaped names withheld from the environment.
  • Running cc, pkg-config, git: allowed, recorded. A child inherits the same fence.
  • Every request lands in a report with the mechanism that held it.

The ones that behave never notice.

Inside the envelope

In short: this is a live demo of a script trying to misbehave, and what actually happens to it. Each build unit runs in a container buildclamp assembles from your policy. Here is the fixture script from below, running under each of the three commands: what it reaches, where it stops, and which kernel mechanism stopped it.

Build unitevil@0.1.3 [build-script-compile]

cargo buildclamp build

      
Held at the wall Went through, recorded Nothing stopped it The black line is the envelope wall. The square on it names the mechanism holding that capability on this host.

Three commands, in this order

A default-deny tool that breaks your build on the first run gets uninstalled the same afternoon. So the first command refuses nothing. Enforcement is third.

Observe

$ cargo buildclamp observe

Runs your build. Denies nothing. Records everything a build script asked for that the defaults would not have covered.

buildclamp 0.1.0 — observe mode
  build units held           1
  ungranted requests         4

  evil@0.1.3 [build-script-compile]
    x net        connect 127.0.0.1:9
    x fs-write   mkdir …/autostart
      fs-write   openat …/generated.rs
      exec       execve /bin/sh

An x marks a request the defaults did not cover. The unmarked lines are the same script writing OUT_DIR and shelling out, which is allowed.

Init

$ cargo buildclamp init

Writes buildclamp.toml from what it saw. Every grant is marked unreviewed, with the observation and its date in the reason.

[grants."openssl-sys"]
version = ">=0.9, <0.10"
capabilities = ["exec", "fs-write",
                "unix"]
write-zones = ["out-dir", "tmp"]
why = "OBSERVED 2026-09-01: used more
  than the defaults. Replace this with
  the reason, then set reviewed = true."
reviewed = false

Replacing each reason is the work. --strict refuses to pass while any grant is still unread.

Build

$ cargo buildclamp build

Now enforce it. A build script that asks for more than its grant fails the unit, and the error names the syscall, the target and the grant that would allow it.

cargo buildclamp check
    --strict
    --require-enforced

--strict fails on any unread grant. --require-enforced fails when this host could only watch instead of enforce. Use both in CI.

A project with fifty grants is a correctly configured project. Not a compromised one.

Compared with what you have now

In short: this is the fact-check — where buildclamp genuinely beats the alternatives, and where it honestly doesn't. Scanners read a database after the fact. cargo-acl got here first and sandboxes build scripts too. Here is what each one does when a build script actually misbehaves.

Enforces anyway

When the sandbox helper is missing, cargo-acl reports success. buildclamp has no helper to be missing.

When bwrap (cargo-acl's sandboxing helper program) is not on PATH, cargo-acl prints a notice and keeps going unsandboxed — a security control reporting a green build while enforcing nothing, in exactly the CI container where nobody installed it. buildclamp enforces in-process with seccomp, Landlock and namespaces: no helper binary, a stated fallback ladder, and --require-enforced to fail a run that could only be advisory. The row below is one of nine; this is the one that survives every other comparison.

When a build script… Plain cargo buildno tool Scannerscargo-audit · cargo-vet · cargo-deny cargo-acl0.9.0 · bubblewrap sandbox buildclamp0.1.0 · in-process
Opens a socket to an unknown host Runs
Nothing sees it.
Not seen
They inspect a lockfile or an audit record, not a running process.
Denied
Network is unshared, if bwrap is installed.
Denied
Empty network namespace. The report names the address and the tier.
Writes to ~/.config/autostart Succeeds Not seen Denied
tmpfs (a blank, memory-backed filesystem) over $HOME; only OUT_DIR is writable.
Denied
Read-only root. ReadOnlyFilesystem comes back from the kernel.
Reads AWS_SECRET_ACCESS_KEY from the environment Visible Not seen Withheld
--clearenv with an allowlist. Stronger than buildclamp's deny list.
Withheld
Credential-shaped names scrubbed. A user namespace also closes the read of cargo's /proc environment.
Runs on a CI host where the sandbox helper is missing n/a n/a Continues unsandboxed
Prints a notice that bwrap is not installed and keeps going. The build is green.
Enforces anyway
No helper binary. seccomp, namespaces and Landlock in-process, with a stated fallback ladder.
Is asked "how hard was that actually held?" n/a n/a Sandboxed or not
One backend, one answer.
Per capability, per unit
enforced:netns, advisory:seccomp-notify, none. --require-enforced fails an advisory-only run.
Is a proc-macro that misbehaves Runs Not seen One grant for all
Sandboxes rustc, so every proc-macro in the build shares one policy.
Named, with confidence
exact, stack-scan or closure. Attribution is built; holding rustc itself is not yet wired in 0.1.0.
Was published 86 minutes ago, before any advisory exists Runs Runs
No advisory yet, nothing to match.
Held
The boundary does not need to know the name.
Held
Same. A fence works on day zero.
Could open a socket, but that code never runs Unknown cargo-vet: if a human said so Known before it runs
Static reachability analysis of the compiled code. buildclamp does not do this.
Not known
Sees only what a build actually did on this machine, this time.
Spawns cc or pkg-config Runs Not seen No path for it Allowed, recorded
The child inherits the namespaces and the filter.
Leaves a record None Advisory list Permission summary
JSON of what was allowed.
Per-request stream
Syscall, target, capability, tier, exit code. What a CI gate and an audit need.
Held at the wall Went through, recorded Partly Nothing stops it Not this tool's job

cargo-acl got here first. Run both.

cargo-acl (formerly cackle) has intercepted build units since 2023, sandboxes build scripts per crate, generates its own config, and is named in the Rust project's own sandboxing goal. Its static analysis knows a crate could open a socket before it ever runs. buildclamp does not do that.

buildclamp claims two things it does differently: enforcement that cannot silently evaporate when a helper is missing, and attribution that reaches individual proc-macros. Everything else is a wash or a loss.

Why a cooldown is not enough

A registry cooldown, global-min-publish-age, merged into cargo on 2026-08-28 and is due in Rust 1.100. Good. It does not help when an attacker yanks the five previous versions four seconds apart so cargo's own warning steers you into cargo update, which resolves to the trap.

No cooldown, advisory feed or --offline flag helps someone who deliberately updates. A boundary does.

The same hostile script, held and not held

The repository ships a replica of a real malicious build script as a test fixture. It reconstructs a command-and-control address, writes outside OUT_DIR and reads secrets out of its parent's environment. Same script, same host, two runs.

What it tried
Not held
Held
Connect to its C2 address
Reached the syscall
PermissionDenied
Read credentials from its own environment
Both visible
None visible
Read its parent's environment via /proc
7,883 bytes
PermissionDenied
Persist to ~/.config/autostart
Succeeded
ReadOnlyFilesystem
Write its own OUT_DIR
ok
ok
cargo buildclamp observe
$ cargo buildclamp observe
   Compiling evil v0.1.3 (…/tests/fixtures/evil)
buildclamp 0.1.0 — observe mode
  build units held           1
  ungranted requests         4

  evil@0.1.3 [build-script-compile]
      net          none
      unix         none
      fs-write     none
      env-secret   none
      exec         none
    x net        socket AF_INET
    x net        connect 127.0.0.1:9
    x fs-write   mkdir /root/.config/autostart
    x fs-write   openat …/autostart/evil.desktop
      fs-write   openat …/out/generated.rs
      exec       execve /bin/sh
      exec       execve /usr/bin/id

Every tier reads none because observe mode enforces nothing. The point is to learn what your build does on the machine you actually have, not on a sandboxed approximation of it. The one write the script is supposed to make, into OUT_DIR, still works when held.

What it costs

Measured, not estimated. Every number below carries the host it was measured on and the enforcement tier that applied, because a script stopped by a namespace and one stopped by a seccomp answer are being stopped by different machinery at different prices.

Clean build of a serde-plus-derive graph

Unhookedplain cargo build
5.06 s
Hookedcargo buildclamp build
4.86 s

The two are within measurement noise of each other. The hooked run happened to be faster; do not read that as a speed-up. Read it as: you will not notice.

3–4 ms
Fixed cost per held build unit, seccomp setup
2.4 ms
Plus namespaces, per unit
10–40 µs
Per notified syscall, depending on which CPU the supervisor wakes on
0 daemons
Listens on nothing. Opens no port. Keeps no state beyond a report and a policy file.
host: 4-vCPU Linux 6.18 on x86_64 · landlock absent, so the filesystem tier was a mount namespace · user namespaces yes · seccomp notification yes

Which tier actually held it

In short: not every computer can enforce this the same way, so buildclamp checks first and tells you honestly instead of assuming. Some kernels lack Landlock. Some container runtimes block user namespaces. Instead of assuming, buildclamp probes the host and names the mechanism per capability. Flip what this host has and watch the answer change. This is the property the whole design turns on.

What this machine can do
User namespacesRead-only root and a private network become enforceable. Some distributions and container runtimes forbid this.
seccomp notificationRequests can be observed and answered. Without it buildclamp cannot see what a unit asks for.
LandlockThe preferred filesystem rung: the kernel refuses out-of-zone writes per path, with the mount namespace still underneath. Absent on this project's development host, present on its CI runner at ABI v7.
Run in observe modeObserve enforces nothing on purpose, and says so per capability rather than reporting the tier that would have applied.
cargo buildclamp doctor · simulated from the same rules

What it does not do

Printed next to the claims, not in a footnote. A green build that enforced nothing is worse than no tool, so the limits are part of the product.

Linux only

macOS and Windows get nothing in v0. Not "coming soon". Not started.

Build time only

It does not stop a malicious dependency at runtime. It holds build scripts and, once wired, proc-macro expansion.

Reads are not restricted

A build script can read ~/.aws/credentials. It cannot send them anywhere or write them outside OUT_DIR, but it can stage them into OUT_DIR, which your crate includes. A read-deny list is on the roadmap and absent today.

Proc-macro expansion is not yet held

The interception, the policy and the attribution machinery exist. Wiring rustc itself through the sandbox is not done in 0.1.0. Documented as unbuilt rather than half-done.

The advisory tier is defeatable

A decision made from a seccomp notification is racy by construction: the kernel re-reads the argument after the answer. That is why it is called advisory, and why --require-enforced exists.

Destination lists are recorded, never enforced

net-destinations in a grant is written down and reported. Enforcing it needs a proxy inside the namespace, which v0 does not have, so the field says so everywhere it appears.

Circumvention is possible

A proc-macro can detect that it is being held and emit different code. buildclamp narrows what build-time code can reach. It does not make running untrusted code safe.

Not audited. Not released.

Nothing here has had an external audit. There is no signed release artefact yet. Install from source, run doctor, and read the report before you trust a green build.

Install. Run doctor. Then observe.

Four commands. The first two tell you what your host can enforce. The third refuses nothing and shows you what your build does. Nothing is enforced until you have read a policy.

Needs Rust 1.91 or newer and a Linux host. Apache-2.0.

sh
$ cargo install --locked --git https://github.com/lucheeseng827/buildclamp cargo-buildclamp

$ cargo buildclamp doctor      # what this host can actually enforce
$ cargo buildclamp observe     # runs your build, refuses nothing
$ cargo buildclamp init        # writes buildclamp.toml from what it saw
$ cargo buildclamp build       # now enforce it