Permission Rules
Permission rules tell Polytoken which tool calls to run automatically, which to ask you about, and which to block. A rule can match a tool name, a file path, a shell command, or a web URL.
For the configuration format (fields, types, YAML syntax), see the permission configuration reference. The Permissions page covers the interactive approval prompt.
How Polytoken evaluates a tool call
Section titled “How Polytoken evaluates a tool call”Every time the model requests a tool call, Polytoken runs the built-in protections first, then gathers every rule that matches the call and resolves the matching rules into one verdict.
The protections guard your configured secret files and secret words in
Standard and Autonomous modes. Bypass and Bypass+ skip the protections
entirely, even though Bypass+ still evaluates your explicit deny rules.
Each protection holds the call for your approval, filters secret material
from the result, or denies the call outright. A hold lifts only when a
rule names that exact command or, for file reads and grep, that exact
path; broad allow rules do not lift a hold, and no rule lifts an outright
denial. See Protecting secrets for which reads,
commands, and searches each list affects.
Then Polytoken checks your rules. Rules live in four buckets that correspond to verdicts:
- deny blocks the call immediately.
- ask prompts you before running.
- ask-unless-allowed prompts unless an allow rule of equal or greater specificity also matches the same call.
- allow runs the call without prompting.
Deny beats ask, and ask beats allow. When multiple rules across different buckets match the same call, the most restrictive verdict wins. Within a single bucket, any matching rule is enough.
If no rule matches, the permission monitor decides what happens next.
Malformed permission rules
Section titled “Malformed permission rules”A malformed rule can fail at parsing or at compilation, and Polytoken treats the two failures differently.
A rule with a syntax error, an unrecognized field, or an invalid value, such
as command_tokens with no entries, fails while Polytoken parses the
permission file. Polytoken rejects the whole file, whatever bucket the rule
sits in, and the error names the file and the problem.
Polytoken judges a rule that parses but fails to compile, such as a glob
with an unclosed [, by its bucket. In the allow bucket, Polytoken
skips the rule and warns you with the rule name; other allow rules remain
active, so one invalid allow rule does not prevent Polytoken from starting
or reloading the permission configuration. In the ask,
ask-unless-allowed, and deny buckets, a rule that fails to compile
prevents startup with an error that identifies the rule and bucket. During
reload, Polytoken keeps the previously active permission configuration
instead of publishing the invalid ruleset.
Permission monitors
Section titled “Permission monitors”The monitor controls what happens when no rule produces a verdict. You
switch monitors with /permissions or the Ctrl+Shift+P typeahead.
Standard
Section titled “Standard”Standard is the default. When no rule matches, Polytoken asks you. The
approval prompt lets you allow the call once, for the session, for the
project, or for your user account. Each choice takes effect immediately.
The session choice lasts until the session ends. The project and user
choices write the grant to a permissions.local.yaml file: the project
choice in the project’s .polytoken/ directory, the user choice in
$XDG_CONFIG_HOME/polytoken/ (default: ~/.config/polytoken/). When
Polytoken cannot inspect a command, the prompt offers only one-time
choices, with no option for the session, the project, or your user
account.
Built-in protections still apply. Polytoken holds a command it cannot
inspect (command substitution, nested shells, dynamic arguments) for your
approval, because a rule whose conditions require particular command
details cannot approve it safely. A whole-tool allow is the exception:
it can approve the command without a prompt. A dynamic directory change
such as cd "$WORKTREE" names no fixed destination, and the destination
is the capability being granted. Polytoken holds such a change for your
approval even when a whole-tool allow covers the tool.
Autonomous
Section titled “Autonomous”Autonomous hands routine approvals to a classifier model. When a tool call
would normally ask you, the classifier reads the request and decides whether
to allow it. If the classifier is confident the call is safe, the call runs
without interrupting you. When the classifier is unsure, Polytoken asks you
instead. The fallback prompt carries the same scope choices as the Standard
prompt whenever Polytoken can derive a permission rule for the call.
Polytoken cannot derive a permission rule for an opaque shell command, a
tool_flow script, or a dynamic directory change. The prompt for such a
call offers only one-time choices: allow, deny, or allow with a
permission-mode switch. Repeated classifier denials, which suggest the model
is stuck, lead to the same one-time prompt.
Tool calls that match an explicit allow rule run immediately, without the classifier. Polytoken still asks you before granting access to a new filesystem location.
The plan facet can hand work to the execute facet in Autonomous mode.
Built-in protections still run before the classifier. Opaque shell commands
go to the classifier, which reads the full command text
and judges the command on that text. A literal relative directory
change such as cd src names a fixed destination, so Polytoken can
suggest permission rules for that directory.
Polytoken holds a dynamic directory change for approval: the destination
controls what the model can access, and Polytoken cannot know the destination
before the command runs. Such changes include cd "$VAR" and any cd
that follows a directory change Polytoken could not determine.
When the classifier approves a call and Polytoken can derive a deterministic permission rule for the call, the approval also creates a session-only overlay grant. The grant covers matching calls for the rest of the session. An approval with no derivable rule, such as an opaque shell command, covers only that one invocation. Overlay grants never persist to disk.
What the classifier sees
Section titled “What the classifier sees”When Autonomous mode asks the classifier to assess a tool call, Polytoken sends the following inputs:
- The tool call, including the tool name and complete input JSON. For shell tools, the input also includes the full command and the permission subject under review.
- All configured permission rules and the deterministic candidate rule derived for the tool call.
- The session’s prior user prompts.
- The working directory for the tool call.
- The name of the facet the call runs in, together with that facet’s
polytoken.autonomous_hint. For a call inside a subagent, the subagent’s definition takes the facet’s place: the classifier receives the subagent’s name and the subagent’spolytoken.autonomous_hint, plus a boundary note that the call runs inside a subagent. - The authored
autonomous_permission_matcher.classifier_rulesvalue when you set it. - Optional
autonomous.mdfiles from the user config directory and the project.polytoken/directory. - When a saved-session goal is running, the goal text you set with
/goal. Subagent calls do not receive the goal text.
The classifier does not receive the facet’s prompt body, assistant replies, tool results, or system reminders. Polytoken sends only the user prompts from session history, not the other conversation messages.
Bypass
Section titled “Bypass”Bypass runs every tool call without checking. Polytoken evaluates no rules, runs no protections, and shows no prompt. Use Bypass only when you want zero interruptions and accept full responsibility for what the model does.
Bypass+
Section titled “Bypass+”Bypass+ skips most checks like Bypass, but still evaluates your explicit deny rules. If a deny rule matches, Polytoken blocks the call. Everything else runs without interruption. Use Bypass+ when you want an open mode but need certain paths, commands, or tools to stay off-limits.
How approvals accumulate
Section titled “How approvals accumulate”Approvals you grant interactively build up across three scopes:
- Session grants live in memory and disappear when the session ends. Every approval starts here unless you choose a broader scope.
- Project grants persist in
.polytoken/permissions.local.yamlinside the project directory. They apply every time you work in that project. - User grants persist in
permissions.local.yamlunder$XDG_CONFIG_HOME/polytoken/and apply across all projects where you use Polytoken.
Polytoken reads rules from permissions.yaml as well as
permissions.local.yaml in each location. The approval prompt writes its
grants to the local file only, so permissions.yaml stays a file you edit
yourself.
Seed permissions also run at session start. These built-in rules allow
low-risk work without prompts: reading, writing, and editing files inside
the project, changing directories within the project, running read-only
shell commands like ls and cat, and using control-plane tools. The
seeds also block direct edits to package-manager lock files, such as
package-lock.json, with a message that tells the model to regenerate the
lock file instead of editing it by hand. Seed rules do not persist and do
not appear in your permission file.
Writing shell command rules
Section titled “Writing shell command rules”Shell rules match the structure of a command, not its raw text. Polytoken
parses each command into an executable, subcommand tokens, and flags, then
matches each part separately. A rule that allows find does not allow the
rest of the line.
Shell interpreter calls are exempt from structural matching. Commands that
invoke bash, sh, zsh, or any other shell interpreter are always opaque,
so command_tokens and executable rules cannot match them. A whole-tool
allow can approve these commands silently. Without one, Polytoken asks you to
approve each call one time in Standard mode, and the autonomous classifier
judges the call in Autonomous mode.
Matching the executable and subcommand
Section titled “Matching the executable and subcommand”Use command_tokens to match the beginning of a command as a prefix. A
trailing "*" in the list makes the rule a prefix match: the actual command
tokens must start with the listed tokens, and Polytoken allows anything
after them.
allow: - tool: shell_exec args: command_tokens: ["git", "status", "*"]This allows git status, git status --short, git status ., and any other
command that starts with git status. Without the trailing "*", the match
is exact over the leading tokens Polytoken collects: the executable plus
argument tokens up to the first token that looks like a flag, a path, a URL,
a glob, or a number. command_tokens: ["git", "status"] therefore matches
git status --short, because the flag never enters the token list, but not
git status ., because Polytoken collects . as a third token.
Narrow the match to the executable name alone:
allow: - tool: shell_exec args: executable: "rg"Or combine the executable with its ordered subcommand tokens:
allow: - tool: shell_exec args: executable: "git" subcommand: ["remote", "get-url"]The subcommand field matches only non-flag argument tokens, in order. The
matcher treats a token as a flag when the token starts with - and has at
least two characters, so a rule with subcommand: ["remote", "get-url"]
matches git remote --verbose get-url as well as git remote get-url. The
end-of-options marker -- counts as neither a flag nor a subcommand. The
matcher does not track which flags take values, so a non-flag token that
follows a flag still counts as a subcommand.
For example, git commit -m msg produces subcommand: ["commit", "msg"],
so a rule for subcommand: ["commit"] does not match.
The match requires the same number of non-flag tokens in the rule and the
command. Use command_tokens with a trailing "*" when you want prefix
matching instead.
Matching flags
Section titled “Matching flags”The flags_present field constrains which flags the command may or may not
carry. An array (or bare string) requires those flags to be present. To
require their absence instead, use an object with an excludes key listing
the flags that must not appear.
allow: - tool: shell_exec args: executable: "find" flags_present: excludes: ["-exec", "-execdir", "-ok", "-okdir", "-delete"]The rule allows find as long as none of the listed execution or deletion
flags appear. It does not match find . -exec rm {} \; because -exec is
present.
Matching file paths
Section titled “Matching file paths”File tools match on path:
allow: - tool: file_read args: path: "/src/**"Polytoken treats bare strings with glob metacharacters (*, ?, [, {)
as globs. ** matches any number of path segments. Use the {,/**} suffix to
match both a directory and everything under it: /src{,/**} matches /src
and /src/anything/inside.
To match a literal metacharacter, escape it with a backslash: path/to/\*
matches a file literally named *, and nothing else. A character class such
as [*] also matches a literal *.
Which characters trigger glob matching depends on the argument. Path
arguments treat *, ?, [, and { as metacharacters. Flat arguments,
such as the command_tokens of a shell rule, treat only * as a
metacharacter; ?, [, and { are literal characters there and need no
escaping.
A malformed glob, such as a rule containing an unclosed [, is an invalid
rule. Polytoken skips an invalid allow rule with a warning, and an invalid
rule in the ask, ask-unless-allowed, or deny bucket prevents startup. See
Malformed permission rules for how Polytoken
handles each bucket.
Polytoken resolves .. segments in a path before it matches the path
against your rules, so a path like src/../README.md matches normally. A
path that escapes through a leading .., such as ../secrets, remains
blocked.
Matching URLs
Section titled “Matching URLs”Web tools match on url:
allow: - tool: web_fetch args: url: "https://polytoken.dev{,/**}"The {,/**} suffix matches the address itself and every address under it.
On URLs, the same rule also matches the address with a query or fragment
attached: https://polytoken.dev?x=1 and https://polytoken.dev#section
both match this rule.
Rule messages
Section titled “Rule messages”Every rule can carry an optional message. When a rule with a message is the
final verdict for a tool call, Polytoken injects the message text as a system
reminder after the tool batch finishes.
Messages fire only for the resolved verdict. An allow rule with a message
fires its message when Polytoken allows the call; a deny rule fires when
Polytoken denies the call. Ask rules never produce messages. If multiple
rules with messages match the same call, each one whose verdict matches the
resolved outcome fires its message.
Use messages to explain why a rule exists or to guide the model toward a different approach. Bypass mode skips all messages.
Example: safe git commits
Section titled “Example: safe git commits”Suppose you want Polytoken to ask before any git commit, but block
git commit --no-verify outright. Commits that skip hooks are a common
source of problems, so you want a message that tells the model why.
version: 2ask: - tool: shell_exec args: command_tokens: ["git", "commit", "*"]deny: - tool: shell_exec args: command_tokens: ["git", "commit", "*"] flags_present: ["--no-verify"] message: "All commits must go through commit hooks. Remove --no-verify."Both rules match any command that starts with git commit. The deny rule
only matches when --no-verify is present, and for those commands the
call lands in the deny bucket. Deny outranks ask, so the verdict comes
out deny:
git commit -m "fix"matches only the ask rule. The verdict is ask.git commit --no-verify -m "fix"matches both rules. A deny match outranks the ask match, so the verdict is deny, and the message fires as a system reminder.
The flags_present: ["--no-verify"] form means the rule matches when
--no-verify is among the flags Polytoken parsed from the command. Other
flags on the line do not affect the match.
Protecting secrets
Section titled “Protecting secrets”Two configuration lists guard sensitive material in Standard and Autonomous modes:
- Secret files are paths you name as sensitive, such as
.env. A file read that targets a secret file prompts you. A shell command prompts you when Polytoken recognizes a secret-reading pattern, such ascatorlessopening the file. Thegreptool runs without a prompt, and Polytoken filters from the results anything that would surface the path. Only a rule that names that exact command, or that exact path for file reads andgrep, lifts the hold; broad allow rules do not. Polytoken denies two kinds of command outright, whatever your rules say: a command that directly prints a configured secret variable, and a command that reads environment secrets through a scripting-language interpreter such as Python or Node. - Secret words are values you name as sensitive. When a search would surface them, Polytoken filters them from the result before you or the model see them.
See the permission configuration reference
for the secret_files and secret_words fields.