Profiles
Why Profiles?
Manually specifying capabilities for every tool is tedious and error-prone:Predefined Profiles vs User Profiles
Predefined profiles (shipped with nono or installed via packs) cover two things:- Agent packs — profiles for specific AI coding agents (Claude Code, Codex, opencode, openclaw). These grant the exact paths each agent needs to run, including its config directory, credential stores, and runtime group.
- Language runtimes — profiles for common developer workflows (node-dev, python-dev, go-dev, rust-dev). These include the appropriate runtime group and a developer network profile.
java-dev profile cannot know whether your JDK config lives in /etc/java-openjdk, /etc/java21-openjdk, or somewhere else entirely — that depends on your system.
Environment-specific additions belong in a user profile. Write a profile that extends the closest predefined profile and adds only what your environment requires:
~/.config/nono/profiles/java-dev-local.json and use it with:
Profile Sources
Profiles can come from three sources, in order of precedence:
CLI flags always override profile settings.
nono resolves config paths via
$XDG_CONFIG_HOME/nono/ on every platform. If
you have not set XDG_CONFIG_HOME — typical on macOS — that is the same as
~/.config/nono/ shown above. Set XDG_CONFIG_HOME only when you want
config elsewhere; profile JSON path grants should use $XDG_CONFIG_HOME,
$NONO_CONFIG, or $NONO_PACKAGES rather than hardcoded $HOME/.config/....Profile Format
Profiles use JSON format:Session Hooks
Session hooks are scripts that run before or after the sandboxed process, outside the sandbox with host privileges. This is the mechanism for custom instrumentation around a run — for example, replacing an external wrapper script that sets up or tears down per-session state. There are exactly two events,before and after; there is no mid-session or per-tool-call hook here. (That’s a different, unrelated feature: see Hooks below, which packs use to install an agent’s own hook system, e.g. Claude Code’s tool-call hooks.)
Configuration
Bothbefore and after are optional. Each hook specifies a script path and an optional timeout:
script(required): absolute path to an executable script. It must resolve to a regular file, be owned by you or root, and not sit in a world-writable directory — this rejects/tmpand similar shared directories even when the sticky bit is set. Put session hook scripts somewhere private, e.g. under$XDG_CONFIG_HOME/nono/hooks/.timeout_secs(optional): kills the whole script process group if it exceeds this duration.
NONO_HOOK_TYPE (before or after), in addition to the variables listed below.
Hooks fail open. An invalid script path, a non-zero exit, or a timeout never blocks the sandboxed process — nono logs a warning and continues. A timeout discards any environment variables the before-hook had already written; a plain non-zero exit does not — variables written before
exit still take effect.Before hook
Runs before the sandboxed process starts. The script receivesNONO_SESSION_ID, NONO_WORKDIR, and NONO_ENV_FILE — a temporary file where KEY=VALUE lines (blank lines and # comments are skipped) can be written to export environment variables to the sandboxed process.
LD_PRELOAD) is filtered out of the before-hook’s exports before they reach the sandboxed process, regardless of what the script writes to NONO_ENV_FILE.
After hook
Runs after the sandboxed process exits. ReceivesNONO_SESSION_ID, NONO_WORKDIR, and NONO_EXIT_CODE. Cleanup only — does not export environment variables.
AF_UNIX Socket Grants
Theunix_socket* filesystem fields grant connect(2) (and optionally
bind(2)) on pathname AF_UNIX sockets. Abstract-namespace and unnamed
sockets are never grantable — only filesystem-backed socket paths.
unix_socket_dir* forms are non-recursive at the socket layer: only
sockets directly inside the named directory are covered. Use
unix_socket_subtree* when a tool creates sockets below nested
subdirectories. The implied filesystem grant is recursive (Landlock’s
only expressible granularity), so socket scope is enforced separately by
the supervisor (Linux) or Seatbelt path emission (macOS).
Today, macOS enforces the direct-child versus subtree distinction in Seatbelt.
Linux V4+ currently relies on Landlock filesystem rules for pathname AF_UNIX,
so directory socket grants are recursive there until the seccomp AF_UNIX
allowlist mediation path is enabled.
Under restricted network modes (--block-net or --network-profile),
connect(2) to a Unix socket requires an explicit unix_socket* grant
— a plain allow_file/allow grant no longer implicitly permits it.
Working Directory
Theworkdir section controls whether and how the current working directory is automatically shared with the sandboxed process.
When a profile specifies a workdir access level, nono will prompt the user to confirm CWD sharing (unless
--allow-cwd is used to skip the prompt). --allow-cwd grants access to the directory at that profile’s workdir level — so with "none" (the default profile) it grants nothing. Pair --allow-cwd with a profile that sets a workdir level, or grant the directory explicitly with --allow/--read.
Filesystem Overrides and Group Composition
Fine-grained additive and subtractive composition lives across three canonical sections:filesystem— path-based allow, deny, and deny-exemption entriesgroups— policy group inclusion (include) and removal (exclude)commands— startup-time command allow/deny (deprecated since v0.33.0; not enforced on child processes)
Adding a Deny Rule to an Inherited Profile
Block a specific path that a base profile would otherwise allow:Blocking Container Access (Docker, Podman, kubectl)
Usefilesystem.deny as the enforcement mechanism. commands.deny remains deprecated startup-only compatibility behavior:
filesystem.deny on a socket path also emits a network-outbound deny — Seatbelt classifies connect(2) as a network operation, so a file deny alone won’t block it. Prefer path- and network-based controls; commands.deny is not enforced for child processes.
Adding Write-Only Access
Grant write-only access to a directory without granting read:Removing Groups from an Inherited Profile
Remove the deprecated startup-only command-gating group to allowrm, chmod, etc.:
Overriding a Deny Rule for a Specific Path
Some deny groups (likedeny_credentials) are marked as required and cannot be excluded. Use filesystem.bypass_protection to punch a targeted hole through a deny group for a specific path. The path must also be explicitly granted via another filesystem entry — bypass_protection only removes the deny rule, it does not implicitly grant access.
~/.docker even though deny_credentials blocks it by default. The deny override does not implicitly grant access — the matching filesystem.allow entry is required.
This is equivalent to the CLI flag --bypass-protection:
Suppressing Save Suggestions Without Granting Access
Usefilesystem.suppress_save_prompt when a path is expected to be denied and
you do not want nono to keep offering it as a profile addition:
filesystem.suppress_save_prompt only suppresses the save-profile suggestion
for matching denials. The sandbox still denies the path, and the diagnostic
footer still shows the denial, annotated with [save skipped] so it is clear
why the path does not appear in the save prompt.
The interactive save prompt applies this to every listed path suggestion when
you choose suppress; it does not create any allow/read/write grants.
Suppressing Expected macOS System-Service Diagnostics
Usediagnostics.suppress_system_services when a macOS Seatbelt operation is
expected to be denied and you do not want nono to repeat the diagnostic footer
(or offer a save-profile prompt) on every run:
user-preference-read (CFPreferences / NSUserDefaults probes from npm
and other Node tools) or forbidden-exec-sugid. Suppression is output-only:
the sandbox still denies the operation.
Unlike filesystem.suppress_save_prompt, this hides matching violations from
the diagnostic footer entirely. There is no CLI flag equivalent yet; add the
operation name to your profile. To silence all post-run diagnostics instead,
use --no-diagnostics.
filesystem, groups, commands, and diagnostics fields are additive across inheritance. A child profile’s filesystem.deny is merged with the base profile’s filesystem.deny, groups.exclude from both levels are combined, and diagnostics.suppress_system_services entries from both profiles are appended and deduplicated.Network Configuration
Thenetwork section controls network access and credential injection.
Custom Credentials
Thecustom_credentials field lets you define credential services for any API:
See Credential Injection for complete documentation.
Hooks (pack-declared, legacy profile field)
Thehooks field is a legacy declaration surface: since v0.44.0, setting it in a profile no longer installs anything itself — nono just prints a one-line note. Actual agent-hook wiring (e.g. installing a script into ~/.claude/hooks/ and patching settings.json) is now done by a pack’s package.json wiring directives, executed on nono pull/nono resolve. See Package Publishing if you’re authoring a pack that needs this.
This is unrelated to Session Hooks above, which is a currently-active profile feature for running host-privileged setup/cleanup scripts around the sandboxed process itself.
Rollback Exclusions
Therollback section controls which files are excluded from atomic rollback snapshots:
Skip Directories
Useskipdirs to extend the built-in heavy-directory skip list for pre-exec trust scanning and rollback preflight:
Environment Variables
Profiles support these environment variables in path values:Creating User Profiles
Usenono profile init to scaffold a new profile:
Overriding Pack Profiles
CLI flags always take precedence over profile settings:network.network_profile to null in the child:
Groups
Groups are named, composable collections of security rules. Profiles reference groups by name in theirgroups.include field.
How Groups Compose
Every profile’s effective capability set is built through composition:- default_profile_groups come from the built-in
defaultprofile - profile.groups.include adds additional groups on top
- profile.groups.exclude removes groups from the composed set (exclusion wins)
- profile.filesystem.allow/read/write, profile.filesystem.deny, and profile.commands.allow/deny apply additive overrides
- profile.filesystem.bypass_protection punches targeted holes through deny groups (requires a matching grant in
filesystem.allow/read/write) - CLI overrides (
--allow,--read,--bypass-protection, etc.) are applied last
profile.filesystem.suppress_save_prompt and --suppress-save-prompt are
prompt filters only; they do not participate in capability enforcement.
profile.diagnostics.suppress_system_services is also output-only (macOS). It
hides recurring non-filesystem Seatbelt violations from diagnostic footers and
save-profile prompts without granting the underlying operation.
Exclusions are applied after group addition. If the same group appears in both
groups.include and groups.exclude, the exclusion wins.
Group Taxonomy
Groups use a structured allow/deny taxonomy:Allow Operations
Deny Operations
Other
Built-in Groups
Adding a group to
groups.include always applies that group’s own rules.
For allow groups (like git_config) that grants access. For deny/protection
groups (like dangerous_commands, unlink_protection) that adds a
restriction — it does not grant the denied commands or paths. To permit a
normally-blocked command or path, put the group in groups.exclude instead,
or use filesystem.bypass_protection for a narrower hole.-
deny_credentials— Block access to cryptographic keys, tokens, and cloud credentials (source) -
deny_keychains_macos— Block access to macOS keychains and password stores (source) -
deny_keychains_linux— Block access to Linux keyrings and password stores (source) -
deny_browser_data_macos— Block access to macOS browser stored data (cookies, saved passwords, sessions) (source) -
deny_browser_data_linux— Block access to Linux browser stored data (cookies, saved passwords, sessions) (source) -
deny_macos_private— Block access to macOS private data (messages, mail, cookies) (source) -
deny_shell_history— Block access to shell command history files (source) -
deny_shell_configs— Block access to shell configuration files that may embed secrets (source)
-
unlink_protection— Block file deletion globally, override for user-writable paths (source) -
dangerous_commands— Cross-platform commands blocked by default to prevent destructive actions (source) -
dangerous_commands_macos— macOS-specific commands blocked by default (source) -
dangerous_commands_linux— Linux-specific commands blocked by default (source)
-
system_read_macos— macOS system paths required for executables to function (source) -
system_read_linux_core— Linux core system paths required for normal CLI execution (source) -
system_write_macos— macOS paths requiring write for temp files and devices (source) -
system_write_linux— Linux paths requiring write for temp files and devices (source) -
linux_runtime_state— Linux runtime state paths for host session and service integration (source) -
linux_sysfs_read— Linux sysfs paths for kernel and device state inspection (source) -
linux_temp_read— Linux shared temporary directory read access (source)
-
user_caches_macos— User cache, log, and preference directories for macOS programs (source) -
user_caches_linux— User cache directories for Linux programs (XDG) (source) -
claude_cache_linux— Claude Code CLI cache and MCP log directories on Linux (source)
-
node_runtime— Node.js runtime and package manager paths (source) -
rust_runtime— Rust toolchain paths (source) -
python_runtime— Python runtime paths (pyenv, conda, uv) (source) -
go_runtime— Go toolchain paths (source) -
go_runtime_linux— Go build cache on Linux (source) -
go_runtime_macos— Go build cache on macOS (source) -
java_runtime— Java runtime and build tool paths (SDKMAN, Maven, Gradle) (source) -
nix_runtime— Nix package manager runtime paths (source) -
user_tools— User-local executables, .desktop files, man pages, and shell completions (source) -
homebrew_macos— Homebrew installation paths on macOS (source) -
homebrew_linux— Homebrew (Linuxbrew) installation paths on Linux (source) -
mise_manager— Mise - dev tools, env vars, task runner (source) -
bun_runtime— Bun javascript runtime and package manager paths (source)
-
claude_code_macos— Claude Code macOS-specific state and credential paths (source) -
claude_code_linux— Claude Code Linux-specific state paths (source) -
codex_macos— Codex macOS-specific state and credential paths (source) -
opencode_linux— OpenCode binary directory (Landlock requires directory read to execute binaries) (source) -
vscode_macos— Visual Studio Code configuration and extension directories for macOS (source) -
vscode_linux— Visual Studio Code configuration and extension directories for Linux (source)
git_config— Read access to git configuration files (source)
Group definitions are located in
crates/nono-cli/data/policy.json. Each
entry above links to its exact definition; inspect the file directly to see
the full set of rules a group applies.Platform-Specific Groups
Groups with aplatform field only apply on that OS:
platform field (like deny_credentials) apply on all platforms.
Platform-specific groups use _macos or _linux suffixes by convention.
Platform Differences
macOS (Seatbelt) supports full deny-within-allow semantics. A group can allow/Users but deny /Users/luke/.ssh and the deny takes precedence.
Linux (Landlock) is strictly allow-list. Deny groups are implemented as exclusion filters - broad allow groups that overlap deny paths will generate warnings. Avoid granting access to parent directories of deny paths on Linux.
Available Profiles
default
The base profile that all other profiles extend. Provides system path access, deny groups for sensitive content, and deprecated startup-only command gating. Does not grant working directory access or any user-specific paths. Groups:deny_credentials, deny_keychains_macos, deny_keychains_linux, deny_browser_data_macos, deny_browser_data_linux, deny_macos_private, deny_shell_history, deny_shell_configs, system_read_macos, system_read_linux_core, system_write_macos, system_write_linux, user_tools, homebrew_macos, homebrew_linux, dangerous_commands, dangerous_commands_macos, dangerous_commands_linux
Network: Allowed
CWD: None
claude-code
claude_code_macos, claude_code_linux, user_caches_macos, claude_cache_linux, node_runtime, rust_runtime, python_runtime, vscode_macos, vscode_linux, nix_runtime, git_config, unlink_protection (plus default groups)
Filesystem: ~/.claude (read+write), ~/.claude.json and ~/.claude.json.lock (read+write), plus platform-specific Claude Code and VS Code paths from the matching _macos or _linux groups, and git config files from git_config group
Network: Allowed
CWD: Read+write
Special: Auto-installs Claude Code hooks. OAuth2 login support via open_urls (allows https://claude.ai and localhost). Profile opts into allow_launch_services for macOS browser opening.
codex
codex_macos, node_runtime, rust_runtime, python_runtime, nix_runtime, git_config, unlink_protection (plus default groups)
Filesystem: ~/.codex (read+write), plus git config files from git_config group
Network: Allowed
CWD: Read+write
Special: OAuth2 login support via open_urls (allows https://auth.openai.com and localhost). Profile opts into allow_launch_services.
opencode
user_caches_macos, user_caches_linux, node_runtime, opencode_linux, git_config, unlink_protection (plus default groups)
Filesystem: ~/.config/opencode, ~/.cache/opencode, ~/.local/share/opencode, ~/.local/share/opentui (all read+write), plus git config files from git_config group
Network: Allowed
CWD: Read+write
openclaw
node_runtime (plus default groups)
Filesystem: ~/.openclaw, ~/.config/openclaw, ~/.local, $TMPDIR/openclaw-$UID (all read+write)
Network: Allowed
CWD: Read-only
python-dev
python_runtime (plus default groups)
Network: Allowed, with developer network profile for host filtering
CWD: Read+write
node-dev
node_runtime (plus default groups)
Network: Allowed, with developer network profile for host filtering
CWD: Read+write
go-dev
go_runtime (plus default groups)
Network: Allowed, with developer network profile for host filtering
CWD: Read+write
rust-dev
rust_runtime (plus default groups)
Network: Allowed, with developer network profile for host filtering
CWD: Read+write
java-dev
java_runtime (plus default groups)
Network: Allowed, with developer network profile for host filtering
CWD: Read+write
java-dev covers SDKMAN-managed JDKs, Maven, and Gradle. It does not include
system JDK config paths (e.g. /etc/java21-openjdk) because these vary by
distribution and installation method. Add them in a user profile that extends
java-dev — see Predefined Profiles vs User Profiles.Requesting New Profiles
If you’d like a profile for a tool not listed here:- Open an issue on the nono GitHub repository
- Include:
- Tool name and repository URL
- Required filesystem access patterns
- Network requirements
- Any special considerations