# Permissions and safety

> Tool authorization pipeline: PreToolUse hooks, allow/deny/ask rules, remembered grants, permission modes (including bypassPermissions/dontAsk), safe-bash defaults, and interaction with sandbox.

- Repository: xai-org/grok-build
- GitHub: https://github.com/xai-org/grok-build
- Human docs: https://grok-wiki.com/public/docs/xai-org-grok-build-90205de50458
- Complete Markdown: https://grok-wiki.com/public/docs/xai-org-grok-build-90205de50458/llms-full.txt

## Source Files

- `crates/codegen/xai-grok-pager/docs/user-guide/22-permissions-and-safety.md`
- `crates/codegen/xai-grok-shell/src/util/config/permissions.rs`
- `crates/codegen/xai-grok-shell/src/util/config/resolve/tool_approvals.rs`
- `crates/codegen/xai-grok-config-types/src/permission.rs`
- `crates/codegen/xai-grok-hooks/src/dispatcher.rs`
- `crates/codegen/xai-grok-tools/src/lib.rs`

---

---
title: "Permissions and safety"
description: "Tool authorization pipeline: PreToolUse hooks, allow/deny/ask rules, remembered grants, permission modes (including bypassPermissions/dontAsk), safe-bash defaults, and interaction with sandbox."
---

Grok authorizes every model-requested tool call through a fixed pipeline in the workspace permission manager: **`PreToolUse` hooks** run first (explicit deny only blocks; failures are fail-open), then merged **allow / deny / ask** rules from CLI flags, native `[permission]` config, managed/enterprise layers, and Claude-compatible settings, then **remembered grants** (when enabled), then built-in **safe-bash / read-only auto-approvals**, and finally the **prompt policy** from the active permission mode (`default`, `dontAsk`, `bypassPermissions`, `acceptEdits`, `plan`, `auto`). OS-level sandbox profiles are a separate enforcement layer after approval.

## Authorization pipeline

When the model requests a tool, checks run in this order:

1. **`PreToolUse` hooks** — Hooks matching the tool name run sequentially. An explicit `deny` stops the call. An allow (or no matching hooks) does **not** skip later checks. Hook crashes, timeouts, missing commands, and malformed output are **fail-open**: the failure is logged and shown in the UI, and the call continues.
2. **Permission rules** (CLI `--allow` / `--deny`, `~/.grok/config.toml`, project `.grok/config.toml`, managed config, `.claude/settings.json` / `settings.local.json`)
   - Matching **`deny`** rejects immediately. Deny wins over every other rule action, regardless of source or file order.
   - Matching **`ask`** forces a user prompt (including for tools that would otherwise auto-approve).
   - Matching **`allow`** approves.
3. **Remembered grants** — Per-project “Always allow” / “never allow” entries saved from earlier prompts can satisfy an `ask` floor without re-prompting. Dangerous commands (below) still prompt. Grants are not consulted under always-approve mode.
4. **Built-in auto-approvals** — Read-only tools and a fixed set of read-only shell command prefixes run without prompting unless a deny/hook/ask rule blocks them.
5. **Prompt policy** — Driven by permission mode: prompt (`default`), auto-deny without a prior allow (`dontAsk`), auto-approve (`bypassPermissions`), auto-allow edits (`acceptEdits`), or classifier-based auto (`auto`).

**Always-approve (`bypassPermissions`)** short-circuits after step 2: deny rules, hooks, and shell-segment `ask` rules still apply; remembered grants (including remembered denials) are skipped; non-shell `ask` rules do not re-prompt.

```mermaid
flowchart TD
  T[Tool request] --> H[PreToolUse hooks]
  H -->|explicit deny| X[Reject]
  H -->|allow / fail-open| R[Merged allow/deny/ask rules]
  R -->|deny| X
  R -->|ask| G{Remembered grant?}
  R -->|allow| OK[Execute]
  R -->|no match| S[Safe tools / safe-bash]
  G -->|yes, not dangerous| OK
  G -->|no| S
  S -->|auto-approve| OK
  S -->|needs decision| P[Prompt policy]
  P -->|default: prompt| UI[User / headless cancel]
  P -->|dontAsk: deny| X
  P -->|bypassPermissions: allow*| OK
  UI -->|allow| OK
  UI -->|reject| X
```

\*Subject to deny rules, hooks, and shell `ask` segment checks.

## Built-in auto-approvals

These run without prompting in every mode (including `dontAsk`) unless a matching **deny** rule or hook blocks them. An **`ask`** rule still forces a prompt for file reads, searches, and shell commands.

### Read-only tools

Mapped as read-class access (no prompt by default):

