























Reality check: this stack is still experimental. It is a practical local hardening pattern, not a finished security product. If you want the strongest isolation boundary for an agent, a dedicated VM is still better than a host-local CLI sandbox.
By default, most agent setups inherit too much trust from the surrounding shell session. That means too many secrets, too much filesystem visibility, and too much confidence in whatever dependency tree happens to answer on PATH.
The actual reasons for hardening this are not abstract:
The old pattern for hardening CLI agents was to stuff everything into shell wrappers: secret injection, Touch ID gates, sandbox flags, target resolution, and sometimes even binary verification. That can work, but it makes your shell script the real security boundary.
The cleaner model is to separate the jobs:
shell name → bondage → [envchain-xtra] → [nono] → exact pinned tool convenience launch policy secret release sandbox actual agent
That split matters because each layer addresses a different failure mode: bondage decides what exact artifact chain is allowed to launch, envchain-xtra decides when secrets are released, and nono decides what the launched process can actually touch.
It also deliberately leans on the OS where the OS is stronger than userland glue: macOS code-signing identity can be used as part of artifact approval, and Keychain remains the underlying secret store rather than re-implementing secret storage in the launcher.
If you want the implementation detail rather than the overview, start with agentbondage.org, then read the Getting Started and Trust Model docs.
This stack is opinionated. The philosophy is not “wrap everything in enough cleverness and call it secure.” It is to reduce ambient trust and make the risky decisions explicit.
In short: less magic, less ambient authority, more deliberate boundaries.
| Layer | What it should do | What it should not do |
|---|---|---|
| Shell wrapper | Short names, prompt shaping, convenience aliases | Decide which binary is trusted or which sandbox policy applies |
bondage |
Verify exact targets, interpreters, package trees, and choose the launch policy | Store secrets or enforce filesystem policy by itself |
envchain-xtra |
Release secrets from Keychain-backed namespaces to one process tree | Own the whole launch-policy story |
nono |
Enforce filesystem, process, and network restrictions | Resolve which launch artifact chain is trusted |
In practical terms: your secrets are already being shipped to frontier labs as tokens they can decode. Don't panic — you can hand this article to your coding agent and ask it to set the stack up for you.
The stack guide should explain the worldview, not carry every operational nuance inline. The sharp edges are better captured as repeatable patterns.
These are the decisions that matter more than the exact command syntax: where the trust boundary lives, which capability gets isolated, and what counts as a real escape hatch.
If you want a working starter shape instead of hand-copying every profile, use agent-stack-bootstrap. It installs public templates and shell aliases, while keeping real local paths, fingerprints, hostnames, and tokens in private generated or ignored files.
git clone https://github.com/nvk/agent-stack-bootstrap.git
cd agent-stack-bootstrap
./install.sh
echo 'source "$HOME/.config/agent-stack/shell.zsh"' >> ~/.zshrc
The default install includes every optional public profile group. Pick narrower groups when you only need one path:
./install.sh --profiles spark
./install.sh --profiles ds4,pi-ds4
./install.sh --profiles none
The bootstrap stages bondage.conf.template; it does not install a live pinned ~/.config/bondage/bondage.conf. Render and pin that file locally after the target tools are installed.
The bootstrap also installs requirements.env and a version check helper. As of this guide update, the tested public ranges are nono >=0.61.0 <0.62.0, agent-bondage >=0.2.7 <0.3.0, envchain-xtra >=1.3.1 <2.0.0, node >=26.0.0 <27.0.0, codex >=0.136.0 <0.137.0, claude-code >=2.1.112 <2.2.0, and opencode >=1.14.40 <1.16.0.
Start with the launcher and the sandbox:
brew tap nvk/tap
brew install nvk/tap/agent-bondage
brew install nono
The current public baseline is agent-bondage 0.2.7, envchain-xtra 1.3.1 when you use Keychain secret release, and nono 0.61.1. If you use registry-managed nono packs for agent profiles, keep the packs pinned too: always-further/codex@0.0.12, always-further/claude@0.0.16, and always-further/opencode@0.0.5.
If a profile needs Keychain-backed secret injection, add:
brew install nvk/tap/envchain-xtra
Important: `agent-bondage` is the formula name. It installs the bondage executable.
Useful references:
Keep the pieces separate:
~/.config/bondage/bondage.conf
~/.bondage/tools/
~/.config/nono/profiles/
~/bin/
Good setups pin exact absolute artifacts, not PATH lookups or mutable global installs. For script-backed tools, that means pinning the entrypoint, the interpreter, and the package tree.
A non-doxing public starting point for the shared launcher config looks like this. Replace every absolute path and hash with values from your own machine.
# ~/.config/bondage/bondage.conf
[global]
envchain = /opt/homebrew/bin/envchain
envchain_fp = sha256:replace-me
nono = /opt/homebrew/bin/nono
nono_fp = sha256:replace-me
nono_profile_root = /Users/you/.config/nono/profiles
touchid = /opt/homebrew/bin/touchid-check
touchid_fp = sha256:replace-me
tool_root = /Users/you/.bondage/tools
[defaults "agent-nono"]
nono_allow_cwd = true
nono_allow_file = /dev/tty
nono_allow_file = /dev/null
nono_read_file = /dev/urandom
[defaults "github-env"]
env_command = GH_TOKEN=/opt/homebrew/bin/gh auth token
env_command = GITHUB_TOKEN=/opt/homebrew/bin/gh auth token
[defaults "codex-target"]
target_kind = native
target = /Users/you/.bondage/tools/codex/0.128.0/codex-aarch64-apple-darwin
target_fp = sha256:replace-me
[profile "codex"]
inherits = agent-nono,github-env,codex-target
use_envchain = false
use_nono = true
nono_profile = codex
touch_policy = none
Use [defaults "…"] blocks for repeated launch policy: common nono device grants, GitHub token injection, target paths, interpreter pins, and package-tree hashes. Each profile must opt in explicitly with inherits = …. Avoid one giant catch-all defaults block; keep defaults narrow enough that repin output tells you exactly what changed.
Then add one [profile "…"] block per client in bondage.conf and one matching JSON profile in ~/.config/nono/profiles/. The old fully-expanded profile format still works, but defaults make upgrades less brittle because a shared target pin can be repinned once instead of repeated across every tier.
A thin shell wrapper should look like this:
codex() {
bondage exec codex ~/.config/bondage/bondage.conf -- "$@"
}
That wrapper is only naming sugar. The trust boundary is below it.
The login shell itself should not depend directly on a fragile repo symlink. Keep tiny stable bootstrap files in $HOME and let them source the real repo-backed config when it is readable.
# ~/.zshrc
if [ -r "$HOME/src-repo/.dotfiles/zsh/.zshrc" ]; then
. "$HOME/src-repo/.dotfiles/zsh/.zshrc"
elif [ -r "$HOME/src-repo/.dotfiles/ai/shell.zsh" ]; then
. "$HOME/src-repo/.dotfiles/ai/shell.zsh"
fi
That looks less clever than a direct symlink, but it fails much better. If the repo path is temporarily unreadable, you still have a stable entrypoint instead of silently falling back to a raw binary on PATH.
If you want concrete examples instead of the generic stack, jump to the tool-specific guides:
The architecture is only half the story. Local launcher stacks get brittle when upgrades, wrappers, and helper scripts are treated as harmless convenience instead of real operational policy.
*-fix profile is different from *-unsafe and different again from *-rawdog. Repair access should be explicit and sandboxed where possible.A practical post-upgrade loop is:
export BONDAGE_CONF="${BONDAGE_CONF:-$HOME/.config/bondage/bondage.conf}"
brew upgrade nono
brew cleanup nono
bondage --config "$BONDAGE_CONF" repin-globals
bondage --config "$BONDAGE_CONF" doctor
bondage --config "$BONDAGE_CONF" repin codex # only if doctor suggests it
nono pull always-further/codex@0.0.12 --force
nono pull always-further/claude@0.0.16 --force
nono pull always-further/opencode@0.0.5 --force
nono pin always-further/codex
nono pin always-further/claude
nono pin always-further/opencode
nono list --installed
nono profile show "${NONO_PROFILE:-codex}" >/dev/null
bondage --config "$BONDAGE_CONF" verify codex
bondage --config "$BONDAGE_CONF" chain codex -- --help
If repin updates a defaults block, every profile that inherits that block gets the new pin. That is the point; the command output should make the shared update scope visible. Run brew cleanup nono before repinning globals if your config pins a versioned Homebrew Cellar path; otherwise the old keg can remain valid and keep the launcher pinned to the previous nono. Then open a fresh login shell and confirm your wrapper names still resolve to the expected shell functions.
Use non-doxing checks when you want to paste results into an issue, PR, or agent transcript:
export NONO_PROFILE="${NONO_PROFILE:-codex}"
for path in \
"$HOME/.ssh" \
"$HOME/.npmrc" \
"$HOME/.aws" \
"$HOME/Library/Keychains"
do
nono why --profile "$NONO_PROFILE" --path "$path" --op read
done
nono why --profile "$NONO_PROFILE" --path "$HOME/.config/nono/profiles" --op write
nono why --profile "$NONO_PROFILE" --path "$HOME/.config/nono/profile-drafts" --op write
Expected shape: credential paths are denied, active profile writes are denied, and profile-drafts is writable only for profiles that intentionally support draft-and-promote profile edits.
This stack only makes sense if you are explicit about what is trusted, what is not, and what you are responsible for maintaining over time.
nono and the underlying kernel sandbox are more trustworthy than an agent's own self-reported sandbox modebondage.conf is a policy file you control and review deliberatelyFor JS-heavy agents, the real trust object is not the shell command name. It is the launch artifact set:
This is not a complete supply-chain solution, and it does not magically make agent dependencies trustworthy.
If you bless a compromised artifact tree, you only get a perfectly preserved compromise.
The stack is good at launch-time integrity and runtime containment. It is not magic at acquisition time. That means:
Before wiring a tool into your shell, verify the profile explicitly:
bondage verify codex ~/.config/bondage/bondage.conf
bondage chain codex ~/.config/bondage/bondage.conf -- --help
bondage exec codex ~/.config/bondage/bondage.conf -- --help
The sequence matters:
verify confirms the pinned artifacts match what you intendedchain lets you inspect the exact launch chain without executing itexec proves the real path worksIf you need one sentence for the model: keep shell convenience thin, put launch policy in bondage, let envchain-xtra release secrets, and let nono sandbox the result.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。