Permissions

Permission modes, permission rules, protected paths, auto mode and the audit trail.

Before any tool runs, Cusp decides whether it may run without asking you. The decision comes from, in order: hooks, your rules, protected paths, the session's permission mode, and in auto mode a classifier model. If none of them allows the call, Cusp asks you.

Permission modes

ModeWhat runs without askingSwitch to it
defaultRead-only actions inside the workspace: reading and searching files, read-only shell commands such as ls or git statusThe default
acceptEditsThe above, plus file edits inside the workspaceShift+Tab, /mode acceptEdits
planRead-only exploration only. Edits and mutating commands are denied until you approve a planShift+Tab, /plan
autoLow-risk actions, as judged by a fast classifier model. Risky ones still askShift+Tab, /mode auto
bypassEverything except deny rules and protected paths--permission-mode bypassPermissions at start

In the CLI, Shift+Tab cycles through default, acceptEdits, plan and auto. Bypass is in the cycle only if the session started in it.

There is also dontAsk, for unattended runs: anything that would have prompted is denied instead. Headless cusp -p runs behave this way unless you pre-approve tools.

Plan mode

In plan mode the agent explores, then calls ExitPlanMode with a plan. You approve it (and pick the mode to continue in) or send it back with feedback. While planning, the only file the agent may write is its own plan file. The agent can also enter plan mode itself with EnterPlanMode when a task is large enough to need a design first.

Auto mode

Auto mode hands the "should I ask?" question to a separate, fast model (the provider's small model). The classifier sees only your own recent messages and the proposed action. It never sees the agent's reasoning or tool output, so text injected into a web page or file can't argue it into approving something.

The classifier fails closed. If it's unavailable, errors, or answers ambiguously, Cusp asks you. After 3 consecutive blocks, or 20 in one session, auto mode pauses and falls back to prompting, so a confused agent can't grind against it. Broad allow rules such as Bash(*) are ignored in auto mode, because they would bypass the classifier entirely.

Auto mode is available only if your user settings allow it. Project settings can't turn it on.

Rules

Rules let you pre-approve, always ask, or always deny specific actions. The syntax is Tool or Tool(specifier), the format existing settings.json rules already use.

.cusp/settings.json
{
  "permissions": {
    "allow": [
      "Bash(npm run test:*)",
      "Bash(git log *)",
      "Edit(src/**)",
      "WebFetch(domain:docs.python.org)",
      "mcp__github__get_issue"
    ],
    "ask": ["Bash(git push *)"],
    "deny": ["Read(.env)", "Read(**/*.pem)", "Bash(curl *)", "WebSearch"]
  }
}

Evaluation order

  1. Deny rules win over everything. A deny matches in every mode, including bypass.
  2. Ask rules come next. They force a prompt even if an allow rule also matches.
  3. Allow rules approve the call.
  4. If no rule matches, the mode decides.

Rules from every settings file are combined: managed, command line, local, project and user. See Configuration for where they live.

Rule reference

RuleApplies toSpecifier
Bash(…)Bash, and shell commands run through Terminal and ProcessA command pattern
Read(…)Read, Grep, Glob, LSA path pattern
Edit(…)Edit, Write, MultiEdit, ApplyPatch, EditLines, NotebookEditA path pattern
WebFetch(domain:…)WebFetchA host name
WebSearch(…)WebSearchA query glob
Agent(…) (alias Task)AgentA subagent type, such as Agent(Explore)
Skill(…)SkillA skill name: Skill(deploy), Skill(deploy *), Skill(ops:*)
mcp__server, mcp__server__tool, mcp__server__*MCP tools—
Any other tool nameThat tool—

An Edit(…) allow rule also grants read access to the same paths. A Read(…) deny rule also blocks edits there.

Command patterns

PatternMatches
Bash(npm test)Exactly npm test
Bash(npm run test:*)npm run test followed by anything (the trailing :* is a prefix marker)
Bash(git log *)git log and git log --oneline, but not git logx
Bash(ls*)ls, lsof, anything starting with ls

Cusp parses shell commands properly before matching. A compound command such as npm test && git push is split, and each part has to be allowed on its own. Command substitutions, heredocs, pipes and wrappers such as env, timeout or xargs are understood, so timeout 60 rm -rf build is treated as rm -rf build.

If you write Bash(git * main), Cusp warns that the wildcard before the subcommand matches far more than you probably intended.

Path patterns

Path patterns follow gitignore conventions:

PatternMeaning
src/** or ./src/**Relative to the working directory
/docs/**Relative to the root of the settings file's project (for user settings, ~/.cusp)
~/notes/**Relative to your home directory
//etc/hostsAn absolute path
.env, *.pemA bare name matches at any depth
!patternCarves an exception out of an earlier rule from the same file

* matches within a path segment, ** across segments, and a pattern that names a directory covers everything below it.

Protected paths

Some files are how a prompt injection would persist or escalate: agent configuration, git internals, shell startup files and tool configs that execute code. Writes to them always need a person, in every mode, bypass included. In dontAsk and headless runs they are denied.

Protected directories, at any depth: .git, .cusp, .claude, .codex, .cursor, .gemini, .vscode, .idea, .husky, .cargo, .devcontainer, .yarn, .mvn, .ssh, .gnupg, .aws.

Protected files, at any depth, include .gitconfig, .gitmodules, shell startup files (.bashrc, .zshrc, .profile and friends), .envrc, .npmrc, .yarnrc, bunfig.toml, .pre-commit-config.yaml, lefthook configs, .mcp.json, .claude.json and .cusp.json.

A few locations inside protected directories are exempt because agents legitimately write there, such as .cusp/worktrees and .cusp/agent-memory.

Cusp also refuses to run obviously destructive shell commands against critical paths, such as removing your home directory or the filesystem root, without an explicit confirmation.

Answering a prompt

When Cusp asks, you can choose:

ChoiceEffect
Allow onceThis call only
Allow for this sessionAdds a session rule, so similar calls run without asking until the session ends
Always allowWrites a rule to .cusp/settings.local.json, which is not meant to be committed
DenyThe call fails and the agent is told it was denied
Deny with feedbackThe call fails and the agent receives your explanation, so it can change course

Cusp suggests a rule for "always" that is as narrow as is useful, for example Bash(npm run lint:*) rather than Bash(npm *).

Hooks and permissions

PreToolUse hooks run before the permission engine and can return allow, deny, ask or defer. A hook's deny holds in every mode. A PermissionRequest hook runs when a prompt would be shown and can answer it for you. See Hooks.

Workspace trust

The first time you open a folder, Cusp asks whether you trust it. Until you do, the project's own settings files can only narrow permissions. They can't add allow rules, hooks, environment variables, MCP servers, additional directories, an API key helper or a status line command.

Independently of trust, project and local settings can never set the default mode to auto or bypass, and can't override sensitive environment variables such as HOME, TMPDIR or XDG_*.

Audit

Every decision is recorded as a permission.decision event in the session log, with the tool, the mode, the outcome, what decided it (a rule, the mode, the classifier, a protected path, a hook, or you) and the matching rule. You can review any session's decisions after the fact.

Command-line flags

cusp --permission-mode acceptEdits
cusp --allowedTools "Bash(git log *)" Edit
cusp --disallowedTools "Bash(rm *)" WebFetch
cusp --dangerously-skip-permissions   # same as --permission-mode bypassPermissions

--allowedTools and --disallowedTools accept comma- or space-separated lists and never split inside parentheses. They apply to the session only.

On this page