Skip to content

permissions

Declarative permission rules using Claude Code’s exact Tool(specifier) syntax. Define allow, deny, and ask arrays in any settings.json to control which tools can run, which paths can be accessed, and which commands are permitted.

Add a permissions key to any settings file:

// .tallow/settings.json (or .claude/settings.json)
{
"permissions": {
"allow": [
"Bash(npm run *)",
"Bash(git commit *)",
"Read",
"Edit(/src/**/*.ts)"
],
"deny": [
"Bash(git push *)",
"Read(./.env)",
"Edit({home}/.ssh/**)"
],
"ask": [
"Bash(docker *)",
"Edit(/config/**)"
]
}
}

Rules follow Tool(specifier) or bare Tool (matches all uses):

RuleMatches
Bash(npm *)Any npm command
ReadAll file reads
Edit(/src/**/*.ts)TypeScript files under src/
WebFetch(domain:example.com)Fetch requests to example.com
mcp__puppeteer__*All puppeteer MCP tools
Task(Explore)The Explore subagent
  1. Deny — any matching deny rule blocks. Deny always wins.
  2. Ask — any matching ask rule prompts for confirmation.
  3. Allow — any matching allow rule permits without prompting.
  4. Default — no match: permit (denylist-only) or prompt (allowlist mode).

Hardcoded safety denylists (fork bombs, rm -rf /) are never overridable.

Permission checks now return structured reason metadata (reasonCode, reasonMessage, matchedRule, source scope/path) and surface concise, actionable messages.

Action denied by permission rule Bash(ssh *).
Source: .tallow/settings.json.
Hint: Allowed patterns: git *.
Reason: Confirmation required by permission rule Bash(docker *).
Tool: bash
Input: docker compose up
Rule: Bash(docker *)
Allow this action?

Sensitive values are redacted before surfacing reasons:

Bash(export API_KEY=[REDACTED])
op://[REDACTED]

Both Claude Code and tallow casing work:

Claude CodetallowBoth work
Bashbash
Readread
Editedit
Writewrite
WebFetchweb_fetch
Tasksubagent

Gitignore-style path conventions:

PatternMeaning
//pathAbsolute from filesystem root
~/pathRelative to home directory
/pathRelative to settings file location
./pathRelative to cwd

Plus tallow’s variable expansion: {cwd}, {home}, {project}.

* matches a single directory level, ** matches recursively.

Bash rules split on &&, ||, ;, |, and newlines. Each segment is evaluated independently:

  • Bash(git *) deny rule blocks git status && rm -rf / (second segment fails)
  • Bash(git *) allow rule requires ALL segments to match

Command substitution (` and $()) is fail-closed for allow rules.

From highest to lowest:

  1. CLI flags (--allowedTools, --disallowedTools)
  2. Project local (.tallow/settings.local.json, trusted projects only)
  3. Project shared (.tallow/settings.json, trusted projects only)
  4. User (~/.tallow/settings.json)

.claude/settings.json and .claude/settings.local.json are also read at their respective project tiers for full Claude Code compatibility.

When project trust is untrusted or stale, .tallow project permission files are ignored until you run /trust-project.

Terminal window
tallow --allowedTools "Bash(npm *)" "Read" --disallowedTools "Bash(ssh *)"
CommandDescription
/permissionsShow all active rules grouped by source
/permissions test Bash(docker compose up)Test a rule against current config
/permissions reloadReload config from disk