Permissions & hooks
Permission gate
The permission gate checks every mutating tool, such as write_file,
edit_file and run_command, before it runs.
| Mode | Behavior |
|---|---|
ask (default) | Shows a diff preview of up to 40 lines for a file edit, or the full shell command with line breaks shown as ⏎. Then it asks: yes, no, allow for this session, always allow, or deny with feedback. bwn remembers "always allow" for this project only. The answer covers the program, or the program and its subcommand, such as ls or git status. Some answers cover only that exact command: shells and interpreters like sh or python3, programs like rm, chmod and kill, and git commands that discard work, such as git reset --hard. The prompt names the scope. /permissions reset forgets these answers. Only a single plain command can skip the prompt. Anything chained, piped, redirected or substituted always asks. Web fetches and searches ask once per host and port, and "fetch *" in allowed_commands allows every host. |
accept-edits | Applies file edits inside the workspace, and inside folders added with --add-dir, without asking. Commands, deletions, network access, sensitive paths and any change inside .git still ask. |
auto | Runs without asking. Dangerous commands, sensitive paths and calls an ask rule covers still ask. In WSL, so do file changes on a Windows drive and commands that name a path under /mnt/<drive>/. |
readonly | Refuses every change and never shows a prompt that could allow one. Read-only shell commands such as grep, cat and git log still run. Web fetches, searches and reads of sensitive paths still ask. |
Allow, ask and deny rules under permissions in settings.json apply
before the mode. A rule such as run_command(git push*) names a tool and a pattern.
A deny rule refuses in every mode. An ask rule asks even under auto. An
allow rule skips the mode's prompt, but readonly still refuses every change, and
sensitive paths and dangerous commands still ask. Deny beats ask, and ask beats allow.
bwn checkpoints a file before write_file, edit_file, multi_edit, move_path, remove_path or create_docx changes it, and /undo reverts the last agent turn's edits. bwn doesn't checkpoint what shell commands, apply_patch or str_replace_editor change, so /undo doesn't restore those files. Checkpoints can't hold files over 2 MiB or not in UTF-8, and /undo leaves those files alone rather than clobbering them. If the turn changed one of those files, /undo changes nothing and names the files that blocked it. If you edited a file after the agent did, /undo asks before overwriting your changes. Answer n to keep your version and restore the rest. The file tools refuse writes outside the workspace and the folders added with --add-dir, in every mode. .git is not a sensitive path, so auto doesn't ask before changes inside it. accept-edits asks before any change inside .git, and with the sandbox on, shell commands can't write to it.
Sensitive paths always ask, even under auto, unless one of
your own hooks allows the call: .env files,
~/.ssh, ~/.aws, .kube, .docker/config.json,
.config/gh, .netrc, .npmrc, .pypirc,
.git-credentials and similar. The check follows symlinks and covers shell
arguments too, so cat ~/.aws/credentials asks. Search tools skip credential
directories.
Model replies and tool output can't send control sequences to your terminal. bwn shows an
escape character as a visible ␛.
Project settings
A repository can ship .buildwithnexus/settings.json and
settings.local.json. Until you trust them, they can set these harmless keys:
reasoning_effort, temperature, max_tokens,
instruction_files, images, notify and
idle_notify_secs. They can also tighten permission and
sandbox, lower max_budget_usd, and add ask and deny rules. Anything
else, such as model, base_url, a looser permission, allow rules, hooks
or MCP servers, needs your approval at one prompt:
⚠ /home/you/src/app has project settings that can run commands, send your API key elsewhere, loosen approvals, or add to the system prompt:
.buildwithnexus/settings.json:
base_url: "https://llm.example.com/v1" — your requests and API key go to this address
hook PreToolUse Bash: ./scripts/guard.sh
permission: "auto" — edits and commands run without asking you
(also trusts these project files as they are now; editing one asks again: ./scripts/guard.sh)
Trust? y everything above · e all except base_url and permission · n nothing [N]:
The answer defaults to No, which keeps your own settings in effect. When the file sets
base_url or permission beside other keys, e trusts
everything except those keys. bwn records trust for each file as a digest of that file and of
the project files its hooks and MCP servers run. If you edit any of them, bwn asks again.
Headless runs never prompt. They ignore those keys and print one warning. To trust them for
one run, pass --trust-project <digest> with the digest that
buildwithnexus trust --print prints. When the settings set base_url,
or a permission looser than readonly, also name those keys with
--trust-project-allow. A trusted project
can add MCP servers but never change one you defined. Commands like
/model and "always allow" save only their own key to your user settings, never the
project's.
Permission modes and rules, the sensitive-path prompt, and checkpoints reduce mistakes, but they are not OS-level isolation. An approved command runs with your user's full permissions. Checkpoints can undo file edits in your working tree but not network calls, pushed commits, published packages, or other external effects. Run untrusted work in a container or VM.
Sandbox
You can opt in to OS-level confinement for shell commands. Set
"sandbox": "auto" in settings, pass --sandbox auto, or run
/sandbox auto. Every run_command, !cmd,
check_work, start_server and python_tool call then runs
inside bwrap on Linux or sandbox-exec on macOS. The filesystem is read-only except for your
workspace, the folders added with --add-dir, and the temp directories. On Linux,
/tmp is a fresh directory that bwn discards when the command exits.
~/.buildwithnexus stays read-only, and so do the .git and
.buildwithnexus of the workspace and of each added folder. With or without the
sandbox, these commands don't inherit variables whose names end in _API_KEY or
_API_TOKEN, or a provider's key variable such as HF_TOKEN. To keep
one, list it in shell_env_passthrough. Sandboxed commands also lose
SSH_AUTH_SOCK and the D-Bus session.
When no backend works, "require" refuses to run commands, and
"auto" runs them unconfined with a one-time notice.
"sandbox_network": false also cuts off network access. Sandboxed commands show
[sandboxed] in their header. /sandbox status and bwn doctor
report whether a backend is available. The sandbox does not limit what commands read, and it
does not confine the agent's own file tools, hooks or MCP servers. The file tools already
refuse writes outside the workspace and the added folders. Windows and WSL have no backend yet.
Hooks
Hooks run your own commands at the same lifecycle points as Claude Code. Configure them in
~/.buildwithnexus/settings.json for your user or in
.buildwithnexus/settings.json for a project. Project hooks run only after you
trust the project settings. They can deny a tool but
never grant one, so a hostile repo can't use them to unlock anything. In PLAN,
BRAINSTORM and readonly, a hook can deny a call but never allow a write.
{
"hooks": {
"PreToolUse": [
{ "matcher": "run_command",
"hooks": [{ "type": "command",
"command": "echo 'no shell on main' >&2; exit 2" }] }
]
}
}
The events are SessionStart and SessionEnd (once per process),
UserPromptSubmit, PrePrompt (before every model request in a build turn),
PreToolUse, PermissionRequest (when bwn is about to ask you to approve
a call), PostToolUse, PostResponse, OnError,
Stop (after every turn in every mode), SubagentStop,
PreCompact (before bwn summarizes the conversation) and Notification.
A Notification hook fires when an approval or a question waits for you, when a turn
ends, and when the prompt sits idle for idle_notify_secs, 60 seconds by default.
It fires whether or not bwn shows a desktop notification, and headless runs fire it when each
turn ends. A PreToolUse hook that exits with
2, or returns permissionDecision: "deny", blocks the tool, even under
auto. So does a PreToolUse hook that times out, can't start or is
killed. Any other non-zero exit shows a warning and the call goes on, unless the hook sets
"on_error": "deny", which makes that exit block the call too. An
"allow" from one of your own hooks skips every prompt, even for sensitive paths,
dangerous commands and ask rules. Only deny rules still apply. A PermissionRequest hook can answer allow, deny or ask in your place,
and only your own hooks can allow. Each hook gets 10 seconds by default. Set
"timeout" on a hook to change it.
A matcher is an exact tool name, an a|b list such as "Edit|Write", or
a glob such as "*_file" or "mcp__*". For PreCompact, the
matcher matches the trigger, auto or manual. For
Notification, it matches the notification type, one of
permission_prompt, question, idle_prompt or
done. Claude Code's tool names, such as
Bash, Edit and Write, match the bwn tools that do the same
thing. A hook reads a JSON payload on stdin, and a hook with
"type": "http" gets the same JSON as a POST body. It has
hook_event_name, session_id, transcript_path,
permission_mode and cwd, plus the event's own fields, such as
tool_name, tool_input and tool_response. The field names
match Claude Code's. bwn also runs scripts placed in
~/.buildwithnexus/hooks/<Event>/ for that event, with no settings entry.
MCP servers
bwn is a Model
Context Protocol client over stdio and Streamable HTTP. Servers listed under
mcp_servers in user or project settings.json connect in the background
when you send your first prompt. Each one logs mcp: <name> connected, N tools.
bwn offers each discovered tool to the model as mcp__<server>__<tool>, with
the server's own description and schema. These tools pass through the permission gate like any
mutating tool. bwn honours a server's readOnlyHint only when you set
"trust_read_only_hints": true on that server, because the hint is the server's own
claim. If a server hangs or exits, bwn reports it once and drops its tools until you run
/mcp reload.
{
"mcp_servers": {
"fs": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."] },
"remote": { "url": "https://mcp.example.com/mcp", "headers": { "Authorization": "Bearer …" } }
}
}
/mcp servers, status, tool counts
/mcp <name> that server's tools
/mcp add <name> <command> [args...] stdio server
/mcp add <name> --url <url> [--header K=V]
/mcp remove <name> · /mcp reload
/mcp login|logout <name> OAuth sign-in and sign-out, http servers
bwn mcp list|add|remove the same, for scripts
An http server that asks for an OAuth sign-in shows as needs login.
bwn mcp login <name> or /mcp login <name> signs in
through the browser and saves the token in
~/.buildwithnexus/mcp-auth/<name>.json. In headless runs, servers connect
before the first request. bwn waits once for all of them. The wait is the largest
timeout_secs among the servers, and a server without one counts as 5 seconds.
Then bwn skips any server that isn't ready. bwn doctor connects to every configured
server and reports the result.