Skip to content

Course · Cursor · Permissions · auto-run & sandbox

Allowlist and denylist: friction controls, not the boundary

Cursor gives you two fine-grained lists. The allowlist names commands that run without an approval prompt under Cursor’s run modes (Allowlist: only allowlisted commands run freely; Auto-review: the recommended default, which runs known-safe calls and reviews the rest; run modes documentation, as of September 2026) - set in the editor under Settings > Agents > Approvals & Execution. Beside it, the permissions system accepts a denylist - patterns that should be blocked. In the CLI (agent; older material spells the command cursor-agent, CLI changelog), both live in the permissions configuration; the editor surface exposes the allowlist through the run modes. These look like the way to say “let it run npx vitest run and git status freely on budgetcli (the course’s illustrative personal-finance API), but never git push or rm -rf.” They are useful as intent, but you have to know exactly how much weight they can bear. What follows is this course’s judgment, argued below: treat them as pattern-based workflow controls, not as your security boundary. (The CLI permissions reference, as of September 2026, is the source for the grammar and configuration facts in the next paragraphs - pattern shapes, config locations, and the deny-over-allow rule.)

Concretely, the CLI side is configured in ~/.cursor/cli-config.json (global) or <project>/.cursor/cli.json (per project), under permissions.allow and permissions.deny. A rule names a tool and a pattern - Shell(commandBase) for the first token of a command line, Read(pathOrGlob), Write(pathOrGlob), WebFetch(domain), Mcp(server:tool) (rules for tools exposed by connected MCP servers - Cursor’s mechanism for plugging in external tools) - with * wildcards, and deny rules take precedence over allow rules. One consequence deserves its own sentence: commandBase matching is subcommand-blind. The reference’s own example is Shell(git) - “Allow any git subcommand” - which includes git push. If your intent is “git status yes, git push never,” a first-token allow cannot express it; use the documented command:args syntax for finer control (the reference’s example is Shell(curl:*), “Allow curl with any arguments” - the pattern matches command and arguments with globs), or put the forbidden subcommand in the deny list. An illustrative project file:

{
"permissions": {
"allow": ["Shell(npx)", "Shell(git:status)", "Read(src/**)"],
"deny": ["Read(.env*)", "Write(.github/workflows/**)", "Shell(git:push)"]
}
}

One deliberate coarseness to notice: Shell(npx) admits every npx subcommand - the same subcommand-blindness - kept here because test runners are the classic trusted case. Narrow it with command:args the way the git entries do if your intent is finer.

Sandboxing and command rules answer different questions. This is the course’s control distinction - what guides execution versus what enforces a boundary - in miniature: the sandbox constrains filesystem and network reach where supported; allow/deny rules control command approval and execution policy. Their exact interaction depends on the current editor and CLI surface and settings, so verify it in the current permissions reference before relying on a particular fallback or approval path.

A deny rule is not a security boundary. A pattern can block an obvious command while missing an equivalent shell expression, script, alias, or indirect tool call. Use deny rules to express intent and reduce accidental execution, but do not treat them as proof that an irreversible action is impossible. The sandbox, a disposable environment, and least-privilege credentials are the stronger controls.

The conclusion is not “lists are useless.” Use allow/deny rules to cut friction and express intent, while putting the real containment in the sandbox or an isolated runner. Do not reach for a deny rule as the thing standing between the agent and an irreversible action. When in doubt, believe the environment boundary, not the pattern matcher.

That leaves one case the local controls can’t cover: work where you want a guarantee rather than a good boundary. For that, you stop tuning settings and change where the agent runs at all. Next: when you need real isolation.