local C launcher · pinned artifacts · shell out of the trust boundary

Agent Bondage.

Coding agents are useful, but you cannot let them run loose with live keys, weak dependency provenance, and broad ambient environment access. Exact paths. Exact hashes. Optional envchain-xtra. Optional nono. Your shell gets names. Bondage gets trust.

bondage v0.2.7 static fallback
envchain-xtra v1.3.1 static fallback

On macOS, if you use Claude Code with the normal Claude.ai/OAuth login, do not remove Keychain access from the default Claude profile unless you are deliberately moving to an API-key-only or experimental path.

What Bondage is

Bondage is a small local C launcher for coding agents such as Codex, Claude Code, OpenCode, and Pi. It exists because coding agents should not run loose with live keys, mutable dependency trees, and broad ambient environment access. Instead of trusting a shell alias or PATH lookup, Bondage verifies exact absolute paths and exact hashes, optionally releases secrets through envchain-xtra, optionally applies nono sandbox profiles, and then execs the exact target. The design leans on operating-system primitives where they are strongest: Keychain for secret storage and code-signing identity where available for approval and drift signals. Bondage narrows the launch boundary. It does not make an unaudited dependency tree trustworthy.

In practice, that means the shell keeps short names and convenience, while Bondage owns the trust decision about what exact thing is about to receive secrets and run.

Why this exists

Agent launch stacks drift. Shell wrappers grow policy logic. Global npm installs mutate under your feet. PATH resolution quietly becomes part of the trust boundary whether you intended it or not.

  • you do not want agents running loose with live API keys and other secrets
  • you cannot assume the dependency tree behind a familiar command is clean or stable
  • you do not want broad ambient environment access by default
  • you want to lean on OS-level primitives like code-signing identity and Keychain instead of rebuilding them in shell glue

Bondage is the correction: move the security decisions into one small local launcher, pin the actual artifacts, and demote the shell back to naming sugar instead of policy.

Install

Keep the package simple. Install the launcher from Homebrew. Bring your own nono, optional envchain-xtra, and your own pinned tool tree.

Launcher

brew tap nvk/tap
brew install nvk/tap/agent-bondage

The formula name is agent-bondage. The installed command is bondage.

Companion secret layer

brew tap nvk/tap
brew install nvk/tap/envchain-xtra

envchain-xtra is optional per profile. Use it only when a profile actually needs secret release.

1. Pin exact tool artifacts

Versioned directories. Absolute paths. No PATH-sensitive trust decisions.

2. Write local policy

~/.config/bondage/bondage.conf defines profiles, hashes, optional envchain, optional nono, optional Touch ID.

3. Keep wrappers thin

codex() { bondage exec codex ~/.config/bondage/bondage.conf -- "$@"; }

5-minute quickstart

This is the shortest clean path: install the launcher, point it at one real tool, inspect the launch chain, then add a thin wrapper.

Do this first

  1. Install bondage and optionally envchain-xtra.
  2. Create ~/.config/bondage/bondage.conf from the sample config.
  3. Pin the exact target path and fill in real hashes with bondage hash-file.
  4. Run bondage verify, then bondage chain, then bondage exec.
  5. Only then add a shell wrapper that does nothing except call bondage exec.

Minimal smoke test

brew tap nvk/tap
brew install nvk/tap/agent-bondage

mkdir -p ~/.config/bondage
cp /path/to/bondage.conf.example ~/.config/bondage/bondage.conf

bondage hash-file /absolute/path/to/codex
bondage verify codex ~/.config/bondage/bondage.conf
bondage chain codex ~/.config/bondage/bondage.conf -- --help
bondage exec codex ~/.config/bondage/bondage.conf -- --help

codex() { bondage exec codex ~/.config/bondage/bondage.conf -- "$@"; }

The stack

01

shell name

Convenience only. Names, tab colors, tiny prompt shaping. Not policy.

02

bondage

Verifies exact paths and exact hashes, expands the chosen profile, prints a compact launch summary, builds the final chain, and execs.

03

envchain-xtra

Optional secret-release layer. Namespace-scoped. Only used for profiles that truly need secrets.

04

nono

Optional sandbox layer. The profile that Bondage selects still decides what the process can read, write, and reach.

What it actually does

