docs · v0.1.0

Documentation.

Everything you need to install troopr, sandbox an AI coding agent, and write a troopr.yaml that fits your project.

Quick start

Three commands to go from clone to first protected agent run.

# 1. clone & build (private alpha - see Installation for the GOPRIVATE setup)
$ git clone git@github.com:paanSinghCoder/troopr.git
$ cd troopr && go build -o /usr/local/bin/troopr ./cmd/troopr

# 2. sanity check
$ troopr version
0.1.0

# 3. run your agent inside the sandbox
$ cd ~/code/my-project
$ troopr run --profile standard -- claude

That's it. Your agent runs exactly as it always has - same stdio, same environment, same arguments. The only difference: reads of .env, ~/.ssh, ~/.aws/credentials and the rest of the universal denylist now return Operation not permitted.


Installation

Requirements

  • macOS 13 or newer (Apple Silicon or Intel)
  • Go 1.22+ (only required to build from source)
  • A working sandbox-exec at /usr/bin/sandbox-exec (ships with macOS)

One-time setup for the private module

The GitHub repo is currently private. Until it's public, both git clone and go install need authenticated access to github.com/paanSinghCoder/troopr.

# tell Go not to route this module through the public proxy / sumdb
$ go env -w GOPRIVATE=github.com/paanSinghCoder/*
$ go env -w GONOSUMCHECK=github.com/paanSinghCoder/*

# make Go use SSH for github.com clones
$ git config --global url."git@github.com:".insteadOf "https://github.com/"

You'll also need an SSH key loaded and attached to a GitHub account with read access to the repo. (Or a personal access token with repo scope in ~/.netrc, if you prefer HTTPS.)

Build from source

$ git clone git@github.com:paanSinghCoder/troopr.git
$ cd troopr
$ go build -o /usr/local/bin/troopr ./cmd/troopr

The output is a single static binary of about 5MB. No runtime dependencies - it's pure Go standard library plus cobra (CLI parser) and yaml.v3 (config loader).

Verify

$ troopr version
0.1.0
$ troopr check --profile standard | head -3
# Resolved config
profile:        standard
cwd:            ...

Your first sandbox

Before pointing troopr at a real project, run a 30-second smoke test to confirm the deny is actually working on your machine.

$ mkdir /tmp/troopr-demo && cd /tmp/troopr-demo
$ echo "SECRET=hunter2" > .env
$ echo "ok" > public.txt

# reading a normal file works
$ troopr run --profile standard -- /bin/cat public.txt
ok

# reading .env is blocked
$ troopr run --profile standard -- /bin/cat .env
cat: .env: Operation not permitted

If the second command prints hunter2 instead of the permission error, see Troubleshooting.


Commands

Five subcommands. Run troopr --help for the in-CLI version.

troopr run

troopr run [--profile <name>] [--config <path>] -- <agent-cmd> [args...]

Launch a command inside a sandbox. Everything after -- is passed through to the wrapped binary verbatim.

troopr init

troopr init

Write a sample troopr.yaml to the current directory. Will not overwrite an existing file.

troopr check

troopr check [--profile <name>] [--config <path>]

Dry run. Prints the resolved configuration and the sandbox profile (.sb) that would be generated, without executing anything.

troopr log

troopr log [-f|--follow]

Tail ~/.troopr/log.jsonl. Each line is a JSON record of a denied filesystem read or write.

troopr version

troopr version

Print the installed troopr version.

Flags accepted by run and check

FlagTypeDefaultEffect
--profile string standard One of strict, standard, permissive. Overrides the profile named in troopr.yaml, if any.
--config path ./troopr.yaml Explicit path to the config file. If omitted, troopr looks for troopr.yaml or troopr.yml in the current directory.

Configuration

troopr.yaml lives at the root of your project. Generate a starter file with troopr init.

Schema

# troopr.yaml
version: 1                  # required, must be 1
profile: standard           # strict | standard | permissive
deny:                       # patterns added on top of the profile
  - "**/.env*"
  - "**/*.pem"
  - "~/.ssh/**"
  - "src/internal/secrets/**"
allow:                      # carve-outs from deny
  - "**/.env.example"

Fields

FieldRequiredNotes
version yes Must be 1. Anything else fails with exit code 1.
profile no Default standard. A --profile flag overrides this.
deny no List of path globs to add to the profile's baseline denylist. Order doesn't matter.
allow no List of path globs that are explicitly permitted even if matched by a deny rule.

Glob syntax

TokenMatchesExample
**Any sequence of characters, including /.**/.env/foo/bar/.env
*Any sequence of characters except /.*.pemkey.pem
?A single non-/ character.file?.txtfile1.txt
~ / $HOMEExpanded to your home directory before matching.~/.aws/**/Users/you/.aws/foo

Merge precedence

When troopr starts, it merges configuration in this order - later sources override earlier ones:

  1. The profile baseline (universal denylist + profile-specific allows)
  2. Anything in ./troopr.yaml or the file passed to --config
  3. The --profile flag, if present, overrides the profile named in the config file

The deny and allow lists from your config are added to the profile baseline; they don't replace it. So permissive with a config file is still permissive + your extras, not just your extras.


Profiles

Three baselines that determine what the agent can read and whether it can reach the network. Every profile applies the universal denylist.

ProfileReads outside CWDWritesNetworkUniversal denylist
strict system libraries only CWD + macOS temp dirs blocked enforced
standard $HOME + system $HOME + CWD + temp allowed enforced
permissive everywhere everywhere allowed enforced

strict

