Brig · by osnavi
Version: v0.2.0
Documentation menu

Quickstart

Install the binary, wire it into your agent, start in ride-along, then turn the dial up.

# 1 — install (verified binary, no Rust) curl -fsSL https://get.osnavi.com/brig/install.sh | sh # 2 — wire it into your agent + write a starter .brig.json brig init claude-code # 3 — run your agent as usual; Brig logs every tool call

brig init writes the hook config for your harness and a starter .brig.json in your repo. Out of the box Brig runs in ride-along — every call allowed and recorded, nothing blocked — so you can watch before you enforce. When you're ready, change one line to "mode": "guardrails" and it starts denying the clearly dangerous; "containment" is deny-by-default.

Full install options →  ·  See it work →  ·  Configure it →

Concepts

The handful of ideas the rest of the docs build on.

Modes — the friction dial

Ride-along allows every call and records it (zero friction). Guardrails allows by default but blocks the clearly dangerous — a write escaping the workspace, egress to a non-allow-listed host, an unmistakably destructive shell command. Containment is deny-by-default: only what your capabilities grant gets through. Every mode records every decision.

Capabilities — what a tool may do

Every tool call requires a capability, and nothing is granted unless you grant it (deny-by-default). The vocabulary is small and fixed:

CapabilityGates the toolAllows
fs.readread_fileRead files inside the workspace
fs.writewrite_fileWrite or modify files inside the workspace
sandbox.runrun_codeRun code in the memory-safe sandbox
net.outboundhttp_fetchReach allow-listed hosts over HTTPS
proc.execrun_commandRun a shell command — always human-approved

Profiles — least-privilege presets

A profile is a named bundle of capabilities; pick the smallest that fits the job. The default is the most conservative, reviewer.

ProfileGrantsFor
reviewer (default)fs.read · sandbox.runRead-only review + sandboxed compute
builder+ fs.writeEditing code in the repo
ops+ net.outbound · proc.execNetwork + shell (egress allow-listed, shell human-approved)

The gates

