grok-build-upstream-mirror/crates/codegen/xai-grok-pager/docs/user-guide/22-permissions-and-safety.md
grokkybara[bot] 6e38642082 Synced from monorepo
Synced from monorepo

Changes:
- Refresh tool search when the managed MCP catalog is re-fetched
- Prevent duplicate leader process spawn and startup hang from stale leaders
- Document marketplaces, plugins, and organization controls
- Stamp session ID on image generation direct-to-API requests
- Fix auto mode blocked documentation
- Auto mode considers recent user intent
- Expose deploy archive, taken-down, limit, and in-progress reasons on the chat API
- Fail-closed auth refresh contract for shell clients
- Emit a chat-supplied per-session turn index in turn hooks
- Show bash mode chrome in minimal mode
- Add metrics for true-noop and stationarity stops
- Include voice interim text on prompt submit
- Silently end turn on true-noop thrash
- Quiet copy toast when clipboard delivery is confirmed
- Fix session fork truncating at the wrong prompt in rewound sessions
- Make the idle "still running" watcher cue clickable to open the tasks pane
- Default web search model to grok-4.5
- Let plugin subagents inherit parent MCP servers
- Gate no-op end-turn reminder on system reminders
- Add gateway bridge lifecycle telemetry
- Allow editing finalized text while voice is open
- Relocate token carrier to turn-commit events and plumb per-turn origin context
- Raise workflow scratch quotas and make failed runs resumable
- Workflows overlay: auto-progress phases, live agent status, and drop budget meter

Source-Revision: 9b8d35b46d959c042ea9aa31cbbebbd1f0c5c527
2026-07-24 16:59:42 +00:00

28 KiB
Raw Blame History

Permissions and safety

Control what Grok can access and do: permission modes, allow/ask/deny rules, hooks, and the optional OS-level sandbox.

  • Modes set how often Grok asks for approval (always-approve, auto, ask, and related).
  • Rules set which tools are allowed, asked about, or blocked within that baseline.

Permission modes

When Grok edits a file, runs a command, or calls an external tool, it may pause for approval. Permission modes control how often that happens.

Modes set a baseline. Allow, ask, and deny rules still apply on top of any mode.

Starting points

Situation Mode
Interactive TUI Default (ask), or auto for fewer prompts with background checks
Scripts, SDKs, CI, agent servers Always-approve; add deny rules or hooks for hard limits
grok -p "Run the tests" --always-approve
grok agent --always-approve stdio
grok agent --always-approve serve --bind 127.0.0.1:2419 --secret <token>

ACP clients can set "_meta": { "yoloMode": true } on session/new. See Agent mode.

Available modes

Mode What runs without asking Best for
default (ask) Read-only tools and built-in read-only shell commands Interactive day-to-day use
acceptEdits File edits without a prompt Local coding while you review diffs later
plan Accepted for compatibility; use plan mode for gated planning Claude-compatible settings
auto Work the safety check allows; other calls are blocked or escalated Interactive sessions that want fewer prompts
dontAsk Only pre-approved tools and built-in read-only handling Strict CI allowlists
bypassPermissions (always-approve) Tool calls in general (deny rules, hooks, and some shell ask rules still apply) Trusted automation and agent servers

Always-approve is the product name; config and Claude-compatible settings may use bypassPermissions for the same mode. Always-approve and auto are mutually exclusive (always-approve takes precedence when both are requested).

How to set the mode

Interactive TUI: Shift+Tab / Ctrl+O, /always-approve or /auto, or /settings (shortcuts, commands).

CLI:

grok --always-approve -p "Run the test suite"
grok --permission-mode auto
grok agent --always-approve serve --bind 127.0.0.1:2419 --secret <token>

Config:

[ui]
permission_mode = "always-approve"   # or "auto", "ask", …

Claude-compatible defaultMode in .claude/settings.json is also supported (see Claude-compatible settings). CLI overrides config for that process.

Always-approve

Skips ordinary permission prompts so tools run without waiting for a click. deny rules, hooks, and some shell ask rules still apply. Admins can lock the mode off (below).

Mechanism Example
CLI --always-approve (alias --yolo), or --permission-mode bypassPermissions
Config [ui] permission_mode = "always-approve"
Interactive /always-approve, Ctrl+O
ACP _meta.yoloMode: true on session/new

