Every field in config.yaml.
Nio reads ~/.nio/config.yaml (or $NIO_HOME/config.yaml). Two top-level sections: guard for the runtime pipeline + scan rules, collector for OTLP telemetry. The bundled template at config.default.yaml has inline comments for every field.
Something not working? Run /nio doctor to validate the current config schema and dry-run OAuth / LLM connectivity. See Diagnostics for the full failure-surface model.
Where it lives
- Default path:
~/.nio/config.yaml - Override directory: set
NIO_HOMEenvironment variable - Reset to defaults:
/nio resetorsetup.sh --reset-to-defaults
You can edit the file directly or use /nio config <level> to change the protection level without opening an editor.
To apply an operator-provided config in one shot, run /nio config import <path>. nio runs /nio doctor against the incoming YAML first and refuses to overwrite if any probe fails; on success the previous file is preserved as config.yaml.bak.<ISO-stamp>.
Top-level
agent_name
Type: string · Default: "" · Scope: telemetry only
User-provided telemetry identity (alias). When non-empty, it overrides the platform-derived value on gen_ai.agent.name in traces and log records, and lands as the agent_name field in ~/.nio/audit.jsonl. Use it to split telemetry by deployment / machine / user when multiple installations share one collector backend ("alice-laptop", "ci-runner-3", "prod-scoring-east").
The nio.platform dimension is not overridden — backends still see both axes (nio.platform = the underlying CLI host like claude-code, gen_ai.agent.name = the operator's alias). When empty / unset, behaviour is identical to today: gen_ai.agent.name falls back to platform.
Not emitted on metrics — keeping per-deployment slicing out of metric labels avoids ballooning the backend's cardinality budget; query nio.platform for host-level metric slicing.
agent_name: "alice-laptop"
guard
guard.protection_level
Type: string · Default: balanced · Values: strict | balanced | permissive
Decision aggressiveness. Determines the score thresholds for allow/confirm/deny. See Scoring → thresholds for the full matrix.
guard:
protection_level: balanced
guard.confirm_action
Type: string · Default: allow · Values: allow | deny | ask
What to do when the guard's decision is confirm but the platform has no interactive prompt (e.g. OpenClaw). allow lets it through and writes a warning to the audit log; deny blocks; ask uses the platform confirm if available, else falls back to allow.
guard.file_scan_rules
Type: object · Default: all empty arrays
Extra regex patterns added to the named scan module. Keys are module names; values are arrays of regex pattern strings. Modules:
shell_exec— feedsSHELL_EXEC[high] andAUTO_UPDATE[critical]remote_loader— feedsREMOTE_LOADER[critical]secrets— feedsREAD_ENV_SECRETS[med],READ_SSH_KEYS/READ_KEYCHAIN/PRIVATE_KEY_PATTERN[critical]obfuscation— feedsOBFUSCATION[high]prompt_injection— feedsPROMPT_INJECTION[critical]exfiltration— feedsNET_EXFIL_UNRESTRICTED[high],WEBHOOK_EXFIL[critical]trojan— feedsTROJAN_DISTRIBUTION[critical],SUSPICIOUS_PASTE_URL[high],SUSPICIOUS_IP/SOCIAL_ENGINEERING[med]
guard:
file_scan_rules:
shell_exec:
- "my_unsafe_exec\\("
remote_loader:
- "loadFromCDN\\("
guard.action_guard_rules
Type: object
Extra patterns for Phase 2 runtime action analysis. Two field shapes:
- String fields — plain strings, matched as described per field (case-insensitive substring / prefix / domain suffix). No regex metacharacters, no escaping.
- Regex fields — names ending in
_patterns. Each entry is either a bare regex string or the/pattern/flagsform (flags: g i m s u y). YAML requires backslashes to be escaped — write\\d,\\/,\\.inside quotes.
All fields extend the built-in detection lists; they never replace them. Every match produces a Phase 2 finding at the severity shown in [brackets].
dangerous_commands [critical]
Case-insensitive substring match against the full command line. Use for fixed literal fragments to hard-block.
action_guard_rules:
dangerous_commands: ["format c:", "shutdown -h now"]
dangerous_patterns [critical]
Regex against the full command line. Use when you need alternation, anchors, or character classes.
action_guard_rules:
dangerous_patterns:
- "/rm\\s+-rf\\s+\\//"
- "/curl\\s+.*\\|\\s*(ba)?sh/i"
sensitive_commands [high]
Commands that read sensitive data. Case-insensitive substring match. Built-ins already cover /etc/passwd, ~/.ssh, ~/.aws, printenv, etc.
system_commands [high]
Word-boundary match on the program name (start of line or after a space). Add a trailing space for short names to prevent prefix hits (e.g. "su " not "su").
network_commands [medium]
Same word-boundary rule. Flags any use regardless of target.
webhook_domains [high]
Hostnames matched exactly OR as suffix (foo.example.com is covered by example.com). No scheme, no path, no port.
webhook_domains: ["webhook.site", "requestbin.net", "ngrok-free.app"]
sensitive_paths [critical]
Substring match with a specific convention: the matcher prepends / to each pattern and tests normalized.includes("/" + pattern) || normalized.endsWith(pattern). Input is normalized first: ~/ expands to /HOME/, backslashes become forward slashes.
Two supported shapes:
- Directory fragment — DO NOT include a leading slash; the matcher adds one. Use
etc/,raw_files/,.ssh/. Writing/etc/is a common mistake — it becomes//etc/internally and almost never matches. - Filename suffix — matched via
endsWith(e.g..env,id_rsa,credentials.json,.env.prod).
A bare relative path like raw_files/foo.txt (no leading /) does NOT match a raw_files/ pattern. Use sensitive_path_patterns with "/^raw_files\\//" for that.
sensitive_path_patterns [critical]
Regex against the same normalized path. Use for dynamic segments, relative paths, or flag-based matching.
sensitive_path_patterns:
- "/^\\/abc\\/[^/]+\\/def/" # /abc/<id>/def
- "/^raw_files\\//" # bare relative paths
- "/\\.env\\.[a-z]+$/i" # .env.prod, .env.staging
secret_patterns [high]
Regex against HTTP request body previews on network_request actions. Use for internal token formats the built-in detector doesn't know about.
secret_patterns:
- "/ACME_[A-Z0-9]{32}/"
- "/internal-token:\\s*[\\w-]+/i"
guard.llm_analyser
Type: object
Phase 5 — semantic analysis using Claude. Disabled by default. Requires an Anthropic API key. Validated by /nio doctor.
| Field | Type | Default | Note |
|---|---|---|---|
enabled | boolean | false | Master switch. |
api_key | string | "" | Anthropic API key. Empty + enabled: true → diagnostic. |
model | string | "claude-sonnet-4-20250514" | Model identifier. |
max_input_tokens | number | 50000 | Per-scan input budget. |
guard.external_analyser
Type: array · Default: []
Phase 6 — external scoring API endpoints. 0..N endpoints; each runs concurrently. Each entry contributes to the weighted score average via its own weight field. See the Phase 6 page for the full wire model. Validated by /nio doctor (OAuth entries get a real client_credentials dry-run).
Each entry is an object:
| Field | Type | Default | Note |
|---|---|---|---|
name | string | (required) | Unique identifier, appears in scores.external and phase_timings.external. |
endpoint | string | (required) | HTTPS URL of the scoring endpoint. nio always issues GET — encode any context the endpoint needs in the URL itself (e.g. ?agent-name=X). Endpoint must return { "score": number, "reason"?: string } — see Phase 6 → Response contract. |
headers | object | — | Optional custom request headers. Merged over nio defaults; user entries override (including Authorization). |
enabled | boolean | true | Set false to skip an entry without removing config. |
weight | number | 1.0 | Aggregation weight in final_score = Σ(wᵢ × sᵢ) / Σ(wᵢ). |
timeout | number | 3000 | Per-request timeout (ms). Exceeded → diagnostic. |
auth | object | none | Optional. { type: 'bearer', api_key } or { type: 'oauth', oauth_url, client_id, client_secret }. OAuth uses the client_credentials grant against {oauth_url}/token — pre-register the client with the OAuth provider. OAuth failures → diagnostic. |
guard.allowed_commands
Type: string[] · Default: []
Phase 1 allowlist. Command prefixes treated as safe — additional to the built-in list (ls, git status, npm test, etc.).
allowed_commands: ["npm run", "pnpm "]
guard.allowlist_mode
Type: string · Default: continue · Values: continue | exit
What happens when a command matches an allowlist entry. continue records a hint but still runs Phases 2–6 (so external/LLM policy can't be bypassed). exit returns immediately — fastest path, skips later phases.
guard.permitted_tools
Type: object · Default: { claude_code: [], codex: [], openclaw: [], hermes: [], mcp: [] }
Phase 0 strict allowlist, per platform. When non-empty for a namespace, ONLY listed tools pass on that platform; everything else is denied. The mcp key is platform-agnostic and applies whenever the incoming tool is an MCP tool.
MCP entries accept either a bare local name (HassTurnOn — matches any MCP server; also matches Hermes's flattened single-underscore form when listed verbatim, e.g. mcp_config_db_get_current_config) or server-qualified (hass__HassTurnOn — that server only). List tool names exactly as they appear in the hook payload — no prefix stripping. Matching is case-insensitive.
The mcp list also catches indirect MCP invocations embedded in shell commands — Phase 0 unwraps the command (16 layers covering shells, heredocs, evals, encodings, package wrappers, ssh/docker exec, …) and runs detectors against every fragment (mcporter, curl/wget/HTTPie, language-runtime one-liners, TCP/socket multiplexers, stdio JSON-RPC pipes, package runners, etc.), mapping each hit back to {server, tool} via the MCP server registry. The full capture model lives at Phase 0 — Tool Gate · MCP Tool Routing.
guard.blocked_tools
Type: object · Default: { claude_code: [], codex: [], openclaw: [], hermes: [], mcp: [] }
Same shape as permitted_tools; listed tools are unconditionally blocked. mcp covers MCP tools on every platform in one place. Takes precedence over permitted_tools.
guard.mcp_servers
Type: object · Default: {}
Manual override / addition for the MCP server registry. Phase 0 auto-discovers servers from ~/.claude.json, Claude Desktop, ~/.hermes/config.yaml, ~/.codex/config.toml, and ~/.openclaw/openclaw.json; this field is for SaaS or otherwise non-discoverable servers. Each entry's key is the server name; its value indexes the server by every reachable handle so detectors can route indirect calls back to it.
guard:
mcp_servers:
hass:
urls: ['http://localhost:5173/mcp', 'wss://saas.example/mcp']
sockets: ['/tmp/mcp-hass.sock']
binaries: ['mcp-server-hass']
cliPackages: ['@hass/mcp-cli']
All four arrays are optional. URLs are normalized (case-insensitive host, trailing slash stripped); origin matching covers any sub-path of the registered URL. Binary and CLI-package lookups are case-insensitive and basename-aware. See Phase 0 — Tool Gate · MCP Server Registry for full semantics.
guard.native_tool_mapping
Type: object
Native tool → action type classification, per platform. Not a third allow/deny list — this is the table that decides which Phase 1–6 rule set runs for each platform-native tool. Mapped tools enter Phase 1–6 with the chosen action type. Unmapped tools follow a fallback chain in the hook engine: tools that match an MCP convention are dispatched as mcp_tool_call and flow through Phase 1–6 (Phase 3 runs over the JSON-serialised args; Phase 5 LLM and Phase 6 external scorers receive the same payload); any other unmapped tool is allowed through with an UNCATEGORIZED_TOOL audit entry, skipping Phase 1–6.
native_tool_mapping:
claude_code:
Bash: exec_command
Write: write_file
Edit: write_file
WebFetch: network_request
WebSearch: network_request
codex:
Bash: exec_command # Codex's only native tool — writes/reads/fetches go through shell
openclaw:
exec: exec_command
write: write_file
web_fetch: network_request
browser: network_request
hermes:
terminal: exec_command
exec: exec_command
shell: exec_command
write_file: write_file
patch: write_file
read_file: read_file
fetch: network_request
http_request: network_request
guard.scoring_weights
Type: object
Per-phase weights for the final score aggregation. final = Σ(wi × si) / Σ(wi) across phases that ran.
| Field | Default | Phase |
|---|---|---|
runtime | 1.0 | Phase 2 — pattern matching |
static | 1.0 | Phase 3 — static rules on file content |
behavioural | 2.0 | Phase 4 — AST dataflow |
llm | 1.0 | Phase 5 — LLM semantic |
Phase 6 weights are per-endpoint, configured on each external_analyser[].weight rather than here.
collector
Telemetry. Disabled when endpoint is empty AND all local options are off.
See Collector Signals for the full per-instrument / per-span / per-event schema that lands in OTLP and the local audit log.
collector.endpoint
Type: string · Default: "http://localhost:4318"
OTLP base URL. /v1/traces, /v1/metrics, /v1/logs are appended automatically per signal. /nio doctor validates the schema only — it does not probe the endpoint, since collector gateways are commonly gated by routing headers (e.g. x-event-pipeline-id) that a bare reachability probe wouldn't include. Export failures surface at runtime as collector / otlp_export_failed.
collector.api_key
Type: string · Default: ""
Bearer token for OTLP endpoint authentication.
collector.headers
Type: object · Default: {}
Extra request headers sent to the OTLP endpoint. Values are stringified before export.
collector:
headers:
x-event-pipeline-id: "YOUR_PIPELINE_ID"
collector.timeout
Type: number · Default: 5000
OTLP export timeout in milliseconds.
collector.protocol
Type: string · Default: http · Values: http | grpc
Transport. http uses HTTP/JSON on port 4318; grpc uses gRPC on port 4317.
collector.metrics
| Field | Type | Default | Note |
|---|---|---|---|
enabled | boolean | true | OTLP metrics export. |
Metrics are exported via OTLP only — there is no local metrics file. Hook event audit records live in the audit log (see collector.logs).
collector.traces
| Field | Type | Default | Note |
|---|---|---|---|
enabled | boolean | true | Spans for tool calls, turns, tasks. |
collector.logs
| Field | Type | Default | Note |
|---|---|---|---|
enabled | boolean | true | OTEL Logs export. |
local | boolean | true | Local JSONL backup. |
path | string | "~/.nio/audit.jsonl" | The file /nio report reads. |
max_size_mb | number | 100 | Rotation threshold. |
Environment variables
| Variable | Default | Effect |
|---|---|---|
NIO_HOME | ~/.nio | Where Nio reads config.yaml, writes audit log + metrics. |
CLAUDE_CONFIG_DIR | ~/.claude | Used by setup.sh --cc-home resolution. |
OPENCLAW_STATE_DIR | ~/.openclaw | Used by setup.sh --openclaw-home resolution. |