bondage verify codex ~/.config/bondage/bondage.conf
bondage chain codex ~/.config/bondage/bondage.conf -- --help
bondage exec codex ~/.config/bondage/bondage.conf -- --help

bondage hash-file /absolute/path/to/tool
bondage hash-tree /absolute/path/to/package-root

Common objections

Why not keep it in shell?

Because aliases and shell functions are fine for convenience, terrible for a narrow auditable trust boundary.

  • shell state is ambient and messy
  • PATH lookup leaks into the launch decision
  • functions quietly accumulate policy logic
  • reviewing shell glue is a bad substitute for a small launcher

Why not trust npm globals?

Because launch-time controls are only half the story. Mutable global installs are a poor foundation for anything you plan to bless with secrets.

  • the shim name is not the real trust target
  • the interpreter matters
  • the whole package tree matters
  • if you pinned a poisoned tree, Bondage will faithfully run the poisoned tree

Security assumptions

Trust anchors

  • the operating system remains the strongest available layer for secret storage and signing identity
  • Bondage should trust exact artifacts, not a shell name or mutable PATH result
  • secret release should be explicit and namespace-scoped when envchain-xtra is used
  • nono remains the enforcement layer for sandbox policy when enabled

Declared assumptions

  • the host is not already compromised before launch
  • the pinned artifact set was blessed intentionally, not by accident
  • machine-local policy files are reviewed like code
  • wrappers stay thin and do not quietly grow security logic back into shell glue
  • agent-visible hook output is treated as prompt surface, not harmless logging

What this defends against

  • PATH drift and surprise binary replacement
  • shell wrappers quietly becoming the policy engine
  • mutable launch chains for JS-based agents
  • accidental secret release to the wrong target
  • profile confusion between sandboxed and rawdog paths

What this does not solve

  • blessing a bad npm tree in the first place
  • host compromise before launch
  • a malicious tool you explicitly pinned and approved
  • supply-chain review for dependencies you never audited
  • human judgment failures about what should get secrets

npm reality

Bondage is a launch-time control, not a procurement miracle. If you install compromised garbage and then pin it, Bondage will faithfully protect the compromised garbage.

Preferred JS shape

Immutable versioned bundles. Pinned interpreter. Pinned entrypoint. Whole-tree hash. No trust in mutable global installs.

Hook rule

Fix sandbox denials in managed profiles and launcher config. If a hook reports a denial, keep it factual and short. Do not inject repair workflows that tell the agent to branch the conversation, wait, stop, or create new profile policy.

Live from GitHub

Static site, live repo state. If GitHub is unavailable, the page falls back to the baked-in values.

bondage repo open →

v0.2.7

tag fallback

Small local C launcher. Exact paths. Exact hashes. Optional envchain-xtra. Optional nono.

envchain-xtra repo open →

v1.3.1

tag fallback

Companion secret-release layer. Modernized macOS backend, public tap install, and compatibility shell guards if you still need them.

Upgrade checks

Treat package-manager upgrades as launcher-policy changes. The current public baseline is agent-bondage 0.2.7, envchain-xtra 1.3.1, nono 0.61.1, and pinned nono packs for codex 0.0.12, claude 0.0.16, and opencode 0.0.5.

Post-upgrade loop

export BONDAGE_CONF="${BONDAGE_CONF:-$HOME/.config/bondage/bondage.conf}"
export NONO_PROFILE="${NONO_PROFILE:-codex}"

brew upgrade nono
brew cleanup nono

bondage --config "$BONDAGE_CONF" repin-globals
bondage --config "$BONDAGE_CONF" doctor

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" >/dev/null

bondage --config "$BONDAGE_CONF" verify codex
bondage --config "$BONDAGE_CONF" chain codex -- --help

Non-doxing sandbox checks

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. Update nono before pulling newer packs; old nono builds may reject the newer profile schema.

FAQ

Why C?

Small trusted computing base. Explicit file-descriptor discipline. No new crate ecosystem to escape one package ecosystem.

Why not just patch envchain forever?

Because the main problem is launch policy and artifact verification, not just secret storage. envchain-xtra stays focused on secret release.

Why not keep it all in shell?

Because aliases and shell functions are fine for convenience, terrible for a narrow auditable trust boundary.

What is rawdog?

An explicit no-nono escape hatch. It should be obvious, ugly, and deliberate.