| Surface | Notes |
|---------|--------|
| `read_file`, `list_dir` | Path-scoped reads |
| `grep` | Content search |
| `web_search` | Search queries |
| `todo_write` | Session task list |
| Subagent control | `get_command_or_subagent_output` / wait / kill task variants |
| Skills | Skill invocation |

### Safe-bash defaults

After shell parsing splits on `&&`, `||`, `;`, `|`, and newlines, **each non-setup segment** is checked with word-boundary prefix matching (so `ls` does not match `lsof`; `tr` does not match `truncate`).

**Filesystem (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:** `grep`, `rg` — except `rg --pre` / `rg --pre=…` (spawns a per-file preprocessor)

**Build / cluster read-only:** `cargo check`; `kubectl get`, `kubectl logs`, `kubectl describe`

**Not safe:** `tee` is intentionally excluded (can write stdin to arbitrary paths).

Chained example: `ls && rm -rf /` — `ls` is safe; `rm` is not. In `default` mode the chain prompts; under `dontAsk` it is denied unless an allow rule covers the dangerous segment.

Segment evaluation peels environment assignments and wrappers (`timeout`, `nice`, `ionice`, `chrt`, `stdbuf`, `env`) and recurses into `bash -c` scripts for **deny/ask** and safe-list checks. Wrappers such as `sudo`, `xargs`, and `nohup` are **not** peeled; write rules that name them explicitly. Unparseable constructs (subshells, `$(…)`, backticks, background `&`, complex control flow) fail closed to a single conservative prompt when Bash restrictions apply.

### Dangerous commands

These always need a prompt when covered only by a remembered grant or the safe-command list:

`rm`, `chmod`, `chown`, `chgrp`, `chattr`, `pkill`, `kill`, `killall`, `git push`

An explicit config **allow** rule still approves them; always-approve mode auto-approves them. Use **deny** rules for unconditional blocks. Treat `Bash(rm *)` allow rules with care.

## Permission modes

| Mode | Behavior | Typical use |
|------|----------|-------------|
| `default` | Prompt for anything not pre-approved or built-in-safe | Daily interactive work |
| `dontAsk` | Deny anything without an explicit allow rule or built-in auto-approval | Headless, CI, high security |
| `bypassPermissions` | Auto-approve after deny/hook/shell-ask checks | Trusted local machines |
| `acceptEdits` | Synthetic allow for file edits (`search_replace`, `write`, …) | Fast edit workflows |
| `plan` | Accepted for compatibility; plan sessions are a separate feature | Structured planning |
| `auto` | Classifier-based decisions for non-fast-path tools | Assisted auto-approval |

### Setting the mode

- **Claude-compatible:** `permissions.defaultMode` in `.claude/settings.json` (or nested discovery up to the repo root). Supported values: `default`, `acceptEdits`, `bypassPermissions`, `dontAsk`, `plan`, `auto`. A top-level `defaultMode` is accepted when the nested key is absent.
- **CLI:** `--permission-mode` applies `bypassPermissions` (always-approve) and `default`; an explicit flag wins over config. Passing `dontAsk`, `acceptEdits`, or `plan` to the flag is accepted but does **not** enable those policies — set them via `defaultMode`.
- **Native UI keys:** `[ui] permission_mode` with values `always-approve`, `auto`, `ask`, `default` (maps to ask). Legacy `approval_mode` and `yolo = true` still map to always-approve.
- **Headless (`-p`):** a tool call that would prompt is cancelled and reported to the model. Prefer `defaultMode: "dontAsk"` plus narrow `--allow` rules for automation.

### Locking off always-approve

Administrators can prevent always-approve (`bypassPermissions` / `--always-approve` / `/always-approve`) from being enabled:

```toml
# requirements.toml (prefer root-owned /etc/grok/requirements.toml for non-overridable lock)
[ui]
disable_bypass_permissions_mode = true
```

Do not use user-switchable `permission_mode` as a lock. Legacy `[ui] yolo = false` in **requirements** also disables the mode; in user `config.toml`, `yolo` remains a preference. Grok honors Claude managed **permission rules** but **not** Claude’s `disableBypassPermissionsMode` lock — use Grok `requirements.toml` instead.

`~/.grok/requirements.toml` is user-editable. For org enforcement users cannot remove, deploy `/etc/grok/requirements.toml`.

## Configuring permission rules

Rules from all sources merge into one set. Evaluation is by **severity**, not order: **deny > ask > allow**. A global deny cannot be overridden by a project allow.

### Scopes

| Scope | Location | Shared? |
|-------|----------|---------|
| Global | `~/.grok/config.toml` | No |
| Project (committed) | `<project>/.grok/config.toml` (every level from repo root to cwd) | Yes if committed |
| Project personal | `<project>/.claude/settings.local.json` | No (gitignore) |
| Managed | `/etc/grok/managed_config.toml`, `~/.grok/managed_config.toml` | Org-managed |
| Interactive grants | Internal state dir, per project launch cwd | No |

There is no native `config.local.toml`; use Claude local settings for personal project rules. Rules load at **session start**; changes apply on the next session.

### CLI flags

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

`--allow` and `--deny` may be repeated and are always enforced.

### Native TOML

Structured rules:

```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 *" },
  { action = "ask",   tool = "edit" },
]
```

Compact string arrays (same DSL as CLI / Claude settings):

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

Structured `tool` values: `bash`, `read`, `edit`, `grep`, `mcp`, `webfetch`, `websearch` (and `any` when omitted/default). Omitting `action` defaults to **deny** (safe default).

A catch-all `deny` on `bash` plus narrow `allow` rules does **not** implement “only git”: deny wins for all bash. For deny-by-default, use `defaultMode: "dontAsk"` or a `PreToolUse` allow-list hook.

Managed `allow` rules lose to user/project `deny` and `ask` (severity). Catch-all managed allows are ignored when always-approve is policy-locked off.

### Claude Code compatibility

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

Grok loads `~/.claude/settings.json`, `settings.local.json`, and project `.claude` settings walking up to the repo root. Notes:

- MCP rules must use `MCPTool(server__tool)` — the `mcp__server__tool` form never matches.
- Unknown tool names and parameter rules such as `Agent(model:opus)` are skipped with a warning.
- `permissions.additionalDirectories` is parsed but not supported.
- Interactive import: **Ctrl+I** (“Import Claude settings”).

## Rule matching reference

### Bash rules

- **Prefix:** character prefix match with no word boundary by default — prefer `Bash(git *)` over `Bash(git)` (the latter also matches `gitleaks`).
- **Glob:** full-command glob; `*` matches any characters including spaces; `?` and `[...]` supported.
- Case-sensitive; leading whitespace trimmed; no other normalization.
- Trailing `:*` becomes a plain prefix (`Bash(git commit:*)` → prefix `git commit`).

**Chains:**

| Action | Matching |
|--------|----------|
| `deny` / `ask` | Every segment **and** the whole string; one denied segment rejects the whole command |
| `allow` | Whole command string only — `Bash(git *)` allows `git status && rm -rf /` |

Pair narrow allows with denials for patterns you never want.

### Path rules (`Read`, `Edit`, `Grep`)

- `*` / `?` do not cross `/`; `**` does.
- Bare filenames match only that exact string; use `**/.env` for any depth.
- No special treatment for `//` or `~/` (literal glob text). Cover both absolute and relative forms for boundary rules.
- Paths match as invoked (no canonicalization for direct tool checks).
- `Read` rules also govern grep; `Grep(...)` matches only grep.
- `Read`/`Edit` **deny** rules also apply to paths touched by shell commands (`cat`, `sed`, …) with **symlink resolution** at the shell-access gate; direct `read_file` / `search_replace` checks do **not** resolve symlinks. For OS-wide enforcement, combine with the sandbox.

### MCP and WebFetch

- `MCPTool(linear__*)` matches Grok’s `server__tool` names (no `mcp__` prefix).
- `WebFetch(domain:example.com)` matches that host and subdomains (case-insensitive; ignores leading `www.`; no wildcards inside `domain:`).
- Bare URL patterns glob the full URL: `WebFetch(https://api.example.com/*)`.

### Tool name prefixes

Recognized: `Bash`, `Read` / `NotebookRead`, `Edit` / `Write` / `NotebookEdit`, `Grep` / `Glob`, `MCPTool`, `WebFetch`, `WebSearch`. Bare `*` matches every tool. Globs are not supported in the tool-name position.

## Interactive approvals and remembered grants

When a prompt is required, common choices:

- **Allow once** / **Reject once** (optional message back to the model)
- **Enable always-approve** for the session (policy pin may block this)
- **Allow all edits this session** (in-memory only; not persisted)

### Per-command “Always allow”

Off by default. Enable:

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

Or env: `GROK_REMEMBER_TOOL_APPROVALS=1`. Precedence: **requirements > env > config > managed > remote > default false**.

When enabled, prompts can offer always-allow / never-allow for a **short command prefix**, MCP tool, or web-fetch domain. Read-only commands remember the listed prefix (e.g. `git status`), not the full argument list. Dangerous commands re-prompt rather than reuse a remembered prefix.

Grants are stored under Grok’s home state directory, scoped to the project directory used at launch — not in the repo, not shared across projects, not for hand-editing. For reviewable team policy, put declarative rules in `.grok/config.toml`.

## PreToolUse hooks as a hard allow-list

Hooks run **before** permission rules. Use them when you need an allow-list that holds in **every** permission mode (including always-approve). Only an explicit hook `deny` blocks; hook **allow** still falls through to deny rules.

Hooks are **fail-open**. A security-boundary hook must handle its own errors and inspect every chain segment.

### Example: only `git` and `gh`

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

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

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

```bash
#!/bin/sh
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"

CMD=$(echo "$CMD" | sed 's/&&/;/g; s/||/;/g')
case "$CMD" in
  *'$('*|*'`'*|*'&'*|*'>'*|*'<'*) deny "Substitution, background, and redirection are not permitted" ;;
esac

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
```

```bash
chmod +x ~/.grok/hooks/git-gh-only.sh
```

Hook discovery, trust for project hooks, and other lifecycle events are covered on the hooks page.

## Example configurations

### Headless git / gh only

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

Add the `git-gh-only` hook to deny other bash. For deny-by-default on all tools:

```json
{ "permissions": { "defaultMode": "dontAsk" } }
```

### Read-only code reviewer

```toml
# .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(...)` allows for common commands (`git *`, `cargo test *`, `rg *`). Optionally enable `remember_tool_approvals` for personal friction reduction.

## Interaction with the sandbox

| Layer | Controls |
|-------|----------|
| Permissions + hooks | What the agent is **allowed to request** |
| Sandbox (`--sandbox`, profiles) | What the **process** can do after approval |

Recommended stack for untrusted checkouts:

1. `dontAsk` + narrow allow rules, and/or a restrictive `PreToolUse` hook  
2. `--sandbox strict` (or a custom deny-glob profile)  
3. Review project hooks and permission rules before trusting an unfamiliar repo (project allow rules apply without a separate trust prompt)

## TUI and session controls

- Permission decisions appear in the transcript / scrollback.
- `/always-approve` toggles always-approve when not policy-locked; other modes come from `defaultMode` / config.
- With remembered approvals enabled, prompts show per-command always/never rows.
- Manage hooks and plugins via `/hooks` and `/plugins` (on most terminals **Ctrl+L** opens Extensions; in VS Code / Cursor / Windsurf / Zed, `Ctrl+L` is mid-turn interject instead).

## Failure modes and practices

| Risk | Mitigation |
|------|------------|
| Allow rule matches only the whole chain string | Add segment-level **deny** / **ask** rules or a hook that inspects every segment |
| Safe-bash list treated as a security boundary | Treat it as convenience only; use `dontAsk` + denies + sandbox for security |
| Hook fails open under crash/timeout | Make hooks defensive; monitor UI hook failures; pair with deny rules |
| Project config from unknown source | Review `.grok/config.toml` and `.claude/settings.json` (and hooks) before work |
| `Bash(git)` matching `gitleaks` | Use `Bash(git *)` (trailing space + wildcard) |
| Symlink escape via shell | Combine path deny rules with sandbox; shell path gate resolves symlinks, direct tools may not |
| Always-approve in shared environments | Pin `disable_bypass_permissions_mode` in system requirements |

**Practices:** prefer narrow patterns; stack independent layers; test under `dontAsk` with representative commands; keep shared policy in committed config, not personal grants.

## Related pages

<CardGroup cols={2}>
  <Card title="Hooks" href="/hooks">
    PreToolUse / PostToolUse discovery, runners, and fail-open semantics.
  </Card>
  <Card title="Sandbox profiles" href="/sandbox">
    OS isolation profiles, --sandbox, deny globs, platform differences.
  </Card>
  <Card title="Headless mode" href="/headless-mode">
    -p runs, tool allow/deny lists, max-turns, CI patterns.
  </Card>
  <Card title="Configure Grok" href="/configure-grok">
    Config precedence, requirements, and grok inspect.
  </Card>
  <Card title="Configuration reference" href="/configuration-reference">
    Schema for [permission], [ui], sandbox, and env counterparts.
  </Card>
  <Card title="Plan mode" href="/plan-mode">
    Structured planning and how it interacts with tools and approvals.
  </Card>
  <Card title="Agent mode (ACP)" href="/agent-mode">
    ACP permission surface for IDEs and custom clients.
  </Card>
  <Card title="Subagents and personas" href="/subagents">
    Capability modes and isolation for child agents.
  </Card>
</CardGroup>
