Permissions and security

Control which tools can run, define permission modes, and prevent sensitive values from reaching models or stored output.

Permission rules

{
  "permissions": {
    "allow": ["bash(git status*)"],
    "ask": ["bash(git push*)"],
    "deny": ["bash(rm -rf /*)"]
  }
}

allow, ask, and deny are arrays of permission rules. A rule is either a tool name, such as bash, or a tool and subject pattern, such as bash(git status*) or write(src/*). * matches any sequence of characters and also works in the tool name, so mcp__github__* matches every tool from that MCP server and * alone matches every tool. Deny rules are evaluated before all other permission rules.

Chained commands are evaluated per segment, so git status && rm /etc/hosts asks even though git status alone would not. Commands using substitution or grouping that cannot be split safely always ask. Built-in risky-command rules are ordinary rules, so configuration can override them. For example, "allow": ["bash(curl *)"] stops curl from asking.

Built-in modes

Xal ships three modes, cycled while the session is idle with the session.next-mode shortcut, Shift+Tab by default:

Plan mode

/plan [prompt] enters plan mode and can submit the planning request in the same command. The agent grounds repository facts with read-only tools, asks structured questions only for material choices that cannot be discovered, and produces a self-contained implementation plan. submit_plan saves the complete Markdown as the session-local plan.md, renders it for review, and offers approval or revision. Free-form review input becomes revision feedback, and each resubmission replaces the complete proposal.

Approval restores the writable permission mode that was active before planning and begins implementation with the approved plan in context. If the prior mode was read-only, approval uses normal. A dismissed review leaves plan mode active and waits for new direction. User-driven mode changes are refused while a turn, approval, or input request is active so one turn cannot silently cross permission boundaries.

Custom modes

Custom modes are defined under modes and appear in the TUI mode cycle and --mode:

{
  "modes": {
    "paranoid": { "ask": ["*"], "guidance": "Every action needs confirmation." },
    "trusting": { "base": "normal", "allow": ["bash(curl *)", "write(/*)"] }
  }
}

base selects the built-in mode a custom mode behaves like. It defaults to normal; plan inherits read-only behavior and yolo inherits ask-skipping. allow, ask, and deny are mode-scoped rules that apply only while the mode is active. They sit above global permissions rules and below approvals remembered from the approval prompt. guidance replaces the mode instructions shown to the model.

Built-in mode names cannot be redefined. A session restored with a mode that no longer exists falls back to normal.

Redaction

OptionTypeDefaultDescription
valuesstring[][]Exact sensitive values to replace.
environmentstring[][]Environment variable names whose current values should be used.
{
  "redaction": {
    "environment": ["MY_PROJECT_TOKEN"],
    "values": ["sensitive-literal"]
  }
}

Matches are case-sensitive and normally become [REDACTED] before content reaches a model, session or prompt-history storage, tool-output artifacts, CLI output, or the TUI. Xal chooses a safe alternate marker when a configured value is part of that text. Provider access tokens, refresh tokens, and API keys in the credential store are included automatically. Prefer environment for additional values so the secret itself does not need to appear in a configuration file.

Custom plugins can add values from their own credential sources with ctx.registerSecrets.