Automating the pain out of clash setup
TL;DR;
Clash now detects your project's toolchain and generates sandboxed policies automatically. clash init in a Rust project knows that cargo needs ~/.cargo and ~/.rustup. You don't have to.
The problem with generic permissions
In my last post about clash, I showed a simple policy that allowed cargo and denied git push. That works, but it's lying to you about how much safety you're actually getting. Allowing cargo without restricting where it can read and write is barely better than allowing everything. The whole point of a sandbox is filesystem containment, and filesystem containment requires knowing what paths a tool actually needs.
Here's the thing: every language ecosystem has its own snowflake layout of toolchain paths, cache directories, and config files. Rust wants ~/.cargo and ~/.rustup. Python wants ~/.local, ~/.cache/pip, ~/.virtualenvs, maybe ~/.pyenv. Node wants node_modules and whatever cursed global prefix npm is configured with this week. Go wants ~/go and ~/.cache/go-build.
Nobody wants to write this by hand. I certainly didn't, and I'm the person building the tool.
Ecosystem sandboxes
So we built ecosystem-specific sandbox presets. Here's what the Rust one looks like:
rust_full = sandbox(
name = "rust_full",
default = ask(),
fs = {
subpath("$PWD", follow_worktrees=True): allow(FULL),
"$HOME": {
glob(".cargo/**"): allow(),
glob(".rustup/**"): allow(),
},
glob("$TMPDIR/**"): allow(),
},
net = allow(),
)
This says: cargo gets full access to your project directory (following git worktrees), read/write to the Rust toolchain directories, and temp. Everything else falls through to ask(), which means your agent has to ask you before touching anything unexpected. The kernel enforces this via Landlock on Linux and Seatbelt on macOS — it's not an honor system.
We have these for rust, go, python, node, ruby, java, docker, swift, dotnet, and make. Each one is a .star file in the clash stdlib that knows where that ecosystem keeps its stuff.
Detection
Writing sandbox presets is the easy part. The annoying part is figuring out which ones you need. We could have just made people add load("@clash//rust.star") to their policy and called it a day, but that defeats the purpose. If you're going to make people hand-configure their safety tools, most of them will just...not.
So clash init now looks at your project:
$ clash init
detecting ecosystems...
found: rust (Cargo.toml)
found: git (.git)
generating policy with: rust, git
The detection is straightforward. Each ecosystem defines markers — Cargo.toml for rust, go.mod for go, package.json for node, etc. We check $PWD for these files and wire up the matching sandboxes. The generated policy loads the right .star files and routes binaries to their ecosystem sandbox:
load("@clash//sandboxes.star", "project")
load("@clash//rust.star", "rust_full")
load("@clash//sandboxes.star", "git_safe", "git_full")
when({"Bash": {
("cargo", "rustc", "rustup"): allow(sandbox = rust_full),
"git": allow(sandbox = git_full),
}})
cargo runs in rust_full. git runs in git_full. If your agent tries to run something that isn't in either list, the default kicks in and asks you. You go from zero to sane security policy in one command, and the policy is readable Starlark you can edit if you don't like our defaults.
--from-trace: let the agent tell you what it needs
Sometimes marker files aren't enough. Maybe you have a polyglot monorepo. Maybe your build system calls tools that aren't obvious from the project structure. For these cases, we added --from-trace:
$ clash init --from-trace latest
This reads the trace log from your last agent session and generates a policy based on what the agent actually did. It categorizes observed tool invocations into fs-read, fs-write, network, and exec groups, then generates sandboxed allows for the safe stuff and ask rules for anything destructive. It's the "run it once unsandboxed, then lock it down" workflow.
You can also point it at a specific trace file if you want to be more deliberate about which session to base your policy on.
The onboarding problem
All of this detection and code generation would be wasted if the onboarding experience sucked. Security tools have a specific failure mode: if setup takes more than about 90 seconds, people skip it. Then they're running with no policy at all, which is worse than a bad policy because at least a bad policy blocks rm -rf /.
The old clash init was a wall of text. It dumped a default policy, told you to edit it, and wished you luck. We rewrote it as an interactive walkthrough:
- Pick your security posture (strict, moderate, permissive)
- Clash detects your ecosystems and shows you what it found
- It generates a policy and shows you a summary
- You're running
If you're in a hurry, clash init --quick skips the walkthrough and picks sane defaults. If you already have a policy and want to re-detect ecosystems, it warns you before overwriting anything.
The important thing is that at the end of init, you have a policy that actually understands your project. Not a generic "allow all bash" policy, not a locked-down policy that breaks your build on the first cargo test because it can't read ~/.rustup. A policy that knows what you're working with.
The uncomfortable truth about security UX
I've been thinking about this a lot: the reason most permission systems end up as "click yes 200 times" or "yolo allow everything" is that they ask questions the user doesn't have good answers to. "Allow bash?" is a terrible question. What bash? Doing what? The user doesn't know what the agent is about to run, and the agent doesn't know what the user considers dangerous.
Ecosystem sandboxes are an attempt to bridge that gap. Instead of asking "allow cargo?", we're saying "cargo can touch your project and the rust toolchain, nothing else." The user can evaluate that in about two seconds because it maps to something they already understand about how their tools work.
It's still not perfect. We're essentially encoding "how does this ecosystem's toolchain behave" into static sandbox definitions, and toolchains are weird and inconsistent and change over time. But it's a hell of a lot better than the two options we had before, which were "trust everything" and "configure everything yourself."
What's next
We've implemented an internal agent-agnostic protocol for handling tool permissions and sandboxing. This abstraction let us take the same tool we've built for claude code, and expand it to other popular tools (gemini cli, codex, amazon q, opencode, copilot). The ecosystem sandbox system is the same across all of them — same .star files, same detection, same kernel enforcement. Your policy works regardless of which agent is running. In terms of maturity...
- Beta: Claude Code
- Alpha: Gemini, Codex, Q, OpenCode, Copilot
We're also working on better violation messages. When a sandbox blocks something, clash now tells you why — which sandbox, what it grants, and what to do about it. Because "permission denied" is about as useful as "an error occurred."
As always, clash is under heavy development. Things break, APIs change, and I'm probably going to rewrite half of this by next month. File bugs, they make us better.