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 | Git root .nah.yaml; outside Git, current directory .nah.yaml |
Project policy, tighten-only until trusted |
nah config path # show both paths
nah config show # display effective merged config
Project config roots¶
Inside Git, the Git root is the project root. nah loads only the root
.nah.yaml, even when you run from a subdirectory.
repo/
.git/
.nah.yaml # loaded from anywhere inside repo
app/.nah.yaml # ignored
Outside Git, nah does not search parents. It loads ./.nah.yaml only when the
current directory contains it.
workspace/
.nah.yaml # loaded only when cwd is workspace/
app/ # from here, parent .nah.yaml is not searched
nah config path shows the active project config path, root, and whether that
root is trusted.
Global vs project scope¶
Global config can do everything -- override policies, add trusted paths, configure LLM, modify safety lists.
Project config can only tighten policy by default. It can:
- 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)
- Contribute runtime
classifyentries before the project root is trusted - 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 UI, terminal settings, or non-policy target knobs
This is the supply-chain safety model: a malicious repo's .nah.yaml can't weaken your protections.
Trusting project config¶
Trust is per exact project root. Use it when you trust that project's
.nah.yaml to loosen policy or define project-specific command
classifications:
nah trust-project # trust the active project root, or cwd outside Git
nah trust-project /path/to/repo
nah untrust-project /path/to/repo
This writes trusted_project_configs to global config:
trusted_project_configs:
- /path/to/repo
Trust is exact-root after path resolution. Trusting /work does not trust
/work/app as a separate project root.
trusted_project_configs is different from trusted_paths: project-config
trust lets that root's .nah.yaml loosen nah policy; trusted_paths allows
filesystem operations in selected directories outside the project boundary.
Merge rules¶
When both configs exist, nah merges them with these rules:
| Field | Merge behavior |
|---|---|
presets |
Global only; selected explicitly for one invocation/session |
trusted_project_configs |
Global only; exact project roots whose .nah.yaml can loosen policy |
actions |
Tighten-only (project can only escalate strictness) |
classify |
Global entries are active first; project entries are active only for trusted project roots |
sensitive_paths |
Tighten-only; trusted project config can loosen |
sensitive_basenames |
Global only |
content_patterns |
Project can tighten policies only (add/suppress global-only) |
credential_patterns |
Global only |
provenance |
Project can tighten policies only until trusted; mode/review/category removals are global or trusted |
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 until trusted |
log |
Global only |
active_allow |
Global only |
Quick reference — all config keys¶
| Key | Type | Scope | Docs |
|---|---|---|---|
presets |
dict of name → config overlay | global | Presets |
trusted_project_configs |
list of paths | global | This page |
classify |
dict of type → prefix list | global; trusted project roots | 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) |
taint |
dict (mode, sources, propagation, categories, policies) |
both* | Taint tracking |
provenance |
dict (mode, categories, policies, review) |
both* | Session provenance |
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 |
* Project sensitive_paths_default can only tighten (ask → block) until the project root is trusted. Project taint and provenance can tighten policies by default; non-policy shape changes require trusted project config. Target-scoped project overrides can tighten policy by default; non-policy target settings require trusted project config.
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
Global target overrides can set actions, sensitive_paths_default,
sensitive_paths, content_patterns.policies, llm.mode, llm.eligible, UI
settings, and shell terminal settings. Untrusted project target overrides can
tighten action, sensitive-path, and content policies only. Trusted project
config can loosen policy and change non-policy target settings for that exact
project root.
Codex approval settings are owned by nah run codex. The launcher defaults to
Codex danger-full-access plus untrusted approvals; use
nah run codex --sandbox workspace-write or --sandbox read-only when you want
Codex's own sandbox too. Target config can tune nah policies and LLM behavior
for Codex, but it cannot change Codex safety knobs directly.
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 an OS
keychain/keyring when your CLI install includes keyring support.
Presets¶
Presets are named global config overlays. Use them when you want a temporary policy bundle for one workflow without editing your base config.
# ~/.config/nah/config.yaml
actions:
unknown: ask
presets:
strict:
actions:
network_outbound: ask
lang_exec: ask
unknown: ask
work:
known_registries:
- npm.company.test
targets:
codex:
actions:
filesystem_write: ask
Select a preset explicitly:
nah run claude --preset strict
nah run codex --preset work
nah test --preset strict "python3 script.py"
nah config show --preset work
NAH_PRESET=work claude
Inspect configured presets:
nah config presets # list preset names
nah config presets strict # show the raw global preset block
nah config show --preset strict
nah config presets <name> shows what you wrote in global config.
nah config show --preset <name> shows the final effective config after base
global config, the selected preset, project config safety rules, and target
overrides are applied.
Preset merge rules are intentionally simple:
- Dicts deep-merge, so
actions.network_outboundcan change without restating every action. - Scalars replace.
- Lists replace. This applies to trust/scope lists such as
known_registries,trusted_paths,trusted_containers, pattern lists, anddb_targets.
Presets are global-only in this version. Project .nah.yaml files cannot
define or select presets. Unknown selected preset names fail closed.
Legacy profile key¶
Older nah versions supported profile: full, profile: minimal, and
profile: none. That taxonomy-profile concept has been removed. nah now always
runs the full built-in taxonomy, classifiers, safety lists, sensitive path
checks, and content scanners.
Existing profile keys are accepted for compatibility but ignored. In
particular, profile: none no longer disables built-in safety checks. Use
presets for named policy bundles.
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/keyring
instead of exported env vars. The default Nix package also includes Python
keyring integration, but actual OS keychain/keyring availability depends on
the host backend. If you want both YAML config support and key management with
pip, use pip install "nah[config,keys]" or pipx inject nah pyyaml keyring.