Configuration Overview¶
nah works out of the box with zero config. When you want to tune it, configuration lives in two places.
File locations¶
| Scope | Path | Purpose |
|---|---|---|
| Global | ~/.config/nah/config.yaml |
Your personal preferences, trusted paths, LLM setup |
| Project | .nah.yaml (in git root) |
Per-project tightening, custom classifications |
nah config path # show both paths
nah config show # display effective merged config
Global vs project scope¶
Global config can do everything -- override policies, add trusted paths, configure LLM, modify safety lists.
Project config can only tighten security by default. It can:
- Add classify entries (commands → action types)
- Escalate action policies (e.g.,
git_write: ask) - Tighten content pattern policies (ask → block)
- Add target-scoped tightening under
targets.<target>
It cannot:
- Relax any policy (lowering strictness is rejected)
- Modify safety lists (
known_registries,exec_sinks, etc.) - Set
trusted_paths,allow_paths, ordb_targets - Configure provider credentials or the global LLM provider cascade
- Change the taxonomy profile
This is the supply-chain safety model: a malicious repo's .nah.yaml can't weaken your protections.
You can explicitly opt out of this model by setting trust_project_config: true
in global config. Only use that for repositories whose .nah.yaml you already
trust, because project config can then loosen policies.
Merge rules¶
When both configs exist, nah merges them with these rules:
| Field | Merge behavior |
|---|---|
profile |
Global only |
trust_project_config |
Global only; when true, project config can loosen policy |
actions |
Tighten-only (project can only escalate strictness) |
classify |
Kept separate (global = Phase 1, project = Phase 3 lookup; project can only tighten overlaps unless trusted) |
sensitive_paths |
Tighten-only unless project config is trusted |
sensitive_basenames |
Global only |
content_patterns |
Project can tighten policies only (add/suppress global-only) |
credential_patterns |
Global only |
known_registries |
Global only |
exec_sinks |
Global only |
decode_commands |
Global only |
trusted_paths |
Global only |
allow_paths |
Global only |
db_targets |
Global only |
llm |
Global only |
targets |
Global can override; project can only tighten unless trusted |
log |
Global only |
active_allow |
Global only |
Quick reference — all config keys¶
| Key | Type | Scope | Docs |
|---|---|---|---|
profile |
full / none |
global | Profiles |
trust_project_config |
bool | global | This page |
classify |
dict of type → prefix list | both* | Classification rules |
actions |
dict of type → policy | both | Action types |
sensitive_paths_default |
ask / block |
both* | Sensitive paths |
sensitive_paths |
dict of path → policy | both | Sensitive paths |
allow_paths |
dict of path → project list | global | Sensitive paths |
trusted_paths |
list of paths | global | Sensitive paths |
known_registries |
list or dict (add/remove) | global | Safety lists |
exec_sinks |
list or dict (add/remove) | global | Safety lists |
sensitive_basenames |
dict of name → policy | global | Safety lists |
decode_commands |
list or dict (add/remove) | global | Safety lists |
content_patterns |
dict (add/suppress) | both | Content inspection (Claude Code) |
credential_patterns |
dict (add/suppress) | global | Content inspection (Claude Code) |
llm |
dict (mode, providers, eligible, context_chars) |
global | LLM layer |
targets |
dict of target → overrides | both* | This page |
db_targets |
list of database/schema dicts | global | Database targets |
log |
dict (verbosity, etc.) | global | CLI reference |
active_allow |
true, false, or list of tool names |
global | Claude Code |
* classify entries in global config are Phase 1 (checked first, can override built-in). Project entries are Phase 3: they can add new commands and can tighten overlapping built-in classifications, but cannot weaken them unless trust_project_config: true is set globally. sensitive_paths_default in project config can only tighten (ask → block) unless project config is trusted. Target-scoped project overrides follow the same tighten-only rule.
Target overrides¶
Use targets.<target> when a runtime needs different policy from the shared
default. Supported config targets are claude, codex, bash, and zsh.
codex applies to sessions launched with nah run codex; it is not an
install target.
# ~/.config/nah/config.yaml
actions:
lang_exec: context
git_remote_write: ask
llm:
mode: on
providers: [openrouter]
openrouter:
key_env: OPENROUTER_API_KEY
model: google/gemini-3.1-flash-lite-preview
targets:
claude:
llm:
mode: on
codex:
actions:
network_outbound: ask
llm:
mode: on
bash:
actions:
network_outbound: ask
llm:
mode: off
terminal:
bypass_env: NAH_TERMINAL_BYPASS
zsh:
actions:
network_outbound: ask
llm:
mode: off
Target overrides can set actions, sensitive_paths_default,
sensitive_paths, content_patterns.policies, and llm.mode /
llm.eligible. Shell-specific options live under targets.bash.terminal and
targets.zsh.terminal.
Codex edit auto-allow is intentionally session-scoped in this release. Use
nah run codex --auto-edits or nah run codex --flow; untrusted project
config cannot enable it silently.
Public nah test --target simulation currently supports claude, bash, and
zsh. Do not use codex there unless a later release adds that CLI target.
Bash and zsh are terminal-guard targets. They default to LLM mode off even
when global LLM mode is on. Enable terminal LLM review only with an explicit
target override such as
targets.bash.llm.mode: on.
Provider credentials and provider selection stay global-only. Configure LLM
providers directly in global config and store environment-variable names such
as llm.openrouter.key_env, not raw API keys. The secret value behind that
slot can live either in the current process environment or in the OS keychain
used by the optional nah[keys] extra on PyPI installs.
YAML format¶
Both config files use standard YAML. If nah detects comments in a file before a CLI write operation (nah allow, nah classify, etc.), it warns you that comments will be removed and asks for confirmation.
Optional dependency: pip install "nah[config]" installs pyyaml. The default
install keeps nah's core hook/classifier stdlib-only for users who want the
smallest supply-chain surface. Install the config extra when you want YAML config
files or commands that write config (nah allow, nah deny, nah classify,
nah trust). With pipx, use pipx inject nah pyyaml.
Optional dependency: pip install "nah[keys]" installs keyring support for the
PyPI CLI so remote-provider secret values can live in your OS keychain instead
of exported env vars. If you want both YAML config support and key management,
use pip install "nah[config,keys]" or pipx inject nah pyyaml keyring.