---
title: "Permissions and modes"
---

# Permissions and modes

How the CLI decides whether a tool call runs, asks or is refused, from organisation governance through rules and modes, and how the sandbox and secret handling work.

Every tool call is checked before it runs. Your organisation's governance decides first; then the CLI's own rules and permission mode, which can only make the result stricter. When nothing gives an answer, you are asked.

## Modes

| Mode | Reads | File edits | Commands and network |
|---|---|---|---|
| `ask` (default) | Allowed in the workspace | Ask | Ask; recognisably read-only commands are allowed |
| `auto-edit` | Allowed | Allowed in the workspace | Ask |
| `read-only` | Allowed | Denied | Denied; the agent proposes a plan instead |
| `full-auto` | Allowed | Allowed | Allowed |

Switch with `--mode`, the `permissions.mode` setting, `/mode`, `Alt+M`, or `Tab` on an empty prompt. Administrators can remove `full-auto` with `permissions.disableFullAuto`.

Shell commands that are recognisably read-only (such as `ls`, `cat`, `git status`, `rg`), without redirections or command substitution, count as read-only tools.

In `read-only` mode, the agent submits a plan with `propose_plan`; approving it can switch the mode.

### Locations that always ask

Unless an explicit allow rule covers them, these always ask before being written, even in `auto-edit`: `.git/` internals, your global configuration folder, shell start-up files (`~/.bashrc`, `~/.zshrc`, and others) and `.neuralinverse/settings*.json`.

### Tools that always ask

