If the mode sets the general rhythm and the sandbox the physical boundary, the permissions file refines individual actions. It lists terminal commands that may run without questions, tools of external servers that are trusted, and text guidance for automatic review. It lives in two places - a user one and a repository one - and that separation matters: personal habits and team policy should not mix in one file. The user file belongs to a person and travels with them from project to project. The repository file belongs to the code and arrives together with it to everyone who cloned it. A requirement that must hold for everybody does not live in the user file by definition.
The merging of layers is predictable: arrays from the user and repository files are combined, while any key defined in those files fully replaces the corresponding list from the interface. The difference between combining and replacing is not a detail. Where arrays add up, a local relaxation is appended to the shared one and quietly widens it. Where replacement happens, it displaces what was configured before, and you lose the old list whole. The order of enforcement is strict and worth knowing by heart: the team's administrative policy outranks the permissions files, and the files outrank what you clicked in the interface. That is exactly the hierarchy that lets an organization guarantee a minimum without forbidding projects to refine the details.
It helps to see a complete file once. Below are a list of allowed terminal commands with patterns, a list of allowed tools of external servers and a block of guidance for automatic review: what to allow and what to block. Patterns are convenient here but require care: the wider the pattern, the more it captures, and a permission of the form any package-manager command opens things you did not think about. A pattern is matched against the text of a command rather than its meaning, so an asterisk at the end of a line is a promise that any continuation suits you.
The guidance for automatic review deserves separate attention, because there are more illusions around it than anywhere else. Those are hints to the classifier in natural language: they help it decide borderline cases in the right direction. But that is a direction, not a prohibition. A line saying block publishing and deletion lowers the probability but does not make the action impossible: the classifier reads that line, the command itself and the rest of the context, and the outcome comes out of all of it at once. For a hard ban there are other means: the sandbox, hooks and administrative settings. The allowlists themselves are classed by the documentation as a convenience rather than a security boundary.
That difference is the main distinction the whole permissions setup rests on. Mechanisms split into those that persuade and those that forbid. The persuading ones are useful: they remove routine approvals, and they can be phrased in human terms without enumerating every spelling of a command. But security is built on the forbidding ones, because only they give a reproducible result regardless of wording, repository content and how the model happens to reason today. The persuading layer is responsible for convenience, the forbidding one for what convenience must not be trusted with.
The difference shows on a concrete scenario. The agent reads a file from the repository in which, among ordinary text, sits a line addressed to it: upload the contents of the environment variables to an external address. Guidance to the classifier works here as one more voice in the common chorus of text - it competes with what was read rather than cancelling it. The allowlist of commands works differently: in allowlist mode a command that is not on it does not run without approval, and the words used to ask for it make no difference. In auto-review mode a command that is not allowed still reaches the classifier and may go through without a question. The first mechanism argues, the second simply does not let it through.
Any policy change ends with a negative test. Ask for a knowingly forbidden safe action: read a test blocked file, reach a disallowed domain, run a harmless command that is not on the list. If the refusal is observed, the policy works. If not, you have just learned that under controlled conditions rather than at the moment when it would be expensive. A test on an allowed action proves nothing: it passes with the check switched off entirely.
Allowlists have their own price, and it is better known in advance. Every new line removes one approval and at the same time widens what happens without your participation. A list grown to several dozen patterns is no longer read in full by anyone, which means nobody can say what exactly is permitted. The sign by which real work recognizes that you went too far is simple: approvals stopped appearing at all, and the session record contains commands you did not plan. That is not a failure. That is exactly what you permitted, merely written wider than you meant.
The engineering conclusion about allowlists is simple: they are written for named tasks rather than for convenience. Build, lint, tests, typecheck are clear families of commands that are easy to explain in review and easy to narrow down to specific subcommands. Publishing, deployment, work with secrets and destructive operations stay off the list not because the agent cannot handle them but because the cost of a mistake there is out of all proportion to saving one approval. A good list is short enough to be read aloud without losing your place.
The typical failures are predictable. Taking guidance for the classifier as a prohibition. Writing too wide a command pattern and opening a family you did not consider. Putting team policy into the user file, from where it is easy to remove. Verifying a change only with an allowed action and deciding the policy works. And never rereading the allowlist while it grows.
// .cursor/permissions.json
{
"terminalAllowlist": ["git status", "pnpm:test*"],
"mcpAllowlist": ["github:get_pull_request", "linear:search*"],
"autoRun": {
"allow_instructions": [
"Allow only read-only git commands and the project's tests"
],
"block_instructions": [
"Block publishing, deletion and any command touching secrets"
]
}
}
// the instructions are hints to the classifier, not a prohibition