Always-approve with hard limits

Keep always-approve for automation, and add deny rules for paths or commands you never want run:

# project .grok/config.toml
[ui]
permission_mode = "always-approve"

[permission]
deny = [
  "Bash(rm -rf *)",
  "MCPTool(sales__delete_*)",
]
grok -p "Deploy the service" --always-approve --deny 'Bash(rm -rf *)'

Deny always wins over allow and over always-approves normal pass-through. See Configuring permissions.

Auto mode

Reduces interactive prompts by checking many tool calls before they run. Routine local work often proceeds; other calls may be blocked or escalated. In non-interactive sessions, a blocked call fails and is reported to the model (for example Auto mode blocked this action …). Behavior is the same for grok -p, agent stdio, and agent serve.

For automation that must run tools without interactive approval, use always-approve (and deny rules if you need hard blocks) rather than auto alone.

Disable always-approve (administrators)

Organizations can prevent always-approve from being enabled via CLI, TUI, or /always-approve. Set this in requirements.toml (user-level under ~/.grok/, or system-wide under /etc/grok/ for enforcement users cannot remove):

[ui]
disable_bypass_permissions_mode = true

Do not use permission_mode for this lock; that key is a switchable default. The legacy [ui] yolo = false key in requirements.toml also disables always-approve for compatibility.

Grok can still load Claude-style permission rules from managed settings; always-approve is locked with requirements.toml as shown above.


How a tool call is authorized

When the model requests a tool, the following checks happen in order:

  1. PreToolUse hooks. A hook can deny a tool call before any other check. A hook that allows a call does not skip the checks below; it only declines to deny. See 10-hooks.md.

  2. Permission rules (from configuration files or --allow/--deny flags)

    • A matching deny rule rejects the call. deny wins over every other rule.
    • A matching ask rule prompts you, including for file reads, searches, and shell commands that would otherwise be auto-approved.
    • A matching allow rule approves the call.
  3. Remembered grants. Per-command approvals you saved from earlier prompts apply here, scoped to the current project. An existing grant can satisfy an ask rule instead of re-prompting. Commands on the dangerous list prompt again rather than using a remembered prefix. See Interactive Approvals.

  4. Built-in auto-approvals. Read-only tools and a fixed set of read-only shell commands run without prompting (see below).

  5. Prompt policy (set by the permission mode): prompt you, auto-approve, or auto-deny the call.

Always-approve short-circuits this pipeline after step 2: deny rules, hooks, and ask rules that match a shell command's segments still apply, but remembered grants (including remembered "never allow" entries) are not consulted, and ask rules on non-shell tools do not prompt.


Operations That Never Prompt by Default

The operations below are treated as read-only and run without prompting, in every mode including dontAsk, unless a matching deny rule or a hook blocks them. An ask rule forces a prompt for file reads, searches, and shell commands (see How a Tool Call Is Authorized).

Read-Only Tools

  • read_file
  • list_dir
  • grep (content search)
  • web_search
  • todo_write
  • get_command_or_subagent_output / wait_commands_or_subagents / kill_command_or_subagent (subagent control)
  • Invoking skills

Read-Only Shell Commands

After splitting chained commands (on &&, ||, ;, and pipes), the following commands are recognized as read-only when they appear as the primary command. This list is word-boundary matched, so ls does not match lsof or less. (Your own Bash(...) rules match differently; see Rule Matching Reference.)

Filesystem (read-only viewing):

  • ls, cat, pwd, date, whoami, hostname, uptime, ps
  • head, tail, wc, sort, uniq, tr, cut

Git (read-only):

  • git status, git branch, git log, git diff, git ls-files, git show, git rev-parse

Search and inspection:

  • grep, rg (not rg --pre / rg --pre=…, which spawn a preprocessor per file)

Kubernetes (read-only):

  • kubectl get, kubectl logs, kubectl describe

Note: tee is not on this list because it can write its input to arbitrary files. cargo check is not on this list because it compiles and runs build.rs, proc-macros, and any build.rustc-wrapper from the repo (in Ask mode it therefore prompts; Auto mode may still heuristic-allow cargo as a project code runner). sort --compress-program=… (including unique long-option abbreviations), git -c / --config-env overrides, and a git command whose local/worktree config installs an executable hook (core.fsmonitor, a diff.*.command/textconv/external driver, or a shell alias.<safe-subcommand> = !…) raise a request-level floor and prompt rather than auto-approve, unless the user granted that exact full script or always-approve is enabled.