A tool can be marked to ask for every call in every mode, including `full-auto`, over allow rules and approving hooks. "Always allow" answers count as "once". Deny rules and `read-only` mode still deny. `fw_flash` uses this: flashing hardware always needs a person. See [Firmware and hardware environments](https://neuralinverse.com/docs/cli/environments/#flashing-always-asks).

## Order of evaluation

1. **Organisation governance** (below). A deny is final.
2. **`tool.before` hooks** can block a call or pre-approve it. A pre-approval is still subject to deny rules and never covers an approval governance asks for.
3. **Deny rules.** If any matches, the call is refused. Nothing overrides a deny rule.
4. **Ask rules.** If any matches, you are asked, even in `full-auto`.
5. **Allow rules.** If one matches, the call runs.
6. **The mode's default.**

For file edits, the [checks gate](https://neuralinverse.com/docs/cli/checks/) also runs before the approval card or before the edit is applied; a change blocked by a signed control is refused in every mode.

## Organisation governance comes first

Every tool call is decided by organisation governance before the rules and modes: the same decision point, policy resolution and bundle verification as the Editor, the Agents app and their shared agent host. A call is decided as each of its actions: the tool, the shell command (after full parsing of chains, pipes, substitutions, wrappers and `sh -c`), every file read or written (also through symbolic links), every host contacted and the MCP server. Hook commands are decided the same way before they start.

| Governance | CLI rules and mode | Result |
|---|---|---|
| deny | anything | Refused |
| needs approval | deny | Refused |
| needs approval | allow or ask | You are asked, in every mode. No rule, hook or "always allow" can pre-approve it |
| allow | anything | The CLI's own decision |

When nobody can answer (`--print`, CI, a host without an approval interface), "needs approval" is a refusal, unless an approval of exactly that action, signed under the policy's approval rule, is in `.neuralinverse/records/`.

### Without an organisation policy

Built-in self-protection still applies: changing `.neuralinverse/` (policy and records), git hooks and `core.hooksPath`, CI configuration, shell profiles and governance data needs a person, and so does writing outside the workspace.

### Where the governance policy comes from

Strongest first:

1. Managed policy: the Editor's `/etc/vscode/policy.json` on Linux, or the CLI's managed settings `governance.policyBundle`, `governance.policyKeys` and `governance.organisation`.
2. The governance folder shared with the Editor: `~/.neuralinverse/governance/<quality>/policy.bundle.json`, with the home folder taken from the operating-system account, never from environment variables.
3. The repository: `.neuralinverse/policy.bundle.json`.
4. The last accepted bundle.

Every source must verify against the product's pinned keys plus managed keys. The anti-rollback counter (kept in the keychain, else a file in the governance folder) is shared with the Editor: an older bundle, another bundle with the same version or a deleted cache never loosens the policy. Past its offline grace, an expired policy locks agent sessions.

The organisation policy delivered by Govern sign-in does not yet carry these governance bundles. Deliver them through managed settings, the shared governance folder or the repository.

### Work items

If the policy requires an intent, an interactive session asks for the work item or change request at the start. Pass `--intent PROJ-123` (or set `intent` in user settings) for headless runs, which are refused without one. Accepted forms include `PROJ-123`, `#12`, `AB#7` and an https link.

### The governance command

`neuralinverse gov` runs the Editor's governance command line (`ni-gov`) with the same policy:

```text
neuralinverse gov gate commit|push|merge          git hooks and CI gates
neuralinverse gov release check <release>         release readiness
neuralinverse gov approvals list|verify|sign <id> approvals and signatures in .neuralinverse/records/
neuralinverse gov policy show|verify|keygen|sign  the policy in force; administrators' signing tools
neuralinverse gov hooks install|status            the governance git hooks (they run neuralinverse gov)
neuralinverse gov trace | hook | trailer          traceability; pre-tool hook; prepare-commit-msg
```

`--trusted-keys <file>` adds keys for CI runners, which have no managed policy; pass a list from configuration the pull request cannot change. Exit codes: `0` pass, `1` refused or failed, `2` error or tampering found. Run `neuralinverse gov` without arguments for its help.

## Rules

A rule is a tool name, optionally followed by a pattern in parentheses. Tool names may use `*`.

| Rule | Matches |
|---|---|
| `read_file` | Every call of the tool |
| `run_shell(npm test *)` | Commands starting with `npm test` |
| `run_shell(git diff)` | Exactly `git diff` |
| `edit_file(src/**)` | Writes anywhere under `src/`, relative to the workspace |
| `edit_file(/etc/**)`, `edit_file(~/notes/*)` | Absolute and home-relative paths |
| `read_file(.env)` | Any file named `.env`, at any depth |
| `web_fetch(domain:docs.rs)` | That host; `domain:*.example.com` includes subdomains |
| `mcp_github_*` | Every tool of the MCP server `github` |
| `repl(repl:python *)` | Python interpreter calls |
| `fw_flash` | The firmware flashing tool |

Rule families:

- `edit_file(...)` rules apply to every write tool: `write_file`, `edit_file`, `multi_edit`, `notebook_edit`.
- `read_file(...)` rules apply to every read tool: `read_file`, `find_files`, `search_text`, `list_dir`, `code_intel`. Files denied for reading are also left out of `search_text` and `find_files` results.

### Shell rules and compound commands

Compound commands are split on `;`, `&&`, `||`, `|`, new lines and command substitutions:

- an **allow** rule applies only if every part matches an allow rule and no part redirects output into a file. `run_shell(git diff *)` does not allow `git diff; rm -rf build`;
- a **deny** rule applies if any part matches, also looking through `sudo`, `env`, `timeout`, `sh -c '…'` and similar wrappers.

Shell deny rules are a guard rail, not a security boundary: a determined process can always find another way to run a program. Use the sandbox and least-privilege credentials for untrusted work.

### Where rules come from

| Source | How |
|---|---|
| Settings files | `permissions.allow`, `permissions.ask`, `permissions.deny`; combined across layers, never replaced |
| Command line | `--allow <rule>`, `--deny <rule>` (repeatable) |
| An approval card | "Always allow" saves a rule for this session, this project (`.neuralinverse/settings.local.json`) or all projects (`~/.neuralinverse/settings.json`) |
| In a session | `/rules` lists the active rules; `/rules allow <rule>` and `/rules deny <rule>` add one (`--project` or `--user` to save) |
| Managed policy | Deny and ask lists are added to everyone's; users can add restrictions but never remove them |

`permissions.additionalDirectories` (or `--add-dir`, or `/add-dir`) lets tools work in more folders.

In `--print` mode, anything that would ask is denied. Grant what the task needs with `--allow` or a mode.

## Sandbox

With `"sandbox": { "enabled": true }` (or `/sandbox` for one session), shell commands run inside an operating-system sandbox:

- **Linux:** bubblewrap. The file system is read-only except the workspace, the temporary folder and `sandbox.writablePaths`; the network is cut unless `sandbox.allowNetwork` is true.
- **macOS:** `sandbox-exec` with a generated profile enforcing the same policy.

If the sandbox is enabled but unavailable, commands run unsandboxed with a warning, or not at all when `sandbox.required` is true. `neuralinverse doctor` reports whether the sandbox is available. The interpreter tool runs in the same sandbox.

## Secrets in the tool environment and output

Processes the agent's tools start (shell commands, background jobs, the interpreter, verification and environment tools) do not inherit environment variables whose names look like secrets. The patterns, case-insensitive: `*TOKEN*`, `*SECRET*`, `*PASSWORD*`, `*PASSWD*`, `*PASSPHRASE*`, `*API_KEY*`, `*APIKEY*`, `*ACCESS_KEY*`, `*PRIVATE_KEY*`, `*SIGNING_KEY*`, `*ENCRYPTION_KEY*`, `*CREDENTIAL*`, `*COOKIE*`, `*CONNECTION_STRING*`, `*DATABASE_URL*`, `*_DSN`, `NPM_CONFIG__AUTH*`. Nor do they inherit the variables your model providers read their keys from.

Values in `settings.env` are applied afterwards, so a variable you set there on purpose still reaches the tools. Your organisation can add patterns and keep named variables with the managed setting `toolEnvironment` (ignored in user and project settings):

```jsonc
{ "toolEnvironment": { "secretNames": ["ACME_*"], "passThrough": ["GH_HOST"] } }
```

Before a tool's output reaches the model, secrets in it are replaced with `[REDACTED:<rule>]`, and the value of any secret-named variable of the CLI's environment with `[REDACTED:env:<NAME>]`. What you see in the terminal is the tool's own output.

## Reporting a security problem

Report vulnerabilities privately to security@neuralinverse.com, or through GitHub's private vulnerability reporting on the repository, with steps to reproduce, the version (`neuralinverse --version`) and your platform. Bypasses of permission rules or modes, sandbox escapes, path traversal, SSRF through `web_fetch`, credential exposure, hook or MCP handling flaws and unsafe defaults are in scope. A model doing something you allowed, documented `full-auto` behaviour, and shell deny rules evaded by a process you allowed to run are out of scope. Security fixes are released for the latest minor version.
