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.
Where it lives
- Default path:
~/.nio/config.yaml - Override directory: set
NIO_HOMEenvironment variable - Reset to defaults:
/nio resetorsetup.sh --reset-config
You can edit the file directly or use /nio config <level> to change the protection level without opening an editor.
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.
| Field | Type | Default | Note |
|---|---|---|---|
enabled | boolean | false | Master switch. |
api_key | string | "" | Anthropic API key. |
model | string | "claude-sonnet-4-20250514" | Model identifier. |
max_input_tokens | number | 50000 | Per-scan input budget. |
guard.external_analyser
Type: object
Phase 6 — external scoring API. Disabled by default. Requires an endpoint.
| Field | Type | Default | Note |
|---|---|---|---|
enabled | boolean | false | Master switch. |
endpoint | string | "" | HTTP URL. Use http://localhost:9090 for the bundled mock. |
api_key | string | "" | Bearer token (optional). |
timeout | number | 3000 | Request timeout (ms). |
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) or server-qualified (hass__HassTurnOn — that server only). 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; tools absent from the map skip Phase 1–6 (auto-allow, log only). MCP tools are dynamic and not categorised here.
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 |
external | 2.0 | Phase 6 — external scoring API |
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.
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: "a7f966c2-02a1-46f3-92cf-51d6889c52f4"
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. |