These checks apply per segment. In a command like ls && rm -rf /, the ls segment is recognized as read-only, but the rm segment is not on the list. In default mode the rm segment prompts; under dontAsk it is denied.



Configuring Permissions

Grok reads permission rules from three compatible sources. Rules from all sources are merged into one set; a rule's effect depends on its action (deny > ask > allow), not on which file it came from.

Where Permission Rules Live (Scopes)

Permission rules can be global (all projects), project-scoped (one repository), or personal to you within a project:

Scope File Shared with teammates
Global (all projects) ~/.grok/config.toml No
Project (committed) <project>/.grok/config.toml Yes (commit it)
Project (personal) <project>/.claude/settings.local.json No (gitignore it)
Interactive grants Stored internally by Grok, per project No

Notes on scoping:

  • Grok discovers a .grok/config.toml at every directory level from the repository root down to your working directory, so a subdirectory can add rules on top of the repo root's.
  • Rules from all scopes are merged into one rule set; deny > ask > allow applies across scopes, so a global deny cannot be overridden by a project allow.
  • Grok has no native config.local.toml. For personal, uncommitted rules in a project, use .claude/settings.local.json; Grok reads it directly (see Claude Code Compatibility).
  • Interactive "Always allow" decisions are stored outside the repository, scoped to the project (see Interactive Approvals).

To stop prompts for a specific command in one project, add a narrow allow rule to that project's .grok/config.toml (or .claude/settings.json):

[permission]
allow = ["Bash(cargo test *)", "Bash(npm run build)"]

This approves only the listed commands. Always-approve mode, by contrast, approves all tool calls.

1. CLI Flags

grok -p "Review the API changes" \
  --allow 'Bash(git *)' \
  --allow 'Bash(gh *)' \
  --allow 'Read' \
  --allow 'Grep' \
  --deny 'Bash(rm -rf *)'

--allow RULE and --deny RULE can be repeated and are always enforced.

Rule syntax examples:

  • Bash(git *) — any command starting with git
  • Bash(npm run build) — exact command (or prefix)
  • Bash(git commit:*) — the cmd:* suffix form, equivalent to prefix matching on git commit
  • Read(src/**) — read access under src/
  • Edit(**/*.rs) — edit any Rust file
  • Grep — all grep operations
  • MCPTool(my-server__*) — MCP tools from a specific server

See Rule Matching Reference for the exact matching semantics, including how chained commands and wildcards are evaluated.

2. Native Configuration (~/.grok/config.toml and .grok/config.toml)

[permission]
rules = [
  { action = "allow", tool = "bash", pattern = "git *" },
  { action = "allow", tool = "bash", pattern = "gh *" },
  { action = "allow", tool = "read" },
  { action = "allow", tool = "grep" },
  { action = "deny",  tool = "bash", pattern = "rm -rf *" },  # block a dangerous pattern
  { action = "ask",   tool = "edit" },
]

The structured tool field accepts the lowercase names bash, read, edit, grep, mcp, webfetch, and websearch, corresponding to the tool classes in Tool Names.

Because deny always wins, you cannot combine these allow rules with a catch-all deny on bash to mean "only allow git/gh"; a deny tool = "bash" rule would block git and gh too. For deny-by-default, use defaultMode: "dontAsk" in .claude/settings.json or a PreToolUse hook (below).

Rules from the global ~/.grok/config.toml and every project .grok/config.toml (from the repo root down to your working directory) are merged into one rule set, alongside any .claude/settings.json rules.

Managed configuration deployed by your organization also contributes [permission] rules: the system /etc/grok/managed_config.toml, and a user-level copy that Grok maintains automatically at ~/.grok/managed_config.toml. Managed rules merge like rules from any other source, with two properties specific to managed allow rules: your own deny and ask rules win over a managed allow (severity ordering), and a catch-all managed allow is ignored when always-approve is locked off. For rules that users cannot edit away, use the root-owned system /etc/grok/requirements.toml.

Permission rules from every source are read once, when a session starts. Changes apply to the next session.