The most restrictive profile. The agent can read its working directory and the system libraries it needs to start (/usr/lib, /System, /usr/bin, /bin, and a few others). It cannot read anywhere else under your home directory, cannot reach the network, and cannot write anywhere outside the CWD and macOS temp directories. Use this when running an unfamiliar agent unattended.

standard

The default. The agent has full read and write access under $HOME, plus the system. Network is allowed. The universal denylist is still enforced, so ~/.aws/credentials, ~/.ssh/**, and similar paths remain unreadable. This is the right profile for day-to-day use with trusted agents like Claude Code or Cursor.

permissive

Filesystem and network access are unrestricted except for the universal denylist and anything you add via the deny list. Use this when you want minimal interference and just need the safety net of "never let it read my AWS keys."


Universal denylist

Seventeen patterns that are blocked in every profile, including permissive. Add more in your troopr.yaml under deny:.

PatternWhy it's blocked
**/.envdotenv files at any depth
**/.env.*.env.local, .env.production, etc.
**/*.pemPEM-encoded keys and certs
**/*.keyraw private keys
**/id_rsa*OpenSSH RSA keypairs
**/id_ed25519*OpenSSH Ed25519 keypairs
~/.ssh/**everything under your SSH directory
~/.aws/credentialsAWS access keys
~/.aws/configAWS profile + region config
~/.gnupg/**GPG keyrings and trust DB
~/.kube/configKubernetes cluster credentials
~/.docker/config.jsonDocker registry auth tokens
~/.netrclegacy HTTP basic auth store
~/Library/Keychains/**macOS Keychain database files
**/secrets/**anything in a secrets/ directory
**/*.kdbxKeePass 2 password databases
**/*.kdbKeePass 1 password databases

If your stack stores secrets somewhere unconventional (a ~/.config/myapp/ directory, a non-standard cloud config path, a custom token cache), add it to your deny list explicitly. The universal denylist covers the common cases; it cannot anticipate yours.


Recipes

Concrete one-liners for the agents people actually use.

Claude Code

$ cd ~/code/my-project
$ troopr run --profile standard -- claude

Cursor CLI

$ troopr run --profile standard -- cursor-agent

Codex CLI

$ troopr run --profile strict -- codex

Aider

$ troopr run --profile standard -- aider

An arbitrary script or binary

$ troopr run --profile strict -- ./my-script.sh --flag value
$ troopr run -- node ./scripts/refactor.js

Everything after -- is passed to sandbox-exec verbatim. Flags, environment variables, and stdio all work exactly as they would outside troopr.


Audit log

troopr tails the macOS unified log for sandbox denials and writes each one as a JSON line to ~/.troopr/log.jsonl.

Format

{"timestamp":"2026-05-14T03:30:26Z","agent":"claude","path":"/Users/g/.aws/credentials","action":"deny"}
FieldValue
timestampRFC3339 UTC.
agentThe first argument passed after -- (typically the agent binary name).
pathThe absolute path the kernel denied access to.
actionAlways "deny" in the current version.

Watching it live

$ troopr log -f

How it's collected

troopr spawns log stream --predicate 'eventMessage CONTAINS "Sandbox:" AND eventMessage CONTAINS "deny"' in the background while your agent runs. Each matching line is parsed and appended to ~/.troopr/log.jsonl. This is best-effort - if the unified log stream lags or drops events, the JSONL will lag too. Treat the audit log as a debug aid, not a security-of-record audit trail.


Exit codes

CodeMeaning
0Success.
1Configuration error. Invalid YAML, unknown profile, unknown key.
2Sandbox setup error. Could not exec sandbox-exec, profile file could not be written, etc.
nExit code of the wrapped command, propagated verbatim. A signal-killed child returns 128 + signal number.

Troubleshooting

"sandbox-exec: command not found"

You're not on macOS, or your shell can't see /usr/bin. troopr only runs on macOS 13+; there is no Linux or Windows build.

The agent works outside troopr but fails under strict

Most agents read configuration from ~/.config/<tool>/ or ~/.cache/<tool>/ on startup. strict blocks everything outside the CWD. Switch to standard and add specific deny patterns for the secrets you care about.

Reads of .env succeed when they shouldn't

Check that your config actually loaded. Run troopr check --profile standard and confirm the deny regex for .env appears in the generated profile. If you have a troopr.yaml with an explicit allow: entry that matches your file, the carve-out wins. Carve-outs are emitted after denies precisely so they can override.

"Operation not permitted" on files I want the agent to read

You're probably in strict. Either move to standard, or add an allow: rule for the path. If the path lives outside CWD and outside $HOME, even standard won't reach it - switch to permissive.

The audit log at ~/.troopr/log.jsonl is empty

The log stream background process needs a moment to start. Very short-lived agent runs may finish before the first deny event lands. Try the command again, or watch log show --predicate 'eventMessage CONTAINS "Sandbox:"' --last 1m to confirm denials are firing at the kernel level regardless.


Known limitations

  • macOS only. Apple's sandbox-exec has no Linux or Windows equivalent.
  • sandbox-exec is deprecated by Apple. It still ships on macOS 13–15 and works reliably; a future macOS release may force a migration to a different isolation mechanism.
  • Path-based, not content-based. A secret stored at an unusual path is readable until you add the path to deny:.
  • One profile per troopr run. There is no way to apply different rules to subprocesses spawned by the agent - they inherit the parent's policy.
  • Not a security boundary against an adversarial binary. troopr is defense-in-depth against honest mistakes (the agent reaching for ~/.aws/credentials because the LLM suggested it). A program actively trying to escape can probe TCC-permitted services, kernel bugs, or paths still reachable via mach-lookup.
  • Audit log is best-effort. It tails log stream and can miss events under load.