On top of capabilities, each allowed call still passes the relevant gates: workspace containment (paths can't escape the workspace root, symlink-safe), the egress allow-list (HTTPS to named hosts only), human-in-the-loop on the shell, and budgets and rate limits to cap a runaway agent. You set each in Configuration.

Audit

Every decision and outcome is written to a tamper-evident, hash-chained log. Key it to an operator secret (BRIG_AUDIT_KEY) and a file-write attacker can't silently re-forge it; brig verify recomputes the chain and fails on any edit.

Honest scope: the per-call brig hook is stateless. Cross-call exfiltration tracking — following data across calls — runs in the optional brig hookd daemon (see Security depth).

Configuration

Two places to configure Brig: a .brig.json file in your repo (checked in, shared with your team) and environment variables (for secrets and per-machine settings). Most knobs are available in either.

The .brig.json file

Drop it at the root of your repo — brig init writes a starter. Every field is optional; capabilities overrides profile when both are set.

// .brig.json — checked into your repo { "mode": "guardrails", // ride-along · guardrails · containment "profile": "builder", // reviewer · builder · ops "capabilities": ["fs.read", "fs.write", "sandbox.run"], "egress": ["api.github.com"], // hosts http_fetch may reach "extends": ["./base.brig.json"] // optional: inherit a base file }
Tighten-only — this is the safe part. A repo's .brig.json can only remove power relative to the operator's ceiling, never add it: mode is clamped to the stricter of the two, and capabilities and egress are intersected with what's already allowed. So even though the agent can read and write that file, it can't grant itself more — a repo can lock itself down further, not open the gate.

Environment variables

Use these for secrets (the audit key) and per-machine settings — the common ones here, the full list in the Reference:

VariableSetsExample / default
BRIG_WORKSPACEWorkspace root for file toolscurrent dir
BRIG_PROFILECapability profilereviewer (default)
BRIG_EGRESS_ALLOWAllowed hosts for http_fetchapi.github.com
BRIG_HITLShell approvaltty = prompt you
BRIG_PREAPPROVEDShell commands allowed without a promptnpm test
BRIG_AUDIT_KEYKeyed, unforgeable audit chainhex secret ≥ 16 bytes

Recipes

Common cases, as the config you'd actually write.

Read-only code review

The agent may read and reason — never write, reach the network, or run a shell.

// .brig.json { "mode": "containment", "profile": "reviewer" }

Builder allowed to reach one API

Read/write the repo and run code, reach exactly one host — and nothing else (no shell).

// .brig.json — containment makes the capability list the whole allow-list { "mode": "containment", "capabilities": ["fs.read", "fs.write", "sandbox.run", "net.outbound"], "egress": ["api.github.com"] }

Pre-approve your build commands

A couple of exact shell commands run without a prompt; everything else still asks you.

export BRIG_HITL=tty export BRIG_PREAPPROVED="npm test,npm run build"

Locked-down CI with a provable log

Deny-by-default, key the audit chain, and fail the job if the log was touched.

# .brig.json: { "mode": "containment", "profile": "builder" } export BRIG_AUDIT_KEY="$(openssl rand -hex 32)" # … run your agent … brig verify # non-zero exit if the log was altered brig report brig-audit.html # shareable, verifiable report

Harness setup

One command does it — brig init <harness> writes the config for you, no JSON to hand-edit. Add --dry-run to print exactly what it would write (changing nothing), or --global for a user-level install. You can wire it by hand too; here's what each harness needs. The decision contract is identical everywhere — allow (exit 0) or block (exit 2), fail-closed.

Claude Code

brig init claude-code merges a PreToolUse hook into .claude/settings.json (without touching your other settings):

// .claude/settings.json { "hooks": { "PreToolUse": [{ "matcher": "Bash|Read|Edit|Write|MultiEdit|WebFetch", "hooks": [{ "type": "command", "command": "/path/to/brig hook", "timeout": 10 }] }] } }

OpenCode

A TypeScript plugin that routes tool.execute.before through brig hook --harness opencode (and throws when Brig blocks), plus a tool.execute.after report for cross-call tracking.

brig init opencode # writes .opencode/plugins/brig.ts (or ~/.config/opencode/plugins/ with --global)

Pi

A TypeScript extension that gates the tool_call event through brig hook --harness pi (returning a block decision when Brig denies), plus a tool_result report.

brig init pi # writes .pi/extensions/brig.ts (or ~/.pi/agent/extensions/ with --global)

Hermes

A Python plugin that gates the pre_tool_call hook through brig hook --harness hermes (returning a block decision when Brig denies). It's opt-in — after install, enable it in ~/.hermes/config.yaml. Needs a model with ≥64K context (Hermes v0.17+).

brig init hermes # writes ~/.hermes/plugins/brig/ (a Python package), user-level # then enable it (Hermes plugins are opt-in) in ~/.hermes/config.yaml: plugins: enabled: - brig
See exactly what gets written with brig init <harness> --dry-run. Mode and the tighten-only ceiling come from your .brig.json and env — not the hook line. All four are verified end-to-end against the real tool; OpenCode subagent calls can bypass the plugin (a gap we name — see the coverage table).

How it works — the security depth

Brig's strongest claim is that containment holds by construction, at the layer below the prompt, where a model's intent can't reach. The first thing to understand is that there are two data-flow layers, and they protect different things.

Your external agent
Hook + brig hookd taint tracking

Governs the agent you already use — Claude Code, OpenCode, Pi. Provenance is tracked across the harness's tool calls, so data read from your workspace can't leave through a later call. This is the lethal-trifecta defense for your live session.

Tasks Brig runs itself
CaMeL by-construction defense

For tasks run through Brig's own runtime (run-task) — not the external hook path — a dual-LLM split with capability-typed data-flow means a secret can't reach a forbidden sink, even an allow-listed one. Provenance decides, not byte-matching.

So: CaMeL protects tasks Brig runs itself. Your Claude Code session is protected by the hook's taint tracking — a different layer, the same goal.

The four pillars

Injection defense by construction (CaMeL)

A dual-LLM design separates the planner from the data it reads, and types every value with where it came from. A prompt-injected instruction can't grant a capability it doesn't have, and a tainted value can't reach an egress sink — by construction, not by a rule that could be misconfigured.

Cross-call exfiltration tracking

The brig hookd daemon carries taint across a whole session, so a value read early can't be relayed out by a call made much later — including base64 / paraphrase attempts that a single-call literal check would miss.

A small language is a small attack surface

Untrusted code runs in a clean-room, memory-safe interpreter — no unsafe, no JIT, no filesystem, network, or process access, and hard CPU and memory ceilings. It deliberately runs a minimal, audited language — just enough to transform data, and no more. A sandbox's security is what it can't do — so a smaller language means less to attack and less to audit, by design.

Proven, not asserted

A conformance battery proves containment holds identically across models — including ones people distrust and a built-in hostile model that dumps the secret and still can't launder it out. "Verified to contain these models" is the trust signal — the same discipline our engines live by.

Reference

CLI

CommandDoes
brig init <harness>Wire Brig into a harness + write a starter .brig.json. Flags: --mode · --global · --force · --dry-run
brig doctor [harness] [--live]Check the install + each harness's installed version against what Brig verified. --live drives a real tool call to prove the gate still fires on your version
brig hook --mode <m>The pre-tool decision (stdin event → allow exit 0 / block exit 2). Called by the harness, not by hand
brig hookdStateful daemon: cross-call budgets + exfiltration tracking
brig verifyRecompute the audit chain; non-zero exit on tampering
brig audit [--json]Print the audit log (human or JSON Lines)
brig report [path]Write a self-contained, verifiable HTML audit report
brig export [path]Write the log as JSON Lines for a SIEM
brig anchor · anchor-keygenCommit / sign the audit head to an external anchor (truncation-proof)
brig profilesPrint the profile × tool matrix
brig demoThe scripted governance demo (what See it shows)

Profiles × capabilities

Capabilityreviewerbuilderops
fs.read
sandbox.run
fs.write
net.outbound
proc.exec

Environment variables

The configuration knobs (defaults shown). Network, operator auth, and the CaMeL runtime are opt-in build features beyond this single-developer reference.

VariableDefaultSets
BRIG_WORKSPACEcurrent dirWorkspace root (file tools confined here)
BRIG_PROFILEreviewerCapability profile
BRIG_MAX_PROFILECeiling: caps every caller at this profile
BRIG_EGRESS_ALLOWnone (denied)Comma-separated allowed hosts
BRIG_EGRESS_SCHEMEShttpsAllowed URL schemes
BRIG_HITLdenytty to approve shell at the terminal
BRIG_PREAPPROVEDnoneExact shell commands allowed without a prompt
BRIG_MAX_CALLS · _PER_TOOL · BRIG_RATE_PER_MINunlimitedBudgets + 60-second rate limit
BRIG_AUDIT_DBbrig-audit.dbAudit log path
BRIG_AUDIT_KEY · _KEY_FILEHMAC key for the keyed audit chain
BRIG_POLICY_FILECustom roles → capabilities (multi-user / auth)

FAQ — honest answers to the hard questions

The questions a skeptic should ask. If one isn't answered here, write to us.

How is this different from a container or a VM?

A container or VM isolates a whole machine, but leaves the agent's powers intact inside it — shared-kernel CVEs, egress open by default, and a log you can't prove wasn't edited. Brig governs the agent's individual actions — each file, network, and shell call — at the layer below the prompt, deny-by-default, with a tamper-evident record.

They compose: use a VM for blast radius, Brig for what the agent may actually do. Brig also runs without the weight of a VM.

Does my code leave my machine?

Brig is self-hosted — the gateway, your files, and egress all stay on your infrastructure. With a local model, nothing reaches any model vendor at all. With a vendor API, only what you send the model leaves, under your egress controls and full audit. Brig itself phones nothing home — no telemetry.

Which models can I use?

Any — Brig is model-agnostic (local via Ollama and the like, or a vendor API). The gating is model-independent, so it holds identically whichever model drives the agent. The conformance battery proves that across models, including ones people distrust.

What about the subagent coverage gap?

A hook governs the calls the harness routes through it. On some harnesses, a subagent's calls can be opaque to the hook — we name that gap rather than paper over it. The airtight answer (interception below the harness) is a platform direction, not yet scheduled. If you need a hard ceiling today, run containment mode inside a VM.

Is the audit really un-forgeable?

It's tamper-evident. Every record is hash-chained and HMAC-keyed to an operator secret, so a file-write attacker without the key can't silently re-forge it. brig verify recomputes the chain and fails on any edit; brig anchor commits the head to an external sink (Ed25519-signable — an auditor verifies with only the public key), which also catches deletion and truncation.

What's the license?

Open-core. The gateway — gating, audit, egress, and the injection-defense logic — is open to review under agreement by teams deploying it (not published for the crowd, and never a license to resell). Our proprietary execution engines stay private; a neutral third party holds a sealed source copy for continuity if the vendor ever fails. Provided as is — see the Terms.

What's the performance overhead?

The hook is a fast local decision per tool call — no network round-trip, no per-token interception of the model. The sandbox and CaMeL paths only run for tasks Brig executes itself.

Does CaMeL protect my Claude Code session?

Two layers. Your Claude Code session is governed by the hook's cross-call taint tracking — that's what stops exfiltration there. CaMeL's by-construction defense applies to tasks you run through Brig's own runtime, not the external hook path. Same goal, different mechanism per surface — see Security depth.

Changelog

There's no public code repo — this page and the feed are the release channel. No account, no tracker.

v0.2.0 · OS-enforced containment
  • brig run — OS-enforced boundary: the agent's writes are confined to the workspace by the OS (Seatbelt · Landlock · restricted token), proven on macOS, Linux, and Windows.
  • Egress sole-exit (macOS): with an allowlist, an out-of-sandbox proxy becomes the agent's only route to the network.
  • brig run --contained (preview): selects the privilege-separation tier (the agent as a separate, unprivileged user it can't become); fails closed until provisioned — full enforcement is in progress.
  • Hermes cross-call exfil tracking: Hermes's post_tool_call is now wired in — a secret it reads in one step can't be slipped out in a later one. Verified end-to-end.
v0.1.0
  • Drop-in install: brig init for Claude Code, OpenCode, Pi, and Hermes.
  • Three modes: ride-along · guardrails · containment.
  • Tamper-evident audit: keyed hash chain + Ed25519-signable external anchors.
  • Injection defense: the CaMeL by-construction runtime (opt-in).

Each tagged release will list its changes here and link the verified binaries on the install page.