The native [permission] section also accepts the compact allow / deny / ask string-array form, using the same rule strings as the --allow / --deny flags and .claude/settings.json:

[permission]
deny = [
  "Read(/Users/you/private/**)",
  "Edit(/Users/you/private/**)",
  "Bash(rm -rf *)",
]
allow = [
  "Bash(git *)",
  "Bash(gh *)",
]

deny always wins over allow (evaluation is deny > ask > allow), regardless of order or source. To block reads of paths outside your project at the OS level as well, combine deny rules with the strict sandbox profile (see 18-sandbox.md).

3. Claude Code Compatibility (.claude/settings.json)

Grok reads ~/.claude/settings.json and ~/.claude/settings.local.json, plus the project-level <project>/.claude/settings.json and settings.local.json (walking up to the repo root). The native .grok source for permission rules is config.toml, described in the section above.

Example:

{
  "permissions": {
    "defaultMode": "dontAsk",
    "allow": [
      "Read",
      "Grep",
      "Bash(git *)",
      "Bash(gh *)"
    ],
    "deny": [
      "Bash(rm -rf *)"
    ]
  }
}

Supported defaultMode values include default, auto, acceptEdits, bypassPermissions, dontAsk, and plan. Grok reads defaultMode from its canonical location under permissions; a top-level defaultMode is also accepted when the nested key is absent.

permissions.allow, permissions.deny, and permissions.ask entries are translated into native rules and then matched with the semantics in the Rule Matching Reference. Translation notes:

  • Rules for MCP tools must use the MCPTool(server__tool) form; the mcp__server__tool form never matches (see MCP Rules).
  • Rules naming an unrecognized tool, and parameter rules such as Agent(model:opus), are skipped with a warning rather than failing the load.
  • permissions.additionalDirectories is parsed but not supported.

You can import existing Claude settings interactively with Ctrl+I ("Import Claude settings").


Rule Matching Reference

This section defines exactly how rules are matched.

Bash Rules

A Bash(...) pattern matches a command in either of two ways:

  • Prefix: the command starts with the pattern text, compared character for character. There is no word-boundary requirement, so Bash(git) matches gitleaks as well as git status. Include a trailing space and wildcard (Bash(git *)) to require the prefix to be a whole word.
  • Glob: the pattern matches the whole command as a glob. * can appear at any position and matches any characters, including spaces and slashes, so Bash(git * main) matches git checkout main. ? and [...] are also supported.

Matching is case-sensitive. Leading whitespace in the command is trimmed before matching; nothing else is normalized.

A trailing :* suffix on a Bash rule is stripped to a plain prefix: Bash(git commit:*) becomes prefix git commit. Because prefixes have no word boundary, a deny written as Bash(sed:*) also blocks commands such as sed-custom.

Chained commands. Grok parses each command like a shell and splits it on &&, ||, ;, |, and newlines. The rule actions treat segments differently:

  • deny and ask rules are checked against every segment, and against the whole string. One denied segment rejects the entire command.
  • allow rules are checked against the whole command string only. Bash(git *) therefore auto-approves git status && rm -rf /, because the full string starts with git . Pair narrow allow rules with deny rules for the patterns you want to block.

Commands that cannot be split into simple segments (subshells, command substitution $(...), backticks, background &, control flow) prompt as a single unit when Bash restrictions are configured.

Segment-level checks (deny and ask rules, remembered grants, and the read-only command list) strip environment-variable prefixes such as RUST_LOG=debug, and peel a fixed set of process wrappers (timeout, nice, ionice, chrt, stdbuf, env) so that deny and ask rules match either the wrapped or the inner command. deny and ask rules are also checked inside inline scripts passed to bash -c. Other wrappers, including sudo, xargs, and nohup, are not peeled; write rules that include them explicitly. allow rules do not get this treatment: they match the command string as written, so a leading environment assignment or wrapper keeps an allow rule from matching and the command prompts instead.

Dangerous Commands

A built-in list (rm, chmod, chown, chgrp, chattr, pkill, kill, killall, git push) prompts even when a segment is covered by a remembered command prefix or the read-only command list. An explicit allow rule in configuration does approve them, and always-approve mode auto-approves them like any other command; use deny rules to block them unconditionally. Review rules like Bash(rm *) carefully before adding them as allow rules.

Read, Edit, and Grep Rules

