Skip to main content
The nono profile command provides scaffolding and tooling for creating custom profiles. Instead of reverse-engineering the JSON structure from existing profiles, you can generate skeleton files, get editor autocomplete via JSON Schema, and access an LLM-oriented authoring guide.

File Format

Profile files use JSONC (JSON with Comments): standard JSON plus // line comments, /* */ block comments, and trailing commas. Both .json and .jsonc file extensions are accepted.
When both my-profile.jsonc and my-profile.json exist, the .jsonc file takes priority.
For an overview of what profiles are and how they compose with groups, see Profiles & Groups.

How extends Works

extends lets your profile build on top of an existing one. Instead of writing everything from scratch, you pick a base and only specify what you want to add or change.
This profile inherits everything from default and adds one extra directory. The base profile is unchanged.

What happens when profiles are combined

When nono loads a profile that extends a base, it merges the two together. Most fields fall into one of two patterns: List fields grow. Things like allowed paths, denied commands, and security groups are combined — the base list and your list are joined together. Duplicates are removed automatically. You can always add to a list, but you cannot remove something a base already includes. Single-value fields can be overridden. Things like binary, allow_gpu, workdir, and all security.* settings work differently: if you set them in your profile they replace the base value, and if you leave them out you inherit whatever the base had. A few fields have their own specific rules:

Extending multiple profiles

You can extend more than one profile at once by passing an array:
The profiles are merged left-to-right, then your profile is applied on top. So for any single-value field where the bases conflict, the rightmost one wins. List fields are always combined from all of them.

What if two bases share a common ancestor?

If you extend profiles A and B, and both of them extend a common base C, that’s fine. List entries from C end up in the final profile once — duplicates are removed automatically. For single-value fields, the same rightmost-wins rule applies: if A and B both set the same field, B’s value is what you get (assuming B is listed second in the array). The one thing to watch out for is if A and B intentionally set the same single-value field to different values, because only one can win and it might not be obvious which. If you’re unsure what you’re actually getting, run nono profile show my-profile to see the resolved result.

The default profile is always included

Every profile automatically gets the default built-in merged in, even if you don’t write extends at all. You don’t need to add "extends": "default" — it’s already there. If you want to remove a group that default includes, use groups.exclude:

Checking what your profile actually resolves to

Inheritance can stack up. Use these commands to see the final result after all merging is done:
Inheritance chains are capped at 10 levels. If you hit this limit, nono will fail with a ProfileInheritance error — that’s usually a sign the profile structure needs simplifying.

Generating a Profile

Use nono profile init to scaffold a new profile:
By default, the profile is written to ~/.config/nono/profiles/<name>.json. Use --force to overwrite an existing file.

Minimal Skeleton

A minimal skeleton includes the core sections most profiles need:

Full Skeleton

With --full, additional sections are included as empty stubs for all additive fields:
Fields that would override inherited behavior are intentionally omitted from the skeleton: network_profile (emitting null would clear an inherited proxy profile), open_urls (would replace inherited OAuth2 origins), allow_launch_services (would override inherited browser-opening permissions), and allow_gpu (would override inherited GPU access). Add these explicitly only when you intend to change the inherited behavior.

Validation

The init command validates inputs before writing:
  • Profile name must be alphanumeric with hyphens (no leading/trailing hyphens)
  • --extends target must exist as a preset, pack, or user profile
  • --groups are checked against the embedded policy groups
After creating a profile, validate it:

JSON Schema

nono ships with a JSON Schema for profile files. Use it for editor autocomplete and validation.

Exporting the Schema

Editor Integration

Export the schema locally, then add a $schema field to your profile for automatic validation in editors that support JSON Schema (VS Code, IntelliJ, Neovim with LSP, etc.):
In VS Code, you can also configure schema association in .vscode/settings.json:

Authoring Guide

nono includes an embedded authoring guide designed for LLM agents assisting with profile creation:
This outputs a comprehensive reference covering every profile section, field descriptions, common patterns, variable expansion, and validation workflow. It is useful when asking an LLM to help you write a profile — pipe or paste the guide into your conversation for context. To make the guide automatically available to your coding agent, add a line to your project’s instruction file (e.g., CLAUDE.md, AGENT.md, .cursorrules):

Workflow

A typical profile authoring workflow:
  1. Scaffold the profile:
  2. Edit the generated file in your editor (with schema autocomplete):
  3. Validate the profile:
  4. Inspect the resolved profile (after inheritance and group expansion):
  5. Compare against a baseline:
  6. Test the profile:
  7. Use the profile:

Available Groups

Use nono profile groups to list all available security groups. To see details for a specific group:
Groups are referenced by name in the groups.include field. See Profiles & Groups for the full group taxonomy and built-in group list.
The groups.include key was renamed from its former location under security in issue #594. The legacy key still deserializes with a deprecation warning; see nono profile guide for the full migration table. Legacy keys will be removed in v1.0.0.

Process and IPC Isolation

The security section controls process-level isolation knobs that are not filesystem path grants:
Use nono why --scope to inspect the effective scope policy for a profile:

Common Patterns

Agent with API Credentials

CI Build Environment

Override a Deny Rule

filesystem.bypass_protection only removes the deny rule. You must also grant access via filesystem.allow, filesystem.read, or filesystem.write (or the matching *_file variant) for the path to be accessible.

Suppressing Repeated Save Suggestions

Use filesystem.suppress_save_prompt for paths you intentionally do not want to grant, but also do not want to see in the save-profile prompt on every run:
This does not grant access and does not hide diagnostics. Suppressed denials still appear in the diagnostic footer annotated with [save skipped], making it clear why they are absent from the save prompt. It only suppresses matching save-profile suggestions. filesystem.ignore is accepted as an alias, but the canonical name is deliberately explicit so it is not mistaken for allowing the denied path. The post-run save prompt offers the same behavior interactively: choose suppress to save all listed denied-path suggestions here instead of adding them as read, read_file, allow, or allow_file grants.

Suppressing Expected macOS System-Service Diagnostics

Use diagnostics.suppress_system_services for macOS Seatbelt operations you intentionally deny but do not want to see in every diagnostic footer:
Common values include user-preference-read (CFPreferences probes from npm and similar tools) and forbidden-exec-sugid. This does not grant access and does not change sandbox enforcement — it only hides matching violations from post-run output and save-profile prompts. Values merge additively across profile inheritance, like other list fields. For path denials that should stay visible but skip save prompts, use filesystem.suppress_save_prompt instead. To disable all diagnostic footers, use --no-diagnostics.

Exclude Inherited Groups

Target Binary

User profiles can declare a binary field to specify the program that nono should execute. This makes the trailing -- <command> optional:
If both a profile binary and a CLI trailing command are provided, the profile binary takes precedence and a warning is emitted.

Restrictions

The binary field is only honoured for user-authored profiles — profiles loaded from a filesystem path (e.g. ./my-profile.jsonc) or from the user profile directory (~/.config/nono/profiles/). Pack and built-in profiles cannot set binary; the field is silently ignored for security reasons.

Inheritance

When profiles are composed via extends, the child’s binary overrides the parent’s. If the child does not specify binary, it inherits the parent’s value.

CLI Reference