Skip to main content
Claurst uses a layered permission system that controls which tool calls can execute automatically and which require user approval. The system is deliberately conservative by default and supports several operational modes.

Permission modes

The active mode is set via --permission-mode on the CLI or the permission_mode field in settings.
ModeRust variantBehaviour
Default (interactive)PermissionMode::DefaultRead-only operations run automatically. All write and execute operations prompt the user.
AcceptEditsPermissionMode::AcceptEditsAll operations — including writes and edits — run automatically.
BypassPermissionsPermissionMode::BypassPermissionsEvery permission check returns Allow immediately; no prompts are shown. Use only in trusted automation contexts.
PlanPermissionMode::PlanRead-only planning mode. Only tools with is_read_only == true are permitted; all others are denied.
The original TypeScript codebase contains an additional mode called YOLO that, despite its name, actually denies all non-read-only operations. The name is counterintuitive. In Claurst the equivalent of “allow everything” is BypassPermissions, not a mode called YOLO.
The auto permission mode referenced in the TypeScript source uses an ML-based transcript classifier to approve tool calls automatically. In Claurst this maps to AcceptEdits for tool execution purposes — the classifier infrastructure is not part of the clean-room implementation.

Permission categories

Every permission check is associated with one of these categories, which map directly to the tool types that trigger them:
CategoryTools that use it
BashBashTool, PowerShellTool
FileReadFileReadTool, GlobTool, GrepTool
FileEditFileEditTool, NotebookEditTool
FileWriteFileWriteTool
WebFetchWebFetchTool, WebSearchTool
MCPListMcpResourcesTool, ReadMcpResourceTool, and dynamically registered MCP tools
SandboxSandboxed execution contexts

How the AutoPermissionHandler works

AutoPermissionHandler is the non-interactive handler used in headless and scripted runs. It implements the PermissionHandler trait:
impl PermissionHandler for AutoPermissionHandler {
    fn check_permission(&self, tool_name: &str) -> PermissionDecision {
        match self.mode {
            PermissionMode::BypassPermissions => PermissionDecision::Allow,
            PermissionMode::AcceptEdits      => PermissionDecision::Allow,
            PermissionMode::Plan             => {
                if self.is_read_only(tool_name) {
                    PermissionDecision::Allow
                } else {
                    PermissionDecision::Deny
                }
            }
            PermissionMode::Default => {
                if self.is_read_only(tool_name) {
                    PermissionDecision::Allow
                } else {
                    PermissionDecision::Deny
                }
            }
        }
    }
}
In the interactive TUI, the PermissionHandler surfaces a dialog to the user and waits for a y/n response before returning a decision.

Layered settings

Permission rules are stored in JSON settings files. The files are read in priority order — the highest-priority source wins:
1

Managed / enterprise settings (read-only)

Deployed by an organisation’s device management system. Users cannot override these rules. Equivalent to a policy layer.
2

Local project: .claude/settings.local.json

Machine-local settings for the current project. This file should be added to .gitignore. Suitable for developer-specific overrides that should not be shared with the team.
3

Project: .claude/settings.json

Shared project settings checked into source control. Suitable for project-wide permission rules that every contributor should use.
4

Global: ~/.claude/settings.json

User-level settings applied to all projects. Loaded by Settings::load() in cc-core.
The Settings struct in cc-core handles loading and saving:
// Load from ~/.claude/settings.json; returns default on missing file
pub async fn load() -> Result<Settings>

// Serialize to JSON and write; creates parent directories
pub async fn save(&self) -> Result<()>

Example settings.json permission rule configuration

{
  "permissions": {
    "allow": [
      "Bash(git log *)",
      "Bash(git diff *)",
      "Read(*)"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Write(/etc/*)"
    ]
  }
}
Rules use permission rule syntax: ToolName(pattern). Patterns support glob matching against the tool’s input.

Risk classification

Every tool action is internally classified as LOW, MEDIUM, or HIGH risk. This classification drives the auto-permission decision and determines what information the permission explainer surfaces to the user.
Risk levelExamples
LOWRead, Glob, Grep, WebFetch, ToolSearch
MEDIUMEdit, Write, NotebookEdit, WebSearch
HIGHBash, PowerShell, Task (sub-agents)

Protected files

The following files resist automatic editing. Any tool call that would write to these paths requires explicit user approval, even in AcceptEdits mode:
  • .gitconfig
  • .bashrc
  • .zshrc
  • .mcp.json
  • .claude.json
These protections are enforced at the permission layer before the tool’s execute() method is called.

Path traversal prevention

Claurst’s memory path validation (validateMemoryPath()) and team memory write validation (validateTeamMemWritePath()) guard against path traversal attacks using several techniques: URL-encoded traversal detection — Patterns like %2e%2e%2f (URL-encoded ../) are detected and rejected before path resolution. Unicode normalization attacks — Fullwidth characters such as ../ normalise to ../ under NFC. Paths are normalised before comparison. Backslash injection — Backslashes in path keys are rejected regardless of platform. Absolute path rejection — Relative paths to the memory directory must not start with /. Two-pass symlink validationvalidateTeamMemWritePath() first uses path::resolve() to eliminate .. segments, then follows symlinks using realpathDeepestExisting() to verify that the real filesystem path is still inside the permitted directory. Symlink loops (ELOOP) and dangling symlinks are handled explicitly. Null byte rejection — Null bytes in path keys are rejected before any filesystem operation.

Build docs developers (and LLMs) love