Path patterns are globs matched against the path string the tool was called with:

  • * and ? do not cross /; ** does. Read(src/*) matches src/main.rs but not src/nested/mod.rs; use Read(src/**) for the whole tree.
  • A bare filename matches only that exact string. Use **/.env to match .env at any depth.
  • There are no anchor prefixes: a leading // or ~/ in a pattern is treated as literal glob text. Write absolute-path patterns or **/ patterns instead.
  • Paths are matched as given, without canonicalization. Whether a path is absolute or relative depends on how the tool was invoked, so patterns intended as boundaries should cover both forms (for example both /repo/secrets/** and secrets/**).
  • Read rules also govern grep searches; Grep(...) rules match only grep.

Read and Edit deny rules additionally apply to file paths that shell commands touch (for example cat or sed on a denied path), including literal inline scripts passed to bash, sh, dash, zsh, or ksh with -c; that shell-level check also resolves symlinks. The direct read_file/search_replace tool checks do not resolve symlinks. For OS-level enforcement that covers every process, combine deny rules with the sandbox (18-sandbox.md).

MCP Rules

MCPTool(...) patterns match the full Grok tool name in server__tool form, with glob support: MCPTool(linear__*) matches every tool from the linear server. Grok tool names carry no mcp__ prefix, so a rule written as mcp__server__tool never matches an MCP call; write MCPTool(server__tool) instead.

WebFetch Rules

  • WebFetch(domain:example.com) matches that host and every subdomain (api.example.com), case-insensitively, ignoring a leading www.. Wildcards are not supported inside domain: patterns.
  • A pattern without the domain: prefix globs against the entire URL: WebFetch(https://api.example.com/*).

Tool Names

Recognized tool names: Bash, Read (and NotebookRead), Edit (and Write, NotebookEdit), Grep (and Glob), MCPTool, WebFetch, WebSearch. A bare * rule matches every tool. Globs are not supported in the tool-name position.

Rules naming an unrecognized tool (for example Agent(model:opus)) are skipped with a warning rather than failing the load.

Evaluation Order

Rules from every source are merged into one set and evaluated by severity, not order: any matching deny rejects, otherwise any matching ask prompts, otherwise any matching allow approves. When no rule matches, the request falls through to the built-in auto-approvals and then the prompt policy, as described in How a Tool Call Is Authorized.


Interactive Approvals and Where They Persist

When a tool call requires approval, the permission prompt offers these choices:

  • Allow once: approve this single invocation.
  • Reject once: reject it, optionally with a message back to the model.
  • Enable always-approve mode: approves all future tool calls, not just the one being prompted.
  • Allow all edits this session: shown for file edits. This grant is held in memory only and does not survive a restart.

Per-Command "Always Allow"

A narrower set of options remembers just the specific command, MCP tool, or web-fetch domain being prompted, for example "Always allow cargo test". These rows are off by default. Enable them with:

# ~/.grok/config.toml
[ui]
remember_tool_approvals = true

With the gate enabled, prompts gain:

  • Always allow: <command>, which persists an allow for the command prefix.
  • A matching "never allow" row, which persists a deny the same way.
  • Equivalent "always allow" rows for MCP tools and web-fetch domains.

The remembered prefix is limited to a short form of the command: read-only commands persist just their listed prefix (for example git status, not the full argument list), and other commands persist a short leading prefix. The prompt shows exactly what will be remembered before you confirm. Commands on the dangerous list prompt again rather than using a remembered prefix.

Persistence Is Per Project

Interactive grants are stored in Grok's own state directory under your home directory, scoped to the directory you launched Grok from. A grant made in one project never applies in another, grants are not written into the repository, and they are not meant to be hand-edited.

Interactive grants are personal, per-machine state. For an allowlist you can review in code review and share with teammates, use declarative rules in the project's .grok/config.toml instead.


Restricting Bash to Specific Commands with a Hook

A PreToolUse hook can enforce an allow list on the Bash tool that applies in every permission mode. Hooks are evaluated before the permission system; a hook deny stops the call, and a hook allow falls through to the normal permission checks (so your deny rules still apply).

Note: Hooks fail open. If a hook script crashes, times out, or is missing, the tool call proceeds as if the hook had allowed it, and the failure is reported in the UI. A hook used as a security boundary must handle its own errors, and must account for chained commands, as the example below does. See 10-hooks.md.

Example: Allow Only git and gh

~/.grok/hooks/git-gh-only.json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "git-gh-only.sh",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

~/.grok/hooks/git-gh-only.sh

#!/bin/sh
# Allow only git and gh commands, including within chained commands.

set -eu

deny() {
  echo '{"decision": "deny", "reason": "'"$1"'"}'
  exit 2
}

INPUT=$(cat)
CMD=$(echo "$INPUT" | jq -r '.toolInput.command // empty')

[ -n "$CMD" ] || deny "Empty command is not allowed"

# Normalize '&&' and '||' to ';' so chains can be checked segment by
# segment, then reject constructs this script cannot inspect.
CMD=$(echo "$CMD" | sed 's/&&/;/g; s/||/;/g')
case "$CMD" in
  *'$('*|*'`'*|*'&'*|*'>'*|*'<'*) deny "Substitution, background, and redirection are not permitted" ;;
esac

# Split on the separators and require every segment to start with git or gh.
echo "$CMD" | tr ';|' '\n\n' | while IFS= read -r SEGMENT; do
  SEGMENT=$(echo "$SEGMENT" | sed 's/^[[:space:]]*//')
  [ -n "$SEGMENT" ] || continue
  case "$SEGMENT" in
    git\ *|git|gh\ *|gh) ;;
    *) deny "Only git and gh commands are permitted. Blocked segment: $SEGMENT" ;;
  esac
done
chmod +x ~/.grok/hooks/git-gh-only.sh

This hook denies every Bash command unless each chained segment starts with git or gh, and rejects command substitution, backgrounding, and redirection outright because it cannot verify what they execute. It works in every permission mode.

For hook installation, the JSON format, the trust model for project hooks, and other events, see 10-hooks.md, which also contains a complementary "block dangerous patterns" example.


Example Configurations

Headless git and gh Only (CI and Automation)

grok -p "Implement the feature using only git and GitHub CLI" \
  --allow 'Read' \
  --allow 'Grep' \
  --allow 'Bash(git *)' \
  --allow 'Bash(gh *)'

Install the git-gh-only hook above to deny every other Bash command. For deny-by-default on all tools, also set {"permissions": {"defaultMode": "dontAsk"}} in .claude/settings.json.

Read-Only Code Reviewer

# .grok/config.toml
[permission]
rules = [
  { action = "allow", tool = "read" },
  { action = "allow", tool = "grep" },
  { action = "deny",  tool = "edit" },
  { action = "deny",  tool = "bash" },
]

Interactive Development

Use default mode plus narrow Bash(...) allow rules for the commands you run most (git, cargo test, rg, and similar).


Combining with the Sandbox

Permissions control what the model is allowed to request. The OS-level sandbox (see 18-sandbox.md) controls what the process can do even after a command is approved.

Recommended combination for untrusted code:

  1. dontAsk plus narrow allow rules, or a restrictive hook
  2. --sandbox strict or a custom profile
  3. Project trust plus review of any SessionStart hooks

Managing Permissions in the TUI

  • Permission decisions appear in the transcript.
  • The /always-approve command toggles always-approve mode; other modes are set through defaultMode (see How to set the mode).
  • With [ui] remember_tool_approvals = true, permission prompts include per-command "Always allow" options that persist for the current project only. See Interactive Approvals.
  • To manage hooks and plugins, run /hooks or /plugins (on most terminals, Ctrl+L also opens the Extensions modal; on VS Code, Cursor, Windsurf, and Zed, Ctrl+L is mid-turn interject instead). See 10-hooks.md.

Best Practices

  1. Prefer narrow patterns. Bash(git *) grants less access than a bare Bash allow rule.
  2. Combine layers. dontAsk, narrow allow rules, a restrictive hook, and the sandbox each restrict independently.
  3. Review project configuration from unfamiliar sources. Project permission rules in .grok/config.toml and .claude/settings.json, including allow rules, apply without a separate trust prompt. Review them, and any project hooks, before working in an unfamiliar checkout (see the security notes in 10-hooks.md).
  4. Test your policy. With defaultMode: "dontAsk" set (or your PreToolUse hook installed), run representative commands and confirm what is blocked.
  5. Treat the read-only command list as a convenience, not a security boundary.

See also