Seven coding agents, checked against the same 16 questions: what can a developer, a team or an admin actually make each agent do, or stop it doing, through configuration?
We read the official documentation and changelogs and, for the open-source agents, the source code. Every answer cites its source.
7 of 7can block a tool call
with a hook that runs before it. In most, a hook that crashes or times out lets the call through by default.
0 of 7fully deny commands
Every deny rule matches command text, so a wrapper or another interpreter can get round it, and some only force a prompt.
0 of 7keep a tamper-resistant log
of every permission decision on the machine. Most record decisions only once OpenTelemetry is on.
One policy for every agent
$npx @thinkingraven/rookery check
Key findings
What the settings pages don’t say.
Every agent can be locked down further than its defaults. How far, and how reliably, varies more than the feature lists suggest.
01
A cloned repo’s own config is the first thing to check, and agents differ sharply.
Ignored before you trust the folder: Claude Code (interactive), Codex, Gemini CLI (current releases), Copilot CLI and VS Code.
Applied on open: Cursor ships with Workspace Trust off, so a repo’s hooks, permission allow-lists and sandbox settings apply as soon as you open it. OpenCode has no trust prompt at all.
No trust check in Claude Code’s -p (headless) mode: a repo’s hooks and .mcp.json servers run even in a folder you’ve never trusted.
After trust, a repo can turn approvals off in Codex:approval_policy = "never" plus sandbox_mode="danger-full-access". Since 0.147.0, some Codex flows record a project as trusted without a prompt.
After trust, a repo can’t turn on bypass or yolo mode in Claude Code, Gemini CLI or Copilot CLI, though in most agents it can still pre-approve commands or tools and add MCP servers and hooks.
02
“Deny” often means “ask”, and every agent’s command rules match text.
VS Code’s false terminal rules and Windsurf Cascade’s deny list only force a prompt, and “Allow all” skips it.
All seven match command text, prefixes or patterns, so a wrapper, an absolute path or another interpreter can get round a deny rule. That’s why every agent is rated partial here. Pair deny rules with the OS sandbox.
03
Read denies mostly cover the agent’s own read tools, not the shell.
With the OS sandbox on, shell reads are stopped too: Codex (whose permission profiles are enforced by its sandbox), Claude Code and Copilot.
Claude Code without the sandbox: a Read deny also stops the file commands it recognises (cat, head, tail, sed, tee) and < redirects, but not grep -r or a script that opens the file itself.
Rules that don’t reach the terminal: Gemini CLI’s ignore files, OpenCode’s read rules, Windsurf’s .devinignore, and .cursorignore. Cursor’s sandbox limits reads outside the workspace, not secrets inside it.
04
Sandboxes exist almost everywhere but are mostly off by default.
On by default: Codex, and Cursor’s Auto-review mode, which sandboxes commands that aren’t on the allow-list.
Off by default: Claude Code, Gemini CLI and Copilot.
Partly or not at all: Windsurf has one only in Devin Local. OpenCode has none and recommends Docker or a VM.
05
Admins can lock bypass modes off in most agents, but the lock lives in the client.
Strongest: Copilot, with one managed setting across the CLI, VS Code and JetBrains.
Claude Code and Codex: both have managed locks. Claude’s server-managed settings aren’t fetched on Bedrock, Vertex or a custom base URL. Codex’s cloud policy only reaches ChatGPT sign-ins.
Cursor: group settings merge with the loosest setting winning.
Windsurf: caps Cascade only.
Gemini CLI: its system settings path can be repointed with an environment variable.
OpenCode: can’t turn off --auto.
06
Every agent can block a tool call with a hook, but most hooks fail open by default.
All seven can block a call before it runs. OpenCode does it with in-process plugins instead of hook commands.
Fail open, with no documented way to change it: in Codex, Gemini CLI and Windsurf, a hook that crashes or times out lets the call through.
Fail open unless you opt in: Claude Code (onFailure: "block", 2.1.295 and later) and Cursor (failClosed: true).
Copilot: a command hook that crashes or exits non-zero blocks the call, but a timeout lets it through.
07
No agent keeps a tamper-resistant local record of every permission decision.
Claude Code, Codex, Gemini CLI and Copilot record decisions only once OpenTelemetry is turned on. Gemini CLI can write it to a local file.
OpenCode logs every permission check by default, in user-writable files of which only the 10 newest are kept.
Cursor and Windsurf: a post-tool hook is the documented way to build a record. Their admin audit logs don’t cover agent tool calls.
The matrix
Seven agents, 16 questions.
“Partial” always comes with limits. Pick a row to read every agent’s answer, its limits and its sources.
Supported as describedSupported as describedSupported with important gaps (see limits)Supported with important gaps (see limits)Not supportedNot supported
Claude Code: Supported with important gaps (see limits)
Codex CLI: Supported with important gaps (see limits)
Gemini CLI: Supported with important gaps (see limits)
OpenCode: Supported with important gaps (see limits)
Cursor: Supported with important gaps (see limits)
GitHub Copilot: Supported with important gaps (see limits)
Windsurf (Devin Desktop): Supported with important gaps (see limits)
Capability by capability
Every answer, with its limits.
How to set each control in each agent, what it doesn’t cover, and where the answer comes from. Hover a source number for the quote.
Allow commandsPre-approve specific shell commands so the agent runs them without asking.Supported as describedSupported as describedSupported as describedSupported as describedSupported as describedSupported as describedSupported as described
Claude Code
Supported as describedSupported as described
permissions.allow with Bash(...) rules, or --allowedTools for one session. Granularity: exact match (no *), prefix (cmd * or legacy cmd:*), and glob-style * anywhere (matches any text including spaces). No regex. Example: {"permissions": {"allow": ["Bash(npm run test *)", "Bash(git diff *)"]}}
Limits
Compound commands are split on &&, ||, ;, |, |&, & and newlines and every subcommand must match. A fixed set of wrappers (timeout, time, nice, nohup, stdbuf, command, builtin, noglob, bare xargs) is stripped before matching. Prefix rules cannot auto-approve exec wrappers (watch, setsid, flock, ionice) or find with -exec/-delete. Allow rules have no effect in bypassPermissions; on entering auto mode broad rules (Bash(*), interpreter wildcards like Bash(python*), package-manager run commands) are dropped. Allow rules never approve writes to protected paths or rm/rmdir of critical paths. Managed allowManagedPermissionRulesOnly discards all non-managed allow rules.
Execpolicy .rules files (Starlark) in a rules/ folder next to an active config layer, e.g. ~/.codex/rules/default.rules: prefix_rule(pattern = ["gh", "pr", ["view", "list"]], decision = "allow"). Granularity: ordered argv-token PREFIX match, each position a literal or a list of alternatives; optional host_executable(name="git", paths=[...]) pins which absolute paths may match a basename rule. No glob or regex. match/not_match examples are validated at load. Test with codexexecpolicycheck--rules<file>--<cmd>.
Limits
allow means 'run OUTSIDE the sandbox without prompting', not just 'skip the prompt': the command gets bypass_sandbox=true unless managed deny-read rules are active. Simple shell chains (&&, ||, ;, |) are split and every segment must be allowed; scripts with redirection, $(...), variables, globs or control flow are not split and are matched as ["bash","-lc","<script>"]. Accepting an 'always allow' suggestion in the TUI writes an allow rule to ~/.codex/rules/default.rules. Managed auto_review.ignore_rules can make Codex ignore allow rules for named models. Rules are marked experimental.
Policy TOML rule with decision="allow" and commandPrefix (prefix at a word boundary), commandRegex (regex) or argsPattern (regex over the JSON args). Example in ~/.gemini/policies/allow.toml: [[rule]] toolName="run_shell_command" commandPrefix=["git status","npm test"] decision="allow" priority=100. Legacy settings: tools.allowed ["run_shell_command(git)"] or --allowed-tools.
Limits
Granularity is prefix (word boundary) or regex. Chained commands (&&, ||, ;, pipes) are split and every part must be allowed, and bash -c wrappers are unwrapped. Redirection (>, >>, <) downgrades allow to ask unless the rule sets allowRedirection=true. If parsing fails, the default (ask, or deny when headless) applies. In the source code, a narrowed tools.allowed entry such as run_shell_command(git) also adds a DENY rule just below it for every other shell command ('Narrowing Enforcement'), so other commands are blocked rather than prompted. This was not tested.
permission.bash object of wildcard patterns -> allow|ask|deny, e.g. {"permission":{"bash":{"*":"ask","git status *":"allow","npm test":"allow"}}}. The command is parsed with tree-sitter (bash/PowerShell). Every command node, including ones nested in pipelines, subshells and command substitutions, is checked against the patterns.
Limits
Granularity is glob over the full command text: * (any chars, including spaces and slashes) and ?. A trailing ' *' also matches the bare command. There is no regex or prefix-at-word-boundary mode. Last matching rule wins, so order matters. The default is already allow for bash, so an allowlist only bites after setting "*": "ask" or "deny". 'Always' approvals add a prefix pattern (e.g. git status *) for the rest of the session only.
terminalAllowlist in permissions.json (prefix match; 'base:args-glob' form) or the in-app allowlist. Used by Allowlist and Auto-review modes. Example: {"terminalAllowlist": ["git status", "npm:install*", "cargo test"]}
CLI
permissions.allow with Shell(commandBase) tokens in ~/.cursor/cli-config.json or <project>/.cursor/cli.json. Example: {"permissions": {"allow": ["Shell(git)", "Shell(curl:*)"]}}
Team
Dashboard terminal allowlist (admin-controlled; replaces file/IDE lists). Org Groups union their allowlists.
Limits
Granularity is a command prefix (first token or a longer prefix); IDE matching is case-sensitive prefix matching. The deployment-patterns page instead says entries are matched against the full command string, so the docs disagree.
Per-user and per-repo permissions.json arrays are concatenated, so a cloned repo can add allowlist entries.
Cursor describes allowlists as best-effort convenience, not a security boundary.
The docs disagree on empty arrays: the permissions reference says an empty array means an empty allowlist with no IDE fallback; deployment-patterns says it falls back to the editor list.
When the team admin controls Run Mode, permissions.json and the IDE cannot add entries.
chat.tools.terminal.autoApprove: per-command booleans or /regex/. Rules match each sub-command, or the whole line with {"approve": true, "matchCommandLine": true}. Example: {"chat.tools.terminal.autoApprove": {"mkdir": true, "/^git (status|show\\b.*)$/": true}}. Built-in defaults auto-approve common read-only commands. Prompt approvals can be saved per session, workspace, or user. Agent Host sessions also honour managed permissions.allow ["Shell(git status)"].
CLI
--allow-tool='shell(git:*)' (session only). Saved per-location approvals in ~/.copilot/permissions-config.json, e.g. {"locations":{"/repo":{"tool_approvals":[{"kind":"commands","commandIdentifiers":["git:*"]}]}}}. Managed permissions.allow ["Shell(git push *)"]. Read-only shell commands are auto-allowed.
Cloud agent
Not applicable. All tools are pre-granted.
Limits
VS Code parsing is best-effort tree-sitter and can be evaded by aliases, quote concatenation, or obfuscation.
CLI commandIdentifiers match literally except for a trailing ':*'. There are no globs or regex.
permissions-config.json is per-user, per-location. It is not a shareable repo policy.
VS Code source: chat.tools.terminal.autoApproveWorkspaceNpmScripts defaults to true and auto-approves npm/yarn/pnpm run of the workspace's package.json scripts.
Per-user allow list, managed from the command card dropdown: 'Prefix' adds e.g. git * (token prefix), 'Allow' adds the exact command. Used in 'Allowlist Only' and higher levels. Team-wide allowlist: Admin Portal > Team Settings > Terminal Commands > Manage Lists (Teams & Enterprise), merged with user lists.
Devin local
permissions.allow rules, e.g. {"permissions": {"allow": ["Exec(git)", "Exec(npm run)"]}} in .devin/config.json, .devin/config.local.json, ~/.config/devin/config.json, or org Team Settings.
Limits
Cascade entries are either exact commands or ' *' token-prefix entries. No regex.
The storage file of the Cascade per-user list is not documented.
Devin Local project config ranks above user config, so a repo's .devin/config.json can add allow rules.
Deny commandsBlock specific shell commands outright.Supported with important gaps (see limits)Supported with important gaps (see limits)Supported with important gaps (see limits)Supported with important gaps (see limits)Supported with important gaps (see limits)Supported with important gaps (see limits)Supported with important gaps (see limits)
Claude Code
Supported with important gaps (see limits)Supported with important gaps (see limits)
permissions.deny with Bash(...) rules, or --disallowedTools for one session. Example: {"permissions": {"deny": ["Bash(git push *)", "Bash(curl *)"]}}. A bare Bash deny removes the tool entirely.
Limits
Hard block: deny rules apply in every mode including bypassPermissions, are evaluated before ask and allow, and a deny at any scope cannot be overridden by an allow at any other scope; the user is not offered a runtime approval for a denied call (they would have to edit settings, which they cannot do for managed rules). Deny rules match after env-assignment stripping and inside subshells, $(...) and control-flow bodies. But matching is on the command text: /usr/bin/curl, sh -c 'curl ...', git -C . push are not matched. The docs state a deny rule is not a security boundary; pair with the sandbox. Exception: on machines without managed settings and not on a Team/Enterprise plan, an installed mod handling tool.check can approve a call a deny rule refuses.
Supported with important gaps (see limits)Supported with important gaps (see limits)
prefix_rule(pattern = ["rm"], decision = "forbidden", justification = "Use git clean -fd instead.") in a .rules file, or admin-enforced in requirements.toml: [rules] prefix_rules = [{ pattern = [{ token = "rm" }], decision = "forbidden" }]. Most restrictive decision wins (forbidden > prompt > allow).
Limits
A forbidden match is rejected before execution with no approval prompt, so the user cannot approve it at runtime, in every approval mode including --yolo (the orchestrator rejects Forbidden before any sandbox decision). The user can still delete user/project rules, and codex exec --ignore-rules skips user and project .rules files (not requirements rules). Matching is argv-prefix only and is easy to sidestep with an unsplit script (e.g. a redirect inside bash -lc) or another interpreter, so the OS sandbox stays the real boundary. Applies to shell commands only.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Policy TOML rule with decision="deny" (optional denyMessage). Example in /etc/gemini-cli/policies/deny.toml: [[rule]] toolName="run_shell_command" commandPrefix="rm -rf" decision="deny" priority=900 denyMessage="blocked by IT". Deprecated: tools.exclude ["run_shell_command(rm)"].
Limits
Deny is final: the call fails with no prompt, so the user cannot approve it at runtime. A deny in any part of a chained command blocks the whole command. Deny beats YOLO only when its tier is above the bundled yolo allow (tier 1, priority 998), so user and admin rules do. Prefix and regex matching is evadable (env vars, aliases, interpreters, other binaries), and the docs warn that blocklists are weaker than allowlists. A higher-tier allow overrides a lower-tier deny: a user rule can override nothing at admin tier, but anyone who can add admin-tier paths can.
Supported with important gaps (see limits)Supported with important gaps (see limits)
permission.bash pattern -> "deny", e.g. {"permission":{"bash":{"rm *":"deny","git push *":"deny"}}}. A deny returns an error to the model immediately.
Limits
The user is not prompted and cannot approve a denied call at runtime, and --auto does not override deny. Glob matching on command text is evadable (env vars, sh -c strings, interpreters, aliases, absolute paths). Because evaluation is last-match-wins over merged config, a later rule (project config, or a key a later layer appends such as "*": "allow") overrides an earlier deny. See repo_config_trust and managed_policy.
Supported with important gaps (see limits)Supported with important gaps (see limits)
IDE
No user-level hard command denylist is documented for the current (3.5+) Run Modes. Hard blocks come from (a) a beforeShellExecution or preToolUse hook returning {"permission":"deny"} or exiting 2, or (b) the team admin command denylist (dashboard, Enterprise). Auto-review block_instructions only steer a classifier.
CLI
permissions.deny, e.g. {"permissions": {"deny": ["Shell(rm)"]}}. Deny beats allow and beats --force/--yolo. The team admin denylist is also enforced in the CLI.
Team
Admin command denylist (Auto Run Configuration in the dashboard). Commands are rejected before execution, including on paths that skip approval prompts.
Limits
Can the user still approve at runtime? CLI deny and the admin denylist are documented as blocking, not prompting. A hook 'deny' blocks the call; no user override is documented. Auto-review block_instructions can still be approved when Cursor asks.
Hooks fail open on crash, timeout, or non-2 exit codes unless failClosed: true is set.
Evidence for the admin denylist comes from CLI and SDK changelog entries. The IDE run-modes page does not describe the denylist.
Supported with important gaps (see limits)Supported with important gaps (see limits)
VS Code
A false rule in chat.tools.terminal.autoApprove only forces a prompt; it does not block. To hard-block: a PreToolUse hook returning permissionDecision 'deny' (Local harness) or the Copilot hook format (Agent Host). Agent Host sessions also honour managed permissions.deny ["Shell(rm -rf *)"].
CLI
--deny-tool='shell(git push)' (session; beats --allow-all and saved approvals). Managed permissions.deny. preToolUse or permissionRequest hook deny. permissions-config.json cannot hold deny rules.
Cloud agent
preToolUse hook in .github/hooks/*.json returning deny. Custom agents can also restrict 'tools'.
Limits
Can the user approve a denied command at runtime? A managed deny blocks regardless of other rules and sources. --deny-tool means the tool 'cannot' be used. A VS Code 'false' rule is only a prompt.
No persistent user-level deny file exists in the CLI. Users get flags only, or managed rules from an admin.
Shell matching for managed rules is exact text or a '<command> *' prefix.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Cascade
No hard deny list. The 'deny list' (user and team) only forces approval. Hard blocking needs a pre_run_command hook that exits with code 2.
Devin local
permissions.deny, e.g. "Exec(rm)". Deny wins over every allow. Org deny rules cannot be overridden by project or user config and stay active in Smart, Bypass and Autonomous modes.
Limits
Can the user still approve a denied command at runtime? In Cascade, yes: the deny list only prompts. A pre_run_command exit-2 hook blocks it, and no override is documented. In Devin Local, deny blocks 'immediately'.
Cascade hook exit codes other than 2 let the action proceed (fail-open).
Always ask for commandsForce a prompt for specific commands, even in permissive modes.Supported as describedSupported as describedSupported as describedSupported with important gaps (see limits)Supported with important gaps (see limits)Supported with important gaps (see limits)Supported as described
Ask rules prompt even in acceptEdits, auto and bypassPermissions modes, and an ask beats a more specific allow. In dontAsk mode and in -p runs with no permission host the call is denied instead of prompted. Content-scoped ask rules still prompt for sandboxed commands, but a bare Bash ask rule is skipped for commands that run sandboxed under autoAllowBashIfSandboxed (default true), except in plan mode. Same text-matching evasion caveats as deny rules.
prefix_rule(pattern = ["git", ["push", "commit"]], decision = "prompt") in .rules, or requirements.toml [rules] prefix_rules with decision = "prompt".
Limits
The prompt decision is evaluated regardless of sandbox mode, so it still prompts under danger-full-access with on-request. Where prompting is impossible it fails closed: with approval_policy = "never" (including --yolo and codex exec's default) or granular rules = false, a matching command is rejected. With approvals_reviewer = "auto_review", eligible approval requests go to a reviewer agent instead of the user. Whether rule-triggered prompts are among them was not verified. Same prefix-matching weaknesses as deny_commands.
Policy TOML rule with decision="ask_user" at user or admin tier. Example: [[rule]] toolName="run_shell_command" commandPrefix="git push" decision="ask_user" priority=500. Tool-level only: settings tools.confirmationRequired ["write_file"] (priority 4.35, above allowed/core).
Limits
User and admin ask_user rules outrank the bundled YOLO allow-all (1.998), so they still prompt in yolo and auto_edit. In non-interactive (headless) mode ask_user becomes deny. tools.confirmationRequired takes plain tool names; the source code shows it does not parse run_shell_command(args), so it cannot target specific commands. A BeforeTool hook can also force a prompt by returning decision "ask".
Supported with important gaps (see limits)Supported with important gaps (see limits)
permission.bash pattern -> "ask", e.g. {"permission":{"bash":{"*":"allow","git push *":"ask"}}}
Limits
There is no rule type that forces a prompt in permissive mode. --auto (TUI toggle or opencode run --auto) has the client auto-reply 'once' to every ask, so ask rules are silently approved. The only way to guarantee a stop is deny. In opencode run without --auto, asks are auto-rejected.
Supported with important gaps (see limits)Supported with important gaps (see limits)
IDE
beforeShellExecution / beforeMCPExecution hooks returning {"permission":"ask"}. Auto-review autoRun.block_instructions in permissions.json: since 3.24, commands the classifier matches to a block instruction always show the approval card. Built-in protections (File-Deletion, External-File, Browser) force approval even when the mode would auto-run.
CLI
No 'ask' list in cli-config.json is documented. Hooks only.
Limits
preToolUse accepts 'ask' in its schema but does not enforce it. subagentStart treats 'ask' as 'deny'.
block_instructions are natural language read by an LLM classifier. They only apply in Auto-review mode.
Whether a hook 'ask' still prompts in Run Everything mode is not documented.
Supported with important gaps (see limits)Supported with important gaps (see limits)
VS Code
chat.tools.terminal.autoApprove false rules (under Manual permissions). chat.tools.eligibleForAutoApproval {"execute/runInTerminal": false} forces approval per tool, not per command; the ChatToolsEligibleForAutoApproval policy does the same. PreToolUse hooks can return 'ask'. Managed permissions.ask applies to Agent Host sessions.
CLI
Managed permissions.ask, e.g. {"permissions":{"ask":["Shell(git push *)"]}}. preToolUse hook permissionDecision 'ask'.
Cloud agent
None. 'ask' is treated as 'deny'.
Limits
Only the managed 'ask' survives auto-approve modes. A VS Code false rule does not survive Allow all or Autopilot, because those 'skip approval prompts'.
In CLI autopilot, a hook 'ask' becomes a deny because nobody is there to answer.
The CLI has no user-level 'ask' list (permissions-config.json does not support ask).
Rules for other toolsAllow, ask or deny for edit, write, read, web and sub-agent tools.Supported as describedSupported with important gaps (see limits)Supported as describedSupported as describedSupported with important gaps (see limits)Supported as describedSupported with important gaps (see limits)
Claude Code
Supported as describedSupported as described
Same allow/ask/deny arrays with tool-specific specifiers: Read(path) (Read, Grep, Glob, LSP), Edit(path) (Edit, Write, NotebookEdit), WebFetch(domain:host), WebSearch (bare only), Agent(SubagentName), Skill(name), mcp__server__tool, PowerShell(...), Cd(path). Deny/ask rules can match any top-level scalar input parameter, e.g. Agent(model:opus) or Bash(run_in_background:true); deny/ask accept tool-name globs such as "*" or "mcp__*". --tools restricts the built-in tool set. Example: {"permissions": {"deny": ["WebSearch", "Agent(Explore)", "Edit(//etc/**)"], "allow": ["WebFetch(domain:docs.python.org)"]}}
Limits
Path rules for Write, NotebookEdit, Glob or MultiEdit are accepted but never consulted (use Edit/Read). WebSearch has no domain specifier. WebFetch prompts by default except for a built-in set of preapproved documentation domains. A bare deny removes the tool from context; EndConversation cannot be denied while any other tool remains. Parameter rules cannot target a tool's primary field (command, file_path, url).
Supported with important gaps (see limits)Supported with important gaps (see limits)
No unified allow/ask/deny list for built-in non-shell tools was found. Separate per-surface switches: file edits (apply_patch) follow the sandbox + approval_policy (writes outside writable roots need approval); web_search = "disabled" | "cached" | "indexed" | "live"; tools.view_image = false; [features] multi_agent = false (sub-agents), shell_tool = false; MCP/app tools via approval_mode (see mcp_tool_rules); approval_policy = { granular = { sandbox_approval, rules, mcp_elicitations, request_permissions, skill_approval } } auto-rejects whole prompt categories; Browser Use / Computer Use have their own requirements. A PreToolUse hook can block any local function tool by name (e.g. matcher "apply_patch|Edit|Write").
Limits
No read-path or edit-path allow/deny for the edit tool other than sandbox write roots and permission profiles. Hosted tools such as web search cannot be intercepted by hooks. Granular approval toggles are by category, not by tool.
argsPattern is a regex over stable-stringified JSON, so path rules are brittle (relative vs absolute paths, escaping). Subagents are governed by treating the subagent name as a toolName, or with the subagent= field. Default rules allow read-only tools and ask for writes, shell, web_fetch and activate_skill.
permission keys: read, edit (covers write/edit/apply_patch), glob, grep, list, bash, task (pattern = subagent type), skill, lsp, question, webfetch, websearch, todowrite, external_directory, doom_loop, plus any custom/MCP tool name. Each is a string action or a {pattern: action} map. Example: {"permission":{"edit":{"*":"ask","docs/*.md":"allow"},"task":{"*":"deny","explore":"allow"},"webfetch":"ask"}}. Also per agent, and the legacy tools: {name: false} maps to deny.
Limits
Defaults are permissive: "*": "allow" for build/general agents, ask only for doom_loop, external_directory and *.env reads; question/plan tools are agent-specific. The config schema types webfetch, websearch, question, todowrite and doom_loop as a single action (no patterns), so per-URL webfetch rules cannot be expressed in config even though the tool passes the URL as the pattern. A tool whose last "*"-pattern rule is deny is hidden from the model.
Supported with important gaps (see limits)Supported with important gaps (see limits)
IDE
No general per-tool allow/ask/deny file. Available pieces: mcpAllowlist (MCP auto-run), Fetch and MCP calls go through Run Modes, Browser Protection, File-Deletion Protection, External-File Protection, Read Access (System|Workspace) with a Read Allowlist, .cursor Directory Protection and Browser origin allowlist (Enterprise dashboard), and preToolUse hooks with matcher Shell|Read|Write|Grep|Delete|Task|MCP:<tool>. subagentStart hooks can deny sub-agents.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Cascade
No general per-tool allow/deny. Pieces available: the admin 'Web search' toggle (when off, the search tool is removed server-side and every page read needs manual approval); page-read approvals ('Always allow this page', 'Allow all requests'); MCP server toggles and disabledTools; the admin 'Restrict Cascade to workspace'; pre_read_code, pre_write_code and pre_mcp_tool_use hooks; Chat mode (no edits).
Devin local
Typed rules: Read(glob), Write(glob), Exec(prefix), Fetch(URLPattern), mcp__server__tool, and tool names read/edit/grep/glob/exec, each in allow/ask/deny. Admin default subagent model can be set to 'none' to disable subagents.
Limits
Cascade file edits need no per-call approval. They appear as diff zones to accept or reject afterwards.
Deny file readsStop the agent reading paths such as .env or ~/.ssh.Supported as describedSupported as describedSupported with important gaps (see limits)Supported with important gaps (see limits)Supported with important gaps (see limits)Supported with important gaps (see limits)Supported with important gaps (see limits)
Claude Code
Supported as describedSupported as described
permissions.deny Read rules (gitignore syntax; // = absolute, ~/ = home, / = relative to settings source). Example: {"permissions": {"deny": ["Read(./.env)", "Read(./.env.*)", "Read(~/.ssh/**)"]}}. With sandbox.enabled true, Read deny rules are also added to the sandbox's denyRead list, so they become OS-level for shell commands too; sandbox-only keys: sandbox.filesystem.denyRead or sandbox.credentials.files [{"path": "~/.ssh", "mode": "deny"}] with sandbox.enabled true. permissions.blockReadsOutsideWorkingDirectories: true refuses file-tool reads outside working directories in every mode.
Limits
Shell reads are stopped at OS level only when the sandbox is on (sandbox.enabled, off by default, unavailable on native Windows). Without it, Read deny rules cover Read/Grep/Glob/LSP, @-mentions, IDE-shared context, Edit/Write on the same path, Bash file commands Claude Code recognizes (cat, head, tail, sed, tee) and < redirect targets, but NOT grep -r pattern . run from the directory or any script/subprocess that opens files itself. Even with the sandbox on, excludedCommands, approved unsandboxed retries (unless allowUnsandboxedCommands is false), MCP servers and hooks are outside it. The sandbox's default read policy allows the whole machine, including ~/.ssh and ~/.aws/credentials, so nothing is read-denied until you configure it.
Enforced by the OS sandbox, so on macOS and Linux it also stops shell reads such as cat ~/.ssh/id_rsa by spawned commands. Nothing is denied by default: the built-in :read-only and :workspace profiles grant read on the whole filesystem (:root). Not enforceable under danger-full-access or --yolo; with managed deny_read present the runtime refuses full access and keeps commands sandboxed even for allow rules or approved escalations. On native Windows, managed deny_read covers direct file tools only, not shell subprocess reads. Profiles cannot be combined with legacy sandbox_mode; if sandbox_mode appears anywhere, profiles are ignored.
Supported with important gaps (see limits)Supported with important gaps (see limits)
(a) Built-in file tools can only read inside the workspace dirs (cwd + context.includeDirectories / --include-directories) and the project temp dir, so ~/.ssh is out of reach unless included. (b) .geminiignore / context.fileFiltering.customIgnoreFilePaths: read_file refuses ignored paths. (c) Policy deny with argsPattern, e.g. [[rule]] toolName=["read_file","read_many_files"] argsPattern='\.env' decision="deny". (d) With tool sandboxing on (security.toolSandboxing=true or a sandbox), .geminiignore'd paths become sandbox forbidden paths.
Limits
Without a sandbox, none of these stop shell reads (e.g. cat .env or cat ~/.ssh/id_rsa through run_shell_command); you would need separate shell deny rules, which are evadable. With a sandbox, the seatbelt default profile (permissive-open) still allows broad reads, while strict-* profiles restrict reads to the workspace. The project controls .geminiignore, so a repo can also un-ignore files.
Supported with important gaps (see limits)Supported with important gaps (see limits)
permission.read patterns (matched against the path relative to the worktree), e.g. {"permission":{"read":{"*":"allow","*.env":"deny","secrets/*":"deny"}}}. For paths outside the project use external_directory with absolute or ~ globs, e.g. {"permission":{"external_directory":{"~/.ssh/*":"deny"}}}.
Limits
Defaults: *.env and *.env.* reads ASK (the docs say 'denied by default'; v1.19.0 and installed 1.18.19 source/binary use ask). The read permission does not stop shell reads: cat .env is a bash permission check. The shell's external_directory check covers only file arguments of a fixed command list (cd, rm, cp, mv, mkdir, touch, chmod, chown, cat, PowerShell *-Item/*-Content). Commands like head ~/.ssh/id_rsa, less, python -c, or redirections are not path-checked (source). In the source code, read patterns are relative paths, so a ~/.ssh/* pattern under read would not match; use external_directory instead (inferred, not tested).
Supported with important gaps (see limits)Supported with important gaps (see limits)
IDE
.cursorignore at the repo root (gitignore syntax), optional Hierarchical Cursor Ignore, a global ignore list in user settings, and team 'Cursor Ignore Configuration' (Enterprise). .gitignore'd files and a default list (including .env*) are ignored. A beforeReadFile hook can deny reads, and receives the file content. Read Access 'Workspace' (3.23+) or sandbox.json {"readBoundary":"workspace"} asks before reads outside the workspace.
CLI
permissions.deny Read(glob), e.g. "Read(.env*)". sandbox.readBoundary in cli-config.json.
Limits
Terminal reads: not stopped. .cursorignore does not apply to terminal or MCP tools.
Read Access 'Workspace' limits only what sandboxed commands read outside the workspace. It does not hide in-workspace secrets.
Not documented: whether CLI Read() deny rules apply to shell commands.
Cursor calls .cursorignore 'not a security boundary'.
Supported with important gaps (see limits)Supported with important gaps (see limits)
VS Code
Copilot content exclusion (org/enterprise/repo settings on github.com) is NOT supported in VS Code agent mode. Built-in agent tools are limited to the workspace (plus chat.additionalReadAccessFolders). Agent Host: managed permissions.deny ["Read(**/.env)"] and sandbox deniedPaths (terminal, when sandbox is on). PreToolUse hooks.
CLI
Content exclusion: docs conflict (see limits). --deny-tool='read(.env)'. Managed Read(...) deny. sandbox.userPolicy.filesystem.deniedPaths for sandboxed commands. preToolUse hook on 'view'.
Cloud agent
Not documented in the pages reviewed (unknown).
Limits
Terminal reads: content exclusion and Read() tool rules are not documented to cover shell reads. Only the OS sandbox deniedPaths (sandbox on) blocks a cat of a denied path.
The content exclusion docs contradict each other on Copilot CLI: one page says CLI and IDE agent mode 'do not support' it, another says the CLI 'respects' it.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Cascade
.devinignore at the repo root (gitignore syntax; legacy .codeiumignore and .windsurfignore still read) and a global ~/.codeium/.codeiumignore. .gitignore'd paths are not indexed or editable ('Gitignore access' is off by default). pre_read_code hook (exit 2 blocks; also fires for directory reads).
Devin local
Read(...) deny rules. With the sandbox on, these also hide the paths from sandboxed shell commands.
Limits
Terminal reads (Cascade): not documented whether .devinignore or pre_read_code stops cat in the terminal. The hook only covers Cascade's own read tool.
Devin Local: Read() deny reaches shell commands only when the OS sandbox is enabled.
Limit file writesRestrict writes to the workspace or to listed paths.Supported as describedSupported as describedSupported with important gaps (see limits)Supported with important gaps (see limits)Supported with important gaps (see limits)Supported as describedSupported with important gaps (see limits)
Claude Code
Supported as describedSupported as described
File tools: writes outside the working directory and additionalDirectories prompt in Manual/acceptEdits; Edit allow/deny rules (e.g. "deny": ["Edit(//etc/**)"]); protected paths (.git, .claude, .vscode, shell rc files, .mcp.json, etc.) never auto-approved outside bypassPermissions. Shell: with sandbox.enabled true, writes are OS-limited to cwd, a per-user temp dir and added directories; widen with sandbox.filesystem.allowWrite, narrow with denyWrite. --restricted confines file tools to the working directories.
Limits
Shell writes are confined at OS level only with the sandbox on (off by default; unavailable on native Windows; falls back to unsandboxed if it cannot start unless sandbox.failIfUnavailable is true). Without it, an approved Bash command can write anywhere the user can. For file tools the working-directory boundary is a prompt, not a hard block, and it is skipped in bypassPermissions; hard path blocks need Edit deny rules or --restricted. Claude may retry a failed command unsandboxed (dangerouslyDisableSandbox) unless allowUnsandboxedCommands is false, and excludedCommands run with full access.
sandbox_mode = "workspace-write" (cwd/workspace roots plus $TMPDIR and /tmp unless excluded) with [sandbox_workspace_write] writable_roots = ["/path"], exclude_tmpdir_env_var, exclude_slash_tmp; --add-dir <DIR>; sandbox_mode = "read-only"; or permission profiles with per-path "read" | "write" | "deny". Requirements: allowed_sandbox_modes, allowed_permission_profiles (0.138.0+), managed default_permissions.
Limits
Inside writable roots, .git (including a resolved gitdir pointer), .agents and .codex stay read-only; source also lists .aws as protected metadata. apply_patch edits outside the writable roots go to an approval prompt rather than being blocked. With approval_policy never, or granular sandbox_approval = false, they are rejected. An approved escalation or an execpolicy allow rule runs the command unsandboxed. No write restriction under danger-full-access.
Supported with important gaps (see limits)Supported with important gaps (see limits)
write_file / replace validate that paths are inside the workspace dirs or the project temp dir. Narrow further with policy argsPattern on file_path. For shell writes, enable the OS sandbox: tools.sandbox=true|"sandbox-exec"|"docker"... (seatbelt default confines writes to the project dir), or security.toolSandboxing=true, plus tools.sandboxAllowedPaths for extra paths.
Limits
Shell commands can write anywhere the user can unless a sandbox is on, and it is off by default. In YOLO mode the tool-level sandbox managers grant workspace write and network. Sandbox expansion requests let the user widen access per command at runtime.
Supported with important gaps (see limits)Supported with important gaps (see limits)
permission.edit patterns (relative to the worktree) plus external_directory for anything outside the start directory. Example: {"permission":{"edit":{"*":"deny","src/**":"allow"},"external_directory":{"*":"deny"}}}. The plan agent denies edits except plan files.
Limits
Covers the edit/write/apply_patch tools. Shell writes are only covered by bash rules and the partial external_directory scan (e.g. echo x > ~/.bashrc is not path-checked, according to the source code). There is no OS-level enforcement. external_directory defaults to ask, and --auto approves it.
Supported with important gaps (see limits)Supported with important gaps (see limits)
IDE
External-File Protection prompts before creating, changing, or deleting files outside the workspace. Sandboxed commands get workspace_readwrite by default; sandbox.json type can be "workspace_readonly" and additionalReadwritePaths adds paths. A hardcoded write-protected list applies inside the sandbox (.cursor/*.json, .vscode/**, .git/hooks/**, .git/config, .cursorignore). Enterprise .cursor Directory Protection.
CLI
permissions Write(glob) allow/deny, e.g. "Write(src/**)" or deny "Write(**/.env*)".
Limits
The sandbox only covers commands that run inside it. Run Everything does not use the sandbox (3.24).
Per-repo sandbox.json additionalReadwritePaths are unioned with the user's paths, so a repo can widen write access for sandboxed commands.
Built-in tools write only inside the workspace. chat.tools.terminal.blockDetectedFileWrites defaults to 'outsideWorkspace' (prompts for detected terminal writes outside the workspace). Agent Host sandbox readwritePaths/readonlyPaths/deniedPaths. Managed Edit() deny.
CLI
A path gate covers trusted dir, --add-dir and allowed_directories; --allow-all-paths disables it. --deny-tool='write(PATH)'. Managed Edit()/Write() rules, which also catch recognised shell redirections and sed -i. Sandbox readwrite lists. /plan blocks edits.
Cloud agent
Ephemeral runner. It can push only to its own copilot/ branch (or the PR branch it was invoked on), subject to branch protection.
Limits
VS Code terminal write detection is best-effort. CLI write rules on shell commands only work when the target path can be resolved before the command runs.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Cascade
Admin 'Restrict Cascade to workspace' (Teams & Enterprise) limits reads and writes to the open workspace. .devinignore and .gitignore paths cannot be edited. pre_write_code hook.
Devin local
By default edits only inside the workspace (Accept Edits auto-approves only there). Write(glob) rules. The sandbox writable roots come from permission scopes.
Limits
Cascade terminal commands are not path-restricted. There is no Cascade sandbox.
The 'Restrict Tool Calls to Workspace' control is not supported for Devin Local.
Network controlBlock or allow-list network access for commands and web tools.Supported as describedSupported as describedSupported with important gaps (see limits)Supported with important gaps (see limits)Supported with important gaps (see limits)Supported as describedSupported with important gaps (see limits)
Claude Code
Supported as describedSupported as described
Shell commands: sandbox.network.allowedDomains / deniedDomains (exact hosts, leading *. wildcards, IPs, optional :port) enforced by a local proxy; sandbox.network.strictAllowlist (user/managed/--settings) and managed-only sandbox.network.allowManagedDomainsOnly refuse unlisted hosts without prompting. WebFetch tool: WebFetch(domain:...) allow/ask/deny rules (also merged into the sandbox lists). WebSearch: whole-tool allow/deny only. Example: {"sandbox": {"enabled": true, "network": {"allowedDomains": ["github.com", "*.npmjs.org"]}}, "permissions": {"deny": ["WebSearch"]}}
Limits
Requires the sandbox (off by default). The proxy decides on hostname and does not inspect TLS by default, so domain fronting is possible; tools that ignore proxy variables (ssh, DB drivers, UDP) simply cannot connect. Without the sandbox, network control for shell is only command-text deny rules such as Bash(curl *). allowedDomains does not restrict WebFetch, and MCP servers, hooks and excludedCommands are outside the sandbox. In bypassPermissions, hosts outside the allowlist are allowed unless strictAllowlist/allowManagedDomainsOnly is on. Hosts resolving to local/link-local addresses (e.g. 169.254.169.254) are refused by default.
Domain rules do nothing unless the proxy is on (features.network_proxy or managed experimental_network). Network on with the proxy off means unrestricted direct egress. The proxy filters only sandboxed commands, not web search, MCP servers, apps/connectors, browser, Computer Use or Codex's own traffic. An approved full sandbox escalation can bypass the proxy. DNS-rebinding protection is best effort. Under --yolo / danger-full-access, web search defaults to live. Codex Cloud has separate per-environment internet settings.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Shell: tools.sandboxNetworkAccess (bool, default false; applies to the sandbox) and SEATBELT_PROFILE=*-proxied / restrictive-* on macOS. The tool sandbox's default policy has network=false per mode. Web tools: web_fetch blocks private, loopback and reserved IPs and pins the resolved IP. Allow/ask/deny web_fetch and google_web_search via policy, and regex-match URLs with argsPattern on the prompt arg. Example: [[rule]] toolName="web_fetch" decision="deny" priority=500. Browser agent: agents.browser.allowedDomains ["github.com","*.google.com"].
Limits
There is no domain allowlist for shell or web_fetch. With no sandbox (the default), shell has full network access. On macOS, the default seatbelt profile (permissive-open) allows network. In YOLO mode the tool-level sandbox grants network. web_fetch domain control is a regex over a free-text prompt containing URLs, so it is weak. google_web_search runs server-side.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Web tools only: {"permission":{"webfetch":"deny","websearch":"deny"}} (or ask). Proxies via HTTPS_PROXY / HTTP_PROXY / NO_PROXY and NODE_EXTRA_CA_CERTS.
Limits
There is no network control for shell commands (no sandbox). There is no domain allowlist: webfetch/websearch accept only a single action in config. Proxy env vars affect OpenCode's own HTTP traffic and whatever child processes honour them; they are not enforcement. No private-IP blocking was found in the webfetch tool.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Sandboxed terminal
Network modes: 'sandbox.json Only', 'sandbox.json + Defaults' (default), 'Allow All'. sandbox.json: {"networkPolicy": {"default": "deny", "allow": ["registry.npmjs.org"], "deny": ["*.internal.corp.example.com"]}}. Domains, wildcards, CIDR. RFC1918, loopback, and cloud metadata are blocked by default.
Team
A team-admin network allowlist replaces the user/repo union. The CLI changelog says admin network allow/deny lists are enforced unconditionally, including MCP traffic. Per-MCP-server network mode (Allow all / Allowlist / Deny all / No sandbox) and a browser origin allowlist (Enterprise).
Web tools
IDE Fetch calls go through Run Modes (allowlist or classifier). CLI: WebFetch(domain) allow/deny; without an allow entry each fetch prompts.
Limits
Only sandboxed shell commands are network-filtered. Allowlisted commands, commands the Auto-review classifier allows, approved escapes, and Run Everything all run outside the sandbox.
The default network mode adds roughly 100 built-in domains, including github.com, google.com, *.googleapis.com and public blob storage. These are usable exfiltration channels.
A repo's sandbox.json allow entries are unioned in, unless a team-admin allowlist is present.
URL two-step approval (request and response) via chat.tools.urls.autoApprove and Trusted Domains. chat.agent.networkFilter with chat.agent.allowedNetworkDomains / deniedNetworkDomains (fetch tool, integrated browser; terminal coverage varies). Agent Host sandbox: chat.agent.sandbox.network.allowNetwork (default true), allowedDomains / deniedDomains. Policies: ChatAgentNetworkFilter, ChatAgentAllowedNetworkDomains, ChatAgentDeniedNetworkDomains.
CLI
--allow-url / --deny-url, allowedUrls / deniedUrls (url rules also cover URLs detected in shell commands). Sandbox allowedHosts / blockedHosts through a local proxy. Managed Domain() rules and permissions.limitTo, e.g. {"permissions":{"limitTo":["github.com","*.github.com"]}}. web_fetch blocks private, loopback, and metadata IPs.
Cloud agent
Firewall on by default with a recommended allowlist. Org and repo 'Internet access' settings: Enabled / Disabled / Let repositories decide; custom allowlists of domains or URL prefixes; orgs can stop repos adding rules.
Limits
VS Code sandbox outbound network is on by default.
Windows CLI sandbox host rules depend on programs honouring proxy settings.
Cloud agent firewall: covers only processes the agent's Bash tool starts (not MCP servers or setup steps). Bypass is possible. Not compatible with self-hosted runners or Windows.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Cascade
Web tools only: the admin 'Web search' toggle (disabled by default for enterprise teams) and per-page read approvals. No documented network control for Cascade terminal commands.
Devin local
Sandbox network filtering with domain allow and deny lists. Org 'Sandbox domains' is authoritative and replaces user allowed_domains. Fetch(URLPattern) rules.
Limits
Cascade page reads run locally on the user's machine.
OS sandboxRun agent commands inside an operating-system or container sandbox.Supported as describedSupported as describedSupported as describedNot supportedSupported as describedSupported as describedSupported with important gaps (see limits)
Claude Code
Supported as describedSupported as described
Built-in Bash sandbox on @anthropic-ai/sandbox-runtime: macOS Seatbelt; Linux and WSL2 bubblewrap + socat (optional seccomp filter for Unix sockets); native Windows not supported. Off by default. Keys: sandbox.enabled, failIfUnavailable, autoAllowBashIfSandboxed (default true), allowUnsandboxedCommands (default true), excludedCommands, filesystem.{allowWrite,denyWrite,denyRead,allowRead,disabled,allowManagedReadPathsOnly}, network.{allowedDomains,deniedDomains,strictAllowlist,allowManagedDomainsOnly,...}, credentials.{files,envVars} (deny or mask), enableWeakerNestedSandbox, enableWeakerNetworkIsolation, allowAppleEvents. Example managed enforcement: {"sandbox": {"enabled": true, "failIfUnavailable": true, "allowUnsandboxedCommands": false}}
Limits
Covers Bash, PowerShell and Monitor commands and their children only; file tools, WebFetch/WebSearch, MCP servers, hooks, LSP servers, status line and apiKeyHelper run with full user access. ! commands typed by the user run unsandboxed in most sessions. Silent fallback to unsandboxed if dependencies are missing unless failIfUnavailable. Project files cannot set filesystem.disabled, allowAppleEvents, strictAllowlist or credential masking, but can set excludedCommands, allowedDomains and the weaker-isolation flags unless the sandbox is admin-required (allowUnsandboxedCommands false via managed/--settings, or allowManagedDomainsOnly). Whole-process isolation options: sandbox-runtime wrapper, dev container, cloud sessions in Anthropic VMs.
sandbox_mode = "read-only" | "workspace-write" | "danger-full-access" (-s/--sandbox), or default_permissions = ":read-only" | ":workspace" | ":danger-full-access" | custom profile. macOS: Seatbelt via sandbox-exec. Linux/WSL2: bubblewrap + seccomp, Landlock for compatibility fallback; bundled bwrap helper needs unprivileged user namespaces. Windows: MXC ([features] prefer_mxc = true) with legacy [windows] sandbox = "elevated" | "unelevated". Test with codexsandboxmacos|linux|windows--<cmd>.
Limits
Defaults: if no sandbox_mode is set, a trusted or explicitly untrusted project gets workspace-write (read-only on Windows without MXC); a directory with no trust decision gets read-only. danger-full-access and --yolo disable the sandbox completely. Inside Docker the Linux sandbox may not work, and the docs suggest danger-full-access with the container as the boundary. On macOS, a policy the platform can't enforce is refused rather than run unsandboxed. Codex Cloud runs in OpenAI-managed containers instead.
Whole-process sandbox: tools.sandbox = true | "sandbox-exec" | "docker" | "podman" | "runsc" | "lxc" | "windows-native" | {enabled, command, image, allowedPaths, networkAccess}; or GEMINI_SANDBOX env or --sandbox/-s. macOS seatbelt profiles via SEATBELT_PROFILE (permissive-open default, permissive-proxied, restrictive-open/proxied, strict-open/proxied, or a custom .gemini/sandbox-macos-<name>.sb). Tool-level sandbox: security.toolSandboxing=true (Linux, macOS and Windows managers).
Limits
Off by default. The configuration reference says YOLO enables the sandbox by default, but in v0.63.0 sandboxConfig.ts nothing turns it on for yolo (source-checked). Env overrides settings, so a user, or a trusted project's .env (according to the source code), can set GEMINI_SANDBOX=false. A project can supply its own seatbelt profile or sandbox.Dockerfile. Sandbox expansion prompts can widen access. Docs warn the sandbox reduces but does not remove risk.
OS sandbox for agent terminal commands: macOS Seatbelt (sandbox-exec, Cursor 2.0+); Linux Landlock + seccomp (kernel 6.2+, unprivileged user namespaces). Auto-review (the default since 3.6) sandboxes non-allowlisted shell commands when it can. Allowlist mode sandboxing is optional. Run Everything has no sandbox. Configure via ~/.cursor/sandbox.json and <workspace>/.cursor/sandbox.json, e.g. {"type": "workspace_readonly", "disableTmpWrite": true}.
CLI
--sandbox enabled|disabled, agentsandboxenable|disable|run, cli-config sandbox.mode / sandbox.networkAccess. The CLI fails fast when sandboxing is enabled but unavailable.
Team
Admins set Sandboxing Mode, Sandbox Networking, Sandbox Git Access, and Read Controls. For Org Groups the loosest setting wins.
Cloud agents
Cloud Agents run in dedicated VMs, auto-run commands, and do not use Run Modes. Egress is set on the Cloud Agents dashboard.
Limits
On Linux kernels without the requirements, Cursor falls back to asking for approval instead of sandboxing.
A sandboxed command that fails on a sandbox restriction can be rerun outside the sandbox after classifier review (Auto-review).
Commands that need full access bypass the sandbox with an approval prompt.
Windows sandbox support is not documented.
The llm-safety page still says there is no boundary between agents and the user account. That predates or contradicts the run-modes page.
Agent Host sandbox: chat.agent.sandbox.enabled 'off' (default) or 'on'. Covers terminal commands and, by default, Agent Host-launched MCP and LSP servers. chat.agent.sandbox.allowUnsandboxedCommands defaults to true. Per-session toggle. Managed sandbox.enabled locks it (1.138+/1.140+ per GitHub docs). Legacy Local-harness sandbox settings and policies are deprecated.
CLI
OS sandbox through Microsoft eXecution Container: Seatbelt (macOS), bubblewrap (Linux), ProcessContainer (Windows 11). sandbox.enabled defaults to false. Enable with /sandbox enable or --sandbox. sandbox.allowBypass defaults to true. Managed floor: enabled, failIfUnavailable, allowBypass:false, sandboxMcpServers. Optional cloud sandboxes (preview, billed, off by default for orgs).
Cloud agent
Each job runs in an ephemeral GitHub Actions environment behind the agent firewall.
Limits
Off by default locally in both the CLI and VS Code.
Users can disable the sandbox for the rest of a session from a bypass prompt unless managed allowBypass is false.
CLI built-in file tools run inside the CLI process and only honour the policy 'best-effort'.
VS Code docs call agent sandboxing Preview (Experimental on Windows), and say the managed sandbox key is not listed for VS Code. The GitHub reference lists it as supported for Agent Host 1.138+.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Cascade
None documented. OS sandboxing is listed as a Devin Local improvement over Cascade, and the admin 'Devin Sandbox' control covers Devin CLI and Devin Local only.
Devin local
Optional OS-level sandbox (filesystem isolation + network filtering), Autonomous permission mode via --sandbox. Org 'Devin Sandbox' setting: Optional (default) or Required (Enterprise). Fails closed and refuses to start if the sandbox can't be set up. Not supported on Windows.
Limits
Sandbox is optional by default. Windows sessions hard-fail when the sandbox is Required.
Approval modesThe modes and flags that decide when the agent asks, including bypass modes.Supported as describedSupported as describedSupported as describedSupported as describedSupported as describedSupported as describedSupported as described
Claude Code
Supported as describedSupported as described
Modes (config value / UI label): default (Manual; alias manual), acceptEdits, plan, auto (classifier model reviews actions), dontAsk (anything that would prompt is denied), bypassPermissions. Set with permissions.defaultMode, --permission-mode <mode>, --dangerously-skip-permissions (= bypassPermissions), --allow-dangerously-skip-permissions (adds bypass to the Shift+Tab cycle), --permission-prompts none (-p: deny anything that would prompt), --permission-prompt-tool, --restricted. VS Code: claudeCode.initialPermissionMode and claudeCode.allowDangerouslySkipPermissions. Built-in default since v2.1.283: auto for interactive terminal and VS Code sessions; -p/SDK default is default (auto in sessions that do not fetch feature flags, v2.1.285+). Example: {"permissions": {"defaultMode": "acceptEdits"}}
Limits
bypassPermissions still honors deny rules, ask rules, critical-path rm prompts and requiresUserInteraction tools, but allow rules are irrelevant and protected-path writes are allowed. It refuses to start as root/sudo on Linux/macOS outside a recognized sandbox. First interactive use shows a one-time acceptance dialog that writes skipDangerousModePermissionPrompt to ~/.claude/settings.json. Auto mode is the default and relies on a classifier; the docs warn it does not guarantee safety.
approval_policy = "on-request" (default; alias on-failure is deprecated) | "never" | { granular = { sandbox_approval, rules, mcp_elicitations, request_permissions, skill_approval } }. The explicit value "untrusted" is retired since 0.149.0 and rejected at load; it survives only as the internal policy for projects marked trust_level = "untrusted". approvals_reviewer = "user" | "auto_review" (CLI --approve-for-me, alias --not-so-yolo). CLI: -a/--ask-for-approval on-request|never; -s/--sandbox read-only|workspace-write|danger-full-access; --dangerously-bypass-approvals-and-sandbox (alias --yolo) = danger-full-access + never; --profile <name>; /permissions in the TUI. codex exec defaults to approval never. --full-auto was removed from codex exec in 0.147.0 and does not exist in source at HEAD or at rust-v0.162.1, although the security docs still call it a deprecated compatibility path.
Limits
Can a project-level config set the most permissive mode? YES, once the project is trusted. Neither approval_policy nor sandbox_mode (nor default_permissions or [permissions] profiles) is on the project-local denylist, and the project layer (25) outranks user config (20). So a trusted repo's .codex/config.toml can set approval_policy = "never" plus sandbox_mode = "danger-full-access". Only CLI flags (30), legacy managed config and requirements win over it. Before trust the project layer is disabled.
Modes: default (prompt), auto_edit (auto-approves write_file, replace, web_fetch; shell still asks), plan (read-only), yolo (allow-all rule at default tier). Set by --approval-mode=default|auto_edit|plan|yolo, --yolo/-y, settings general.defaultApprovalMode (default|auto_edit|plan only), and at runtime Shift+Tab (cycles default/auto_edit/plan) and Ctrl+Y (toggles yolo). Related: security.enablePermanentToolApproval ('Allow for all future sessions', persisted to ~/.gemini/policies/auto-saved.toml), security.disableAlwaysAllow.
Limits
Can a project config set the most permissive mode? Not yolo: the code ignores defaultApprovalMode="yolo" from any settings file. A trusted project can set auto_edit, and can get yolo-equivalent behaviour another way: tools.allowed ["run_shell_command"] or tools.core, mcpServers.<x>.trust, mcp.allowed. In an untrusted folder the mode is forced to default, and Ctrl+Y/yolo is refused.
Two client-side modes: normal (ask rules prompt) and auto (--auto on opencode or opencode run; hidden aliases --yolo and --dangerously-skip-permissions; TUI command palette 'Enable auto-approve permissions'). Headless opencode run without --auto auto-rejects asks. Agents act as modes: build (default, permissive) and plan (edits denied), selected with --agent or default_agent. Prompt replies are once | always (session-scoped prefix approvals) | reject.
Limits
Can a project config set the most permissive mode? There is no config key for --auto, but none is needed: the default ruleset is already "*": "allow", and a project can set "permission": "allow" (normalised to {"*": "allow"}), set default_agent, or define a permissive agent. Auto mode still honours deny.
Settings > Agents > Approvals & Execution: Auto-review (default since 3.6; allowlist, then sandbox, then LLM classifier on the Cursor backend), Allowlist (optional sandbox; an empty allowlist reproduces the deprecated 'Ask Every Time'), Run Everything (no prompts, no sandbox). Separate Read Access: System (default) or Workspace.
CLI
approvalMode in ~/.cursor/cli-config.json: "allowlist" | "auto-review" | "unrestricted". Flags: -f/--force and --yolo (Run Everything; also auto-approves trust and MCP), --auto-review, --approve-mcps, --trust, --sandbox. Slash commands /auto-run and /auto-review. -p/--print has access to write and shell tools.
Cloud agents
Always auto-run; no approval modes.
Limits
Auto-review is explicitly not a security boundary, and its classifier needs Gemini 3.5 Flash Lite or Claude 4.5 Haiku to be allowed by model access control.
The CLI offers to persist Run Everything after repeated use.
Auto-execution levels in the Devin Settings panel: Disabled, Allowlist Only, Auto (model judges; premium models only), Turbo (everything except the deny list). The admin controls page calls them 'No auto-approvals, Allowlist only, Smart, Bypass'. Web page reads: 'Allow all requests'. Auto-Continue (devin.autoContinue in settings.json) removes the per-prompt invocation cap.
Devin local
Permission modes: Normal (default), Accept Edits, Smart (LLM judge with hard exclusions), Bypass (all auto-approved), Autonomous (only with sandbox). Set via --permission-mode, DEVIN_PERMISSION_MODE, the mode selector, or Shift+Tab.
Limits
The same levels appear under two different sets of names across pages (Turbo/Bypass, Auto/Smart).
Lock out bypass modesLet an admin turn bypass or yolo modes off so users and projects cannot turn them on.Supported as describedSupported as describedSupported with important gaps (see limits)Not supportedSupported as describedSupported as describedSupported with important gaps (see limits)
Claude Code
Supported as describedSupported as described
permissions.disableBypassPermissionsMode: "disable" (rejects --dangerously-skip-permissions, ignores agent frontmatter permissionMode: bypassPermissions, makes hook setMode bypass a no-op) and permissions.disableAutoMode (or top-level disableAutoMode): "disable". Put them in managed settings so lower scopes cannot remove them. Example: {"permissions": {"disableBypassPermissionsMode": "disable", "disableAutoMode": "disable"}}. --restricted (or CLAUDE_CODE_RESTRICTED=1 in the launch environment) also refuses bypassPermissions. Desktop on Team/Enterprise: organization policy controls the bypass toggle.
Limits
Client-side control. A local administrator can edit the managed file/profile; server-managed settings are not fetched for third-party providers (Bedrock/Vertex/Foundry), a non-default ANTHROPIC_BASE_URL, or a login to another organization, and only apply to clients new enough to fetch them; a modified binary bypasses everything. HKCU registry policy is user-writable. Before v2.1.223 an agent definition's bypassPermissions ignored the org disable policy.
A disallowed configured value falls back to an allowed one with a startup warning. It is a hard error when full access is disallowed and the fallback would leave read-only with approval never. Cloud-managed requirements reach only users signed in with ChatGPT on a supported plan. Login restrictions (allowed_login_methods, allowed_chatgpt_workspaces) can be set only through the local system file or MDM, so cloud-only lockout can be sidestepped by API-key or personal-account login unless local requirements pin login. If the cloud fetch fails with no valid cache, loading errors out instead of starting without the layer.
Supported with important gaps (see limits)Supported with important gaps (see limits)
(1) security.disableYoloMode=true in the system settings file: --yolo then exits with a fatal error and Ctrl+Y is refused. (2) Enterprise remote admin control 'Strict Mode' (admin.secureModeEnabled, default enabled when admin controls apply) blocks yolo and 'Always allow'. (3) Admin-tier TOML in the root-owned system policies dir: deny/ask rules there outrank the yolo allow-all, e.g. [[rule]] toolName="*" decision="ask_user" modes=["yolo"] priority=999. (4) security.disableAlwaysAllow=true.
Limits
disableYoloMode is an ordinary boolean merged with system last. It is only a lock in the system file, and users can repoint that file with GEMINI_CLI_SYSTEM_SETTINGS_PATH (docs suggest a wrapper script). Remote Strict Mode needs Code Assist auth plus an experiment flag. The system policy dir path is fixed and permission-checked, making it the strongest local lock, but it does not stop auto_edit or a project granting allows below the admin tier.
No setting disables --auto or the TUI auto-approve toggle. The nearest control is managed config deny rules (managed files or macOS MDM), which auto mode still honours.
Limits
Auto mode is a client behaviour (auto-replying 'once'), so admins cannot turn it off. Admins can only convert critical rules to deny. Even then, the source code shows that OPENCODE_PERMISSION is merged after managed config (user-overridable), and a key-ordering quirk lets later-appended wildcard keys outrank managed rules (see managed_policy).
Team dashboard Run Mode policy (Auto Run Configuration, listed under Enterprise-only settings) sets which modes members can choose. Admins can gate Auto-review and disable headless CLI usage. Admin-controlled allowlists replace user and repo lists.
CLI
Team-configured approval modes are respected in headless runs (CLI changelog). Admins can block non-interactive CLI usage.
Mdm
None for Run Mode. MDM covers only workspace trust, extensions, team ID, updates, and HTTP/2.
Limits
With Organization Groups, run-mode availability is a union. If the team or any applicable group enables Run Everything, the member gets it.
Plan level: the dashboard lists Auto Run Configuration under Enterprise-only settings. Teams-plan availability is unclear.
This is enforced by the logged-in client. A personal account on the same machine escapes it unless MDM AllowedTeamId forces the team login.
{"permissions": {"disableBypassPermissionsMode": "disable"}} (or "allow-auto-only"), via server-managed, MDM, or file. CLI: suppresses --allow-all-tools/-paths/-urls, --allow-all, --yolo, /allow-all and /yolo; an unrecognised value fails closed to 'disable'; re-fetched hourly. VS Code: turns off chat.tools.global.autoApprove. Copilot app: blocks 'Allow all'. Supported on JetBrains too. Overridable per enterprise team via {"overridable": "disable"}.
Vscode policy
ChatToolsAutoApprove=false disables global auto-approve and hides Assisted, Allow all and Autopilot. ChatToolsTerminalEnableAutoApprove=false. ChatAgentMode=false disables agents.
Cloud agent
No bypass mode exists. Admins can block the cloud agent in all enterprise repositories.
Limits
Server-managed values follow the account and are cleared when switching to an account without the policy. Only MDM survives account switches.
CLI 1.0.96 keeps /allow-all available during startup while the enterprise policy is still resolving.
If a server-managed fetch fails with no cache, that CLI session has no server policy. Use MDM or file delivery for must-hold rules.
Before CLI 1.0.88, ACP, AHP host and --server sessions ran with no managed policy.
Not documented: whether the COPILOT_ALLOW_ALL env var is also suppressed.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Cascade
Team setting 'Maximum autonomy level' (Teams & Enterprise). Members can choose any level up to the max. For example, max 'Auto' makes Turbo unavailable. 'Enable Cascade' (Enterprise) can disable Cascade entirely.
Devin local
No toggle to disable Bypass is documented. Admins instead enforce org deny/ask rules (active in all modes) and can make the sandbox Required, which leaves Autonomous as the only permission mode (Bypass hidden).
Limits
No documented toggle turns Bypass off in Devin Local. Making the sandbox Required hides it, leaving Autonomous as the only mode.
'Maximum autonomy level' applies to Cascade only. Devin Local uses 'Devin CLI permissions'.
On Team plans Devin Local is available by default. Legacy Cascade terminal controls apply to it only as a fallback when no CLI permission config exists.
MCP server allow-listRestrict which MCP servers may run.Supported as describedSupported as describedSupported as describedSupported with important gaps (see limits)Supported as describedSupported as describedSupported as described
Claude Code
Supported as describedSupported as described
allowedMcpServers / deniedMcpServers entries with exactly one of serverName, serverCommand (exact argv array) or serverUrl (wildcard pattern); managed-only allowManagedMcpServersOnly: true locks the allowlist to managed values; managed-mcp.json gives exclusive control (empty map disables MCP); managedMcpServers provides servers; strictPluginOnlyCustomization ["mcp"]; disableSideloadFlags rejects --mcp-config; disabledMcpjsonServers rejects .mcp.json servers; --strict-mcp-config. Example: {"allowManagedMcpServersOnly": true, "allowedMcpServers": [{"serverName": "github"}, {"serverUrl": "https://mcp.example.com/*"}], "deniedMcpServers": [{"serverCommand": ["npx", "-y", "evil-mcp"]}]}
Limits
Without allowManagedMcpServersOnly, allowedMcpServers merges from every scope (a soft allowlist that user or project files can widen). Denylist wins over allowlist and merges from all scopes. Built-in servers (Claude in Chrome, IDE server) are exempt from the allowlist and in-process sdk servers from both lists. Project .mcp.json servers require a per-server approval prompt interactively, but -p/SDK/cloud sessions load them without asking, and bypassPermissions sessions with skipDangerousModePermissionPrompt skip the prompt. managed-mcp.json cannot be delivered via server-managed settings.
Name AND identity must match. An empty [mcp_servers] table disables every MCP server. The string command form ignores args, cwd and env; structured matchers still ignore cwd, env and env_vars. No user-level allowlist. MCP traffic is outside the command network proxy.
By server name: mcp.allowed ["corp-api"] / mcp.excluded ["x"] in settings, or --allowed-mcp-server-names. Servers not on the allowlist are not started. Enterprise remote admin: admin.mcp.enabled=false disables MCP; the admin.mcp.config allowlist matches by name and forces url/type/trust and clears local command/args/env; admin.mcp.requiredConfig injects servers. Recommended locking pattern: define the servers and mcp.allowed in the system settings file.
Limits
Local allowlisting is by name only; the command or URL is whatever the winning config defines. mcp.allowed has no merge strategy, so a later scope replaces it: a trusted project overrides the user's list and only the system file is authoritative. In the source code, mcp.allowed also adds an ALLOW policy rule for every tool of the listed servers (auto-approval, not just permission to start). Untrusted folders connect to no MCP servers.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Per server name only: mcp.<name>.enabled=false, e.g. {"mcp":{"jira":{"type":"remote","url":"...","enabled":false}}}. Remote org config can ship servers disabled by default. Tool-level hiding: permission {"<server>_*": "deny"}.
Limits
There is no allowlist that rejects servers it does not know about. Any config layer, including a cloned repo's opencode.json, can add a new local server whose command runs at startup. A managed config can only disable servers it names. Denying <server>_* tools does not stop the server process from launching.
Enterprise: Team Settings > MCP Configuration allowlist. Command entries match the full stdio command string with * wildcards, e.g. '*npx -y @acme/mcp-tool@latest'. URL entries match remote URLs, e.g. 'https://mcp.acme.com/*'. Optional 'Allow User Extension' plus a User MCP Network Denylist. Team marketplaces distribute servers.
User
Each MCP connection needs approval. Project .cursor/mcp.json servers still need approval in the CLI. CLI: agent mcp enable/disable; --approve-mcps auto-approves all servers.
Limits
Allowlisting does not install or push servers.
Global ~/.cursor/mcp.json servers load without per-workspace prompts in the CLI. Project-level ones still prompt.
Plugins can bundle MCP servers. Blocked servers inside a plugin cannot make calls.
{"allowedMcpServers": [{"serverUrl": "https://mcp.example.com/*"}, {"serverCommand": ["npx","-y","@example/mcp-server"]}], "deniedMcpServers": [{"serverName": "untrusted"}]}. Lists are intersected and unioned across sources, deny wins, first-party servers are exempt, and servers with unresolved ${VAR} are blocked. Supported: CLI, VS Code, Copilot app, JetBrains. Not the cloud agent.
Github policy
'MCP servers in Copilot' policy (on/off). 'Restrict MCP access to registry servers' + MCP Registry URL (preview; name-based).
VS Code
ChatMCP policy (registryOnly / off), McpGalleryServiceUrl. MCP server trust prompts for non-workspace servers.
CLI
Workspace .mcp.json / .github/mcp.json only after folder trust. disabledMcpServers. Enterprise allowlist evaluation is fail-closed.
Cloud agent
Repo admins configure servers in repo settings. Registry enforcement is not supported for the cloud agent.
Limits
Registry-based enforcement matches by name only and 'can be bypassed by editing configuration files'. GitHub recommends managed-settings allowlists instead.
Team settings 'Allow MCP' toggle and 'MCP allowlist' (Teams & Enterprise). Once one server is allowlisted, all others are blocked. Entries are a Server ID with an optional Server Config JSON whose command and args are matched as anchored regexes. Cascade MCP config is user-level only (~/.config/devin/mcp_config.json). There is no workspace-level file for Cascade.
Devin local
MCP allowlist plus MCP registry URLs with an 'MCP registry enforcement' toggle. Registries do not affect Cascade.
Limits
The recommended 'Server ID only' option allows any configuration under that ID. The ID is the user's own key name in mcp_config.json, so it does not pin the command that runs.
MCP tool rulesAllow or deny individual MCP tools.Supported as describedSupported as describedSupported as describedSupported as describedSupported as describedSupported as describedSupported with important gaps (see limits)
Claude Code
Supported as describedSupported as described
Permission rules on MCP tool names: mcp__<server> or mcp__<server>__* (all tools of a server), mcp__<server>__<tool>, globs such as mcp__github__get_*; deny/ask may use "mcp__*". Example: {"permissions": {"allow": ["mcp__github__get_*"], "deny": ["mcp__github__delete_repo"]}}. Parameter-level rules for MCP tools only via --disallowedTools. Servers can mark tools _meta["anthropic/requiresUserInteraction"] to force a prompt every call.
Limits
Allow globs must start with a literal mcp__<server>__ prefix; unanchored allow globs are skipped. Settings files skip any mcp__ rule with parentheses. Rules key on the configured server name, so a renamed server escapes name-based rules; hooks receive an mcp_server.source field for provenance. Org-level claude.ai connector tools set to ask override allow rules.
[mcp_servers.<id>] enabled_tools = ["open"], disabled_tools = ["screenshot"] (deny applied after allow), default_tools_approval_mode = "auto" | "prompt" | "writes" | "approve"; [mcp_servers.<id>.tools.<tool>] approval_mode = "prompt". Same keys under plugins.<plugin>.mcp_servers.<server>. Managed: apps.<id>.tools.<tool>.approval_mode (connectors). PreToolUse hooks can block by matcher "mcp__server__tool".
Limits
"approve" auto-approves with no prompt. "auto" (default) trusts the server's own annotations: a tool claiming readOnlyHint skips approval. Destructive or open-world tools prompt unless marked read-only. Under approval_policy never, a call that needs approval is denied. No requirements key for per-tool deny on MCP servers was found (only on apps).
Policy TOML with mcpName (+ toolName) or FQN wildcards mcp_<server>_<tool>, mcp_<server>_*, mcp_*. Example: [[rule]] mcpName="my-jira" toolName="delete_issue" decision="deny" priority=500. Per-server includeTools / excludeTools in mcpServers.<name>. mcpServers.<name>.trust=true auto-approves all of that server's tools. Rules can also match toolAnnotations (e.g. readOnlyHint).
Limits
Underscores in server names break FQN parsing, and the docs warn that security policies can 'fail silently'. trust=true from a trusted project's settings becomes a user-tier (4.2) ALLOW.
MCP tools are permission keys named <server>_<tool> (sanitised), so wildcard keys work: {"permission":{"github_*":"ask","github_delete_repo":"deny"}}. Legacy tools: {"mymcp_*": false}.
Limits
Rules apply per tool with pattern "*" only; tool arguments cannot be matched. MCP resource reads use the read permission with patterns mcp:<server>:<uri>. Default is allow.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Cascade
disabledTools array per server in mcp_config.json, e.g. {"mcpServers":{"github":{"command":"npx","disabledTools":["delete_repo"]}}}. pre_mcp_tool_use hook (exit 2 blocks). Cascade does not prompt before MCP tool calls by default.
Devin local
mcp__server__tool, mcp__server__* and mcp__* in allow/ask/deny. Prompts before any MCP tool by default. Admins can default-allow servers or tools.
Limits
Cascade has no 'ask' option for MCP tools. Each tool is either enabled (runs) or disabled.
Blocking hooksRun your own check before a tool call and block it.Supported as describedSupported as describedSupported as describedSupported as describedSupported as describedSupported as describedSupported as described
Claude Code
Supported as describedSupported as described
hooks.PreToolUse (fires for every tool, including MCP tools, except EndConversation). Input on stdin (or HTTP POST body): session_id, prompt_id, transcript_path, cwd, permission_mode, hook_event_name, tool_name, tool_input, tool_use_id, plus mcp_server {name, source} for MCP tools. Block by exiting 2 (stderr becomes the reason) or by printing JSON {"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "..."}}; other decisions: ask, allow, defer (-p only), and updatedInput rewrites the call. Other blocking events: PermissionRequest (decision.behavior allow/deny), UserPromptSubmit, UserPromptExpansion, ConfigChange (not for policy_settings), PostToolBatch, PreCompact, PreModelSwitch, Elicitation, Stop/SubagentStop/TaskCompleted. Handler types: command, http, mcp_tool, prompt, agent. Example: {"hooks": {"PreToolUse": [{"matcher": "Bash", "hooks": [{"type": "command", "command": "~/.claude/hooks/check-bash.sh", "onFailure": "block"}]}]}}
Limits
Fail-open by default: exit codes other than 0/2, crashes, missing scripts and timeouts do not block unless onFailure: "block" is set (v2.1.295+). A hook "allow" cannot override deny/ask rules; deny precedence is deny > defer > ask > allow across hooks. @-file mentions in the prompt bypass PreToolUse. managed allowManagedHooksOnly limits hooks to managed, SDK and force-enabled plugin hooks; disableAllHooks outside managed settings cannot disable managed hooks, and a project's disableAllHooks: false overrides a user's true.
hooks.json or inline [hooks] next to any config layer (e.g. ~/.codex/hooks.json, <repo>/.codex/hooks.json, requirements.toml). [[hooks.PreToolUse]] matcher = "^Bash$"; [[hooks.PreToolUse.hooks]] type = "command", command = "/abs/policy.py", timeout = 30. Blocking events: PreToolUse (Bash/unified exec, apply_patch alias Edit|Write, MCP tools, other local function tools); PermissionRequest (allow/deny an approval; any deny wins); UserPromptSubmit; PostToolUse block only feeds back after the fact; Stop/SubagentStop block means continue. Input: JSON on stdin with session_id, transcript_path, cwd, hook_event_name, model, permission_mode, turn_id, tool_name, tool_use_id, tool_input. Block: exit 2 with a reason on stderr, or {"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"..."}} (legacy {"decision":"block"}). Can rewrite input with permissionDecision allow + updatedInput. Handlers: command and mcp_tool.
Limits
Non-managed hooks run only after the user trusts the exact hook hash via /hooks (stored as hooks.state."<key>".trusted_hash, honored only from user/session layers); --dangerously-bypass-hook-trust skips that. Fails open: errors, timeouts, malformed JSON, exit codes other than 0/2, exit 2 without stderr, and unsupported outputs (permissionDecision "ask") let the tool run. Background (async) hooks cannot block. Hosted web search is not hookable, and some tool paths opt out. Docs call hooks a guardrail, not an enforcement boundary.
settings hooks.BeforeTool: [{"matcher":"run_shell_command|write_file","hooks":[{"type":"command","command":"$GEMINI_PROJECT_DIR/.gemini/hooks/check.sh","timeout":5000}]}]. stdin JSON: session_id, transcript_path, cwd, hook_event_name, timestamp, tool_name, tool_input, mcp_context, original_request_name. Block by printing {"decision":"deny","reason":"..."} (exit 0) or exiting 2 (stderr becomes the reason); {"decision":"ask"} forces a prompt; {"continue":false} stops the agent; hookSpecificOutput.tool_input rewrites args. Other blocking events: BeforeAgent, BeforeModel, AfterModel, AfterTool (hide result), BeforeToolSelection (filter tools). Toggles: hooksConfig.enabled, hooksConfig.disabled.
Limits
BeforeTool runs before the policy check and cannot grant approval; an 'allow' is a no-op, and the policy check still runs on the possibly-rewritten args. Exit codes other than 0/2, or non-JSON stdout, fail open (tool proceeds). In the source code, in an untrusted folder all settings-defined hooks are skipped, including user and system ones (they are processed as 'Project' source). Project hooks are 'fingerprinted', but the code only warns and then auto-trusts them; it does not ask.
Plugins, not config hooks. Put a JS/TS module in .opencode/plugins/ or ~/.config/opencode/plugins/, or list npm packages in plugin. Hook tool.execute.before(input {tool, sessionID, callID}, output {args}) runs before the tool and its permission check; throwing an Error blocks the call, and mutating output.args rewrites it. Example: export const P = async () => ({ "tool.execute.before": async (i, o) => { if (i.tool === "bash" && /rm -rf/.test(o.args.command)) throw new Error("blocked") } }). Other hooks: tool.execute.after, shell.env, chat.params, event (sees permission.asked / permission.replied).
Limits
The documented permission.ask hook (which could return allow/deny) is declared in the plugin types but never triggered in v1.19.0 source, so plugins cannot decide permissions through it. Plugins are arbitrary code in the OpenCode process with no isolation, and project plugins load automatically. --pure disables all non-built-in plugins. Hooks fire for built-in and MCP tools. Whether every tool path (e.g. code-mode) calls the hook was not exhaustively checked.
preToolUse (allow/deny, can rewrite input via updated_input), beforeShellExecution and beforeMCPExecution (allow/deny/ask), beforeReadFile (allow/deny; receives full file content), beforeTabFileRead, subagentStart (allow/deny), beforeSubmitPrompt. stop and subagentStop can force follow-ups.
Input
JSON on stdin. Common fields: conversation_id, generation_id, model, hook_event_name, cursor_version, workspace_roots, user_email, transcript_path. Event fields, e.g. beforeShellExecution {command, cwd, sandbox}.
Blocking
Exit 2 or stdout {"permission":"deny"}. For permission hooks, invalid JSON blocks. Other failures fail open unless "failClosed": true. Types: command or prompt (LLM-evaluated). The matcher is a regex.
Config
{"version":1,"hooks":{"beforeShellExecution":[{"command":".cursor/hooks/guard.sh","matcher":"curl|wget","failClosed":true}]}}. Locations: enterprise MDM paths, team (Enterprise dashboard, synced every 30 min), project .cursor/hooks.json, user ~/.cursor/hooks.json, plus Claude Code settings.json hooks.
Limits
Responses merge with deny > ask > allow regardless of source, so a lower-priority hook can still deny.
Crashes and timeouts fail open by default.
Cloud agents run project, team, and enterprise hooks but not user hooks or MCP hooks.
Project hooks run only in trusted workspaces. Workspace trust is off by default, so every workspace counts as trusted (see repo_config_trust).
Events: preToolUse (allow / deny / ask, modifiedArgs), permissionRequest (CLI only; allow/deny short-circuits the permission service; cannot pre-approve sandbox bypass), agentStop / subagentStop (block = continue), userPromptSubmitted, sessionStart, postToolUse and more. Input: JSON on stdin, camelCase {sessionId, timestamp, cwd, toolName, toolArgs} or the VS Code-compatible PascalCase snake_case form. Types: command, http, prompt. A preToolUse command hook fails closed on crash or non-zero exit (exit 2 = deny), but timeouts fail open. Config: {"version":1,"hooks":{"preToolUse":[{"type":"command","bash":"./guard.sh"}]}}.
Cli locations
Policy hooks /etc/github-copilot/policy.d/*.json, C:\ProgramData\GitHub\Copilot\policy.d\*.json, or the HKLM registry (root-owned; cannot be disabled; run outside the sandbox). Repo .github/hooks/*.json. ~/.copilot/hooks/*.json. Inline 'hooks' in .github/copilot/settings(.local).json, .claude/settings(.local).json and ~/.copilot/settings.json. Plugins. Agent frontmatter.
Cloud agent locations
.github/hooks/*.json from the default branch only. bash entries only. 'ask' is treated as deny.
Pre-hooks that can block: pre_user_prompt, pre_read_code, pre_write_code (input includes edits), pre_run_command (command_line, cwd), pre_mcp_tool_use (server, tool, arguments). Post-hooks: post_read_code, post_write_code, post_run_command, post_mcp_tool_use, post_cascade_response, post_cascade_response_with_transcript, post_setup_worktree.
Input
JSON on stdin: agent_action_name, trajectory_id, execution_id, timestamp, model_name, tool_info.
Blocking
Exit code 2 blocks (stderr is shown to the agent). Any other non-zero exit lets the action proceed.
Config
{"hooks":{"pre_run_command":[{"command":"python3 /usr/local/share/hooks/guard.py","show_output":true}]}}. Locations, merged and run in order: cloud dashboard 'Agent hooks' (Enterprise), system (/Library/Application Support/Devin/hooks.json, /etc/devin/hooks.json, C:\ProgramData\Devin\hooks.json, with legacy Windsurf fallback), user ~/.codeium/windsurf/hooks.json, workspace .devin/hooks.json (legacy .windsurf/hooks.json).
Devin local
Separate lifecycle hooks format, e.g. the 'hooks' key in .devin/config.json.
Limits
Fail-open for any exit code other than 2.
Hooks do not load in Restricted Mode.
System hooks 'cannot be disabled by end users without root permissions'. Workspace hooks still run arbitrary repo-supplied commands.
Managed policySettings that users and projects cannot override.Supported as describedSupported as describedSupported as describedSupported with important gaps (see limits)Supported as describedSupported as describedSupported as described
Claude Code
Supported as describedSupported as described
Managed settings (same JSON shape as settings.json) via server-managed settings (claude.ai admin console or Claude apps gateway), MDM (macOS com.anthropic.claudecode profile, Windows HKLM\SOFTWARE\Policies\ClaudeCode), or files: /Library/Application Support/ClaudeCode/managed-settings.json, /etc/claude-code/managed-settings.json, C:\Program Files\ClaudeCode\managed-settings.json (+ managed-settings.d/*.json, managed-mcp.json). Managed-only keys include allowManagedPermissionRulesOnly, allowManagedHooksOnly, allowManagedMcpServersOnly, managedMcpServers, sandbox.network.allowManagedDomainsOnly, sandbox.filesystem.allowManagedReadPathsOnly, strictKnownMarketplaces, blockedMarketplaces, strictPluginOnlyCustomization, disableSideloadFlags, forceRemoteSettingsRefresh, disableCommandPluginSources, policyHelper, managedSourcesBehavior. Any key can be set in managed settings and then cannot be overridden. Example: {"permissions": {"deny": ["Read(./.env)"], "disableBypassPermissionsMode": "disable"}, "allowManagedPermissionRulesOnly": true}
Limits
Client-side enforcement (see bypass_lockout). Default first-wins means only one managed source applies. Some keys honor a stricter lower-level value. Invalid managed entries mostly fail closed (e.g. invalid allowedMcpServers enforced as empty), but requiredMinimumVersion/requiredMaximumVersion fail open. Server-managed changes to hooks/env need the developer to accept an approval dialog. A managed model is a default, not a lock.
Precedence: system file < cloud < legacy managed_config.toml < MDM. Scalars and lists replace, tables merge by key, rules/hooks/deny_read compose per field. Permission-profile allowlists are ignored by 0.137.0 and earlier. Login/auth keys are honored only from the local file or MDM. Docs: the runtime enforces managed hooks but doesn't distribute the scripts. Work Cloud containers do not inherit these requirements.
(1) System overrides settings.json (merged last), e.g. {"security":{"disableYoloMode":true},"mcp":{"allowed":["corp"]},"tools":{"sandbox":"docker"}}. (2) Admin-tier policy TOMLs in the system policies dir (tier 5, beats user and project). (3) Remote enterprise admin controls (Strict Mode, extensions on/off, MCP on/off, MCP allowlist and required servers, unmanaged capabilities/skills), which cannot be set by local files. (4) security.auth.enforcedType.
Limits
The system settings path can be repointed by the user (GEMINI_CLI_SYSTEM_SETTINGS_PATH). Array settings without a union strategy are replaced by later layers, but system is last. Only the system policies dir has ownership checks. Docs: not a defence against a user with local admin rights.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Managed files (/Library/Application Support/opencode/, /etc/opencode/, %ProgramData%\opencode) and macOS MDM plist domain ai.opencode.managed are merged after the user and project layers. Example managed opencode.json: {"share":"disabled","permission":{"bash":{"rm -rf *":"deny"}},"mcp":{"x":{"enabled":false}}}. experimental.policies (provider.use only) gives global precedence over project.
Limits
Managed config cannot disable --auto, and cannot stop project plugins or project MCP servers it does not name.
Run Mode / Auto Run Configuration, command allow and deny lists, sandbox networking, Read Controls, MCP Configuration, Cursor Ignore Configuration, .cursor Directory Protection, Browser Controls, team hooks, model access, Privacy Mode, repository blocklist. Team settings beat individual and project config.
Mdm
Windows Group Policy, macOS profiles, Linux JSON: AllowedExtensions, AllowedTeamId, ExtensionGalleryServiceUrl, NetworkDisableHttp2, UpdateMode, WorkspaceTrustEnabled. Plus the system hooks.json path.
File distribution
~/.cursor/permissions.json can be pushed by MDM, but it is a normal user file.
Limits
No MDM policy exists for Run Mode or the sandbox. Those are only enforceable through the cloud dashboard tied to the logged-in team.
Org Group merges favour the loosest value.
An MDM-pushed ~/.cursor/permissions.json is not tamper-resistant and still concatenates with repo files.
Client coverage varies. Granular permissions.* rules in VS Code apply only to Agent Host sessions. The cloud agent supports only model and the plugin keys.
Server-managed settings apply only to users licensed by the enterprise. MDM/file settings apply to the device.
hooks.json, rules, workflows, skills, system.json in the system directory.
Limits
No MDM policy covers agent permissions. Agent controls are account-bound team settings, so a personal account on the same machine is not governed by them.
Some controls are Enterprise-only (sandbox, hooks dashboard, Enable Cascade). Others are Teams & Enterprise (max autonomy level, MCP allowlist, command lists, CLI permissions).
Repo config and trustWhether a cloned repo's own config applies automatically, and what it can change.Supported with important gaps (see limits)Supported with important gaps (see limits)Supported as describedNot supportedSupported with important gaps (see limits)Supported as describedSupported with important gaps (see limits)
Claude Code
Supported with important gaps (see limits)Supported with important gaps (see limits)
Interactive sessions show a workspace trust dialog the first time Claude Code starts in an untrusted folder (keyed on the git repository root; stored as projects["<path>"].hasTrustDialogAccepted in ~/.claude.json; never persisted when started in the home directory). The dialog lists the allow rules and additional directories the folder would grant. deny and ask rules apply immediately. permissions.allow, additionalDirectories, most env values, hooks/helper commands and extraKnownMarketplaces wait for trust. Project .mcp.json servers additionally need per-server approval, and a repo cannot self-approve them while untrusted. claude -p and Agent SDK sessions never show the dialog.
Limits
Trust on a non-git directory extends to its subdirectories (except nested git repos), and the docs' 'What runs before you trust a folder' table marks settings-file hooks, env and helpers as 'Used' when only a parent folder was trusted, so a non-git project unpacked under a trusted folder gets no dialog for them; allow rules still wait for their own dialog. Mitigations for untrusted repos in -p/CI: --setting-sources user, --bare, --settings '{"disableAllHooks": true}', disabledMcpjsonServers, --strict-mcp-config; org-wide: allowManagedHooksOnly, allowManagedPermissionRulesOnly (also ignores repo skills' allowed-tools, v2.1.282+), strictPluginOnlyCustomization, admin-required sandbox. Cloud sessions ignore bypassPermissions/dontAsk from settings files.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Project .codex layers (config.toml, hooks, .rules, plus project AGENTS.md since 0.150.0) load only when projects."<repo-root>".trust_level = "trusted" is in the user config (or a managed layer). Unknown and untrusted projects get the layer loaded but disabled. The interactive TUI shows a trust prompt for an undecided project before starting: 'Trust this folder? ... Folder settings can run code automatically, even without a model request.' Options: 'Trust and continue' (pre-selected) or 'Quit'. The choice is written to ~/.codex/config.toml. A subdirectory of a git repo trusts the repo root. An explicitly untrusted folder opens 'restricted'.
Limits
(1) Malicious repo BEFORE trust: its config.toml, hooks and rules are ignored, so it cannot switch off approvals. (2) AFTER trust: the repo can weaken safety. Its .codex/config.toml may set approval_policy = "never", sandbox_mode = "danger-full-access" or a full-access permission profile, enable network, add stdio MCP servers (host processes), and add .rules allow rules, which run matching commands outside the sandbox. Only requirements.toml or explicit CLI flags override this. Project hooks additionally need per-hook hash trust. Project config can't redirect credentials or providers, or set notify, otel or profiles (denylist). (3) Auto-trust without a prompt: since 0.147.0 the app-server marks an undecided project as trusted, and persists it, when a thread starts with a cwd and the effective permissions can write that cwd. This covers codexexec--sandboxworkspace-write in a fresh clone, and any app-server client that starts a thread with workspace-write or full access (passed explicitly, or from the user's own config.toml default) without prompting first. The TUI prompts before it reaches this path. CLI-passed sandbox/approval values still outrank the project's. Requirements that force read-only stop auto-trust. (4) Non-interactive codex exec never prompts. Its only gate is a git-repo check, despite the message 'Not inside a trusted directory', and --yolo skips that check.
Folder trust (security.folderTrust.enabled, default true in v0.63.0). The first run in an unknown folder shows a trust dialog listing the repo's commands, MCP servers, hooks, skills, agents and setting keys, with warnings for tools.allowed, folderTrust.enabled=false and tools.sandbox=false. Until trusted, .gemini/settings.json, project .env (except whitelisted auth vars), MCP servers, hooks, custom commands and extension management are off, and approval is forced to default. Headless runs in an untrusted folder exit (FatalUntrustedWorkspaceError) unless --skip-trust or GEMINI_CLI_TRUST_WORKSPACE=true. Decisions are stored in ~/.gemini/trustedFolders.json.
Limits
Once trusted, the repo's settings merge above user settings. Can a repo weaken safety? It cannot select yolo (defaultApprovalMode=yolo is ignored), and workspace policy TOMLs are disabled in code. It CAN: set auto_edit; auto-approve tools via tools.allowed (warned), mcpServers.<x>.trust or mcp.allowed (not warned); replace the user's tools.allowed/tools.core/mcp.allowed arrays; add MCP servers and hooks that run commands at startup; set .env values such as GEMINI_SANDBOX=false (from the source code, not tested). In older releases such as 0.22.2, folder trust is off by default, so all of this applies with no prompt.
There is no trust prompt. On startup OpenCode merges opencode.json(c) from the cwd up to the git root and every .opencode/ dir, with project config above global config. It auto-loads project agents, commands, modes, plugins, custom tools and skills. It runs a background npm install (scripts ignored) of the project .opencode/package.json deps, and starts project-defined MCP servers. Opt-outs are user-side only: OPENCODE_DISABLE_PROJECT_CONFIG=1 (source), --pure (plugins).
Limits
Can a repo weaken safety? Yes, fully. A cloned repo can set "permission": "allow" or override any user rule. A user bash deny can be flipped by the project setting the same pattern to allow (deep merge), or outranked by a project "*": "allow" appended after it. The repo can define a permissive default_agent, and add MCP servers or local plugins, which are arbitrary JS/TS executed at startup before any prompt. Formatters/LSP commands it defines also run. Upstream treats this as by design: malicious config files are out of scope in SECURITY.md.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Trust prompt
The IDE supports VS Code Workspace Trust, but it is disabled by default. With it disabled, all workspaces are treated as trusted. Enable it with "security.workspace.trust.enabled": true, or enforce it with the WorkspaceTrustEnabled MDM policy. Restricted mode 'breaks AI features'. The CLI shows a workspace-trust prompt in interactive mode. Headless runs fail in untrusted workspaces unless --trust or --force is passed.
Applied automatically from a cloned repo
.cursor/permissions.json: terminalAllowlist, mcpAllowlist and autoRun (allow_instructions) are concatenated with the user's file. A repo can pre-approve commands or all MCP tools ('*:*').
.cursor/sandbox.json: per-repo has priority over per-user. Paths and network allow lists are unioned, so a repo can widen network egress and writable paths for sandboxed commands.
.cursor/hooks.json: arbitrary commands run on agent events in 'trusted' workspaces, which is all of them by default.
.claude/settings.json and .claude/settings.local.json: Claude Code hooks, loaded by default via Third-Party Imports.
.cursor/mcp.json: servers are listed but need approval.
.cursor/rules/, AGENTS.md: prompt context only.
.cursor/worktrees.json setup commands: gated by workspace trust (CLI).
CLI .cursor/cli.json: project-level permissions.allow and deny.
Can repo enable run everything
No documented repo-level key sets Run Mode, so not documented as possible. The repo can still weaken safety: expand allowlists, steer the classifier, widen the sandbox, and run hooks. Team admin controls override permissions.json allowlists. Hardcoded rules stop the agent from writing .cursor/*.json from inside the sandbox.
Limits
This assessment is assembled from separate doc statements. It has not been tested.
The sandbox.json merge rule for 'type' (e.g. 'insecure_none') and for networkPolicy.default when the user file does not set it is undocumented.
Workspace Trust is on by default (security.workspace.trust.enabled = true). Restricted Mode disables agents, workspace settings and terminals. Once trusted: 'restricted' workspace settings in .vscode/settings.json are honoured (chat.tools.terminal.autoApprove, chat.tools.terminal.ignoreDefaultAutoApproveRules, sandbox settings). Servers in .vscode/mcp.json and .mcp.json start with no separate MCP trust prompt. .github/hooks/*.json run. package.json scripts are auto-approved by default. chat.tools.global.autoApprove cannot come from the workspace.
CLI
On first launch in a directory, the CLI asks whether to trust its files: Yes / Yes and remember / No (ends the session). After trust: .mcp.json and .github/mcp.json servers load. .github/hooks/*.json and hooks in .github/copilot/settings.json and .claude/settings.json run. Repo settings may set enabledPlugins + extraKnownMarketplaces (auto-installs repo-scoped plugins, which bring hooks, MCP servers and agents), extend deny lists (union only), set disableAllHooks, and pin model/effort (trusted only). Repo settings cannot add allow rules or turn on allow-all. In -p mode, repo hooks and workspace MCP need an already-trusted folder or the env opt-ins.
Cloud agent
No trust prompt. The repo is the trust boundary and only users with write access can trigger the agent. Repo files apply automatically: .github/hooks/*.json and copilot-setup-steps.yml (both from the default branch), custom agents, and the plugin keys of .github/copilot/settings.json. Firewall and MCP configuration live in repo or org settings, not files.
Limits
VS Code restricted-setting behaviour is inferred from source (restricted: true in the setting registration). It has not been tested.
Not documented: whether repo enabledPlugins in the CLI is gated by folder trust.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Trust prompt
Devin Desktop (a VS Code fork) has Restricted Mode. Since v3.6.21/3.6.22 (July 29, 2026), Cascade, Devin Local, ACP agents and hooks are all disabled while a workspace is in Restricted Mode. Since v3.8.20 the UI warns about untrusted workspaces and offers a 'trust workspace' prompt. Whether Workspace Trust is enabled by default (a prompt on folder open) is not documented.
Applied once trusted
.devin/hooks.json or .windsurf/hooks.json: arbitrary shell commands on Cascade actions, e.g. on the first prompt via pre_user_prompt.
Cascade MCP: user-level file only, so a repo cannot add Cascade MCP servers.
Devin Local: .devin/config.json permissions (project outranks user, so a repo can add allow rules), hooks and MCP servers. The shared Devin CLI harness also imports MCP servers from .cursor/mcp.json, .mcp.json, .claude/settings.json, opencode.json and .zed/settings.json.
Can repo set turbo
Unknown for Cascade (storage of the auto-execution level is undocumented). Devin Local: no documented repo key for Bypass mode, but repo allow rules have the same effect for the commands they match. Org deny/ask rules still win.
Limits
v3.10.31 fixed CVE-2026-81376, where Restricted Mode failed to block restricted workspace settings written in nested-object form. This shows restricted workspace settings are a live attack surface.
Audit logWhether permission decisions and tool calls are recorded.Supported with important gaps (see limits)Supported with important gaps (see limits)Supported with important gaps (see limits)Supported with important gaps (see limits)Supported with important gaps (see limits)Supported with important gaps (see limits)Supported with important gaps (see limits)
Claude Code
Supported with important gaps (see limits)Supported with important gaps (see limits)
Local: full plaintext transcripts at ~/.claude/projects/<project>/<session>.jsonl (every message, tool call and tool result; subagent transcripts under <session>/subagents/), prompt history in ~/.claude/history.jsonl, file snapshots in ~/.claude/file-history/, debug logs with --debug or --debug-file. Retention: cleanupPeriodDays (default 30, minimum 1). Structured audit: OpenTelemetry events (opt-in, exported to a collector, not written locally): claude_code.tool_decision (decision accept/reject, source config/hook/user_permanent/user_temporary/user_abort/user_reject), permission_mode_changed, hook_execution_complete, mcp_server_connection, tool_result, managed_settings_resolved. Example managed env: {"env": {"CLAUDE_CODE_ENABLE_TELEMETRY": "1", "OTEL_LOGS_EXPORTER": "otlp", "OTEL_EXPORTER_OTLP_ENDPOINT": "http://collector:4317", "OTEL_LOG_TOOL_DETAILS": "1"}}
Limits
No dedicated local log of permission decisions is documented; the decision trail (with source) exists only in OTel events, which are off unless configured. Transcripts are plaintext, unencrypted, user-writable, and include any secrets tools read; they are deleted after cleanupPeriodDays. CLAUDE_CODE_SKIP_PROMPT_HISTORY or --no-session-persistence (-p) stops transcript writing. Without OTEL_LOG_TOOL_DETAILS=1, events omit commands and MCP server/tool names. Cloud sessions are logged server-side.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Session rollouts: $CODEX_HOME/sessions/YYYY/MM/DD/rollout-<timestamp>-<thread-id>.jsonl (archived under archived_sessions/) persist messages, tool/function calls and their outputs, web search calls and config updates. Prompt history: ~/.codex/history.jsonl ({session_id, ts, text}); history.persistence = "save-all" | "none", history.max_bytes. Optional log_dir enables codex-tui.log. OpenTelemetry (off by default): [otel] exporter = otlp-http|otlp-grpc, emits codex.tool_decision (approved/denied, source config vs user), codex.tool_result, codex.user_prompt (redacted unless log_user_prompt = true). codex exec --ephemeral writes no session files.
Limits
No dedicated local permission-decision log. Rollout persistence marks ExecApprovalRequest, ApplyPatchApprovalRequest, GuardianAssessment, HookStarted/HookCompleted and ExecCommandBegin/End as transient and does not write them. Approval decisions reach only the opt-in OTel stream. Files sit in the user's CODEX_HOME and can be edited or deleted. Enterprise Compliance API audit events are server-side, not local.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Always on: session transcripts with every tool call's inputs and outputs in ~/.gemini/tmp/<project_hash>/chats/ (auto-deleted after 30 days by default via general.sessionRetention.maxAge) and a prompt log logs.json. Opt-in OpenTelemetry: telemetry.enabled=true with target local|gcp, outfile or otlpEndpoint; the gemini_cli.tool_call event records function_name, function_args and decision (accept|reject|auto_accept|modify). Hooks (AfterTool, BeforeTool) can also log.
Limits
No dedicated tamper-resistant permission audit log. Transcripts are user-writable and expire by default. Telemetry is off by default, and logPrompts defaults to true when it is enabled. Policy rule matches are only in debug logging.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Each permission evaluation (evaluated, with permission, pattern and action) and each prompt (asking) is logged at INFO, the default level, to ~/.local/share/opencode/log/<timestamp>.log. Sessions, messages and tool calls are stored in the SQLite DB ~/.local/share/opencode/opencode.db (opencode db path, opencode export <sessionID>). Plugins can subscribe to permission.asked / permission.replied events.
Limits
Only the 10 most recent log files are kept. Logs and the DB are user-writable, with no tamper resistance or dedicated audit trail. Whether a prompt's reply (once/always/reject) is persisted in the DB was not verified.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Admin audit log
Enterprise audit log (dashboard, Admin API GET /teams/audit-logs, SIEM/webhook/S3 streaming). Records admin and security events such as team_settings changes (old/new value), team_hook changes (including script content), and mcp_server_config. It does not record prompts, agent output, or agent tool calls.
Telemetry
OpenTelemetry Export (Enterprise): cursor.tool.calls metric (builtin vs MCP counts) and cursor.hook.execution_complete logs. Conversation content and tool I/O cover Cloud Agents and Grok Bot only, not the IDE or CLI.
Local
Hooks are the documented way to log tool calls and decisions (postToolUseFailure reports failure_type 'permission_denied'). Transcripts are written to transcript_path. A Hooks output channel exists in the IDE.
Limits
No built-in record of each permission decision is documented for the IDE or CLI.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Github audit log
Enterprise audit log: Copilot policy and settings changes, and agent activity on github.com (filter actor:Copilot; fields actor_is_agent, agent_session_id). Retained 180 days; streamable to a SIEM. Preview: streamed Copilot API usage records with request/response bodies (EMU and data residency enterprises).
Client
Local client sessions are not in the audit log. Use hooks or OpenTelemetry. CLI: OTel execute_tool spans (arguments and results only with content capture). Since 1.0.96 the timeline shows who made each permission decision (you, Assisted Permissions, policy, unattended fallback). VS Code: CopilotOtel* policies, auto-approval notifications.
Cloud agent
Session logs, signed commits with a link to session logs, audit log events.
Limits
Fine-grained OTel content capture works in the CLI, the Copilot app and VS Code Agent Host sessions, not the VS Code Local harness.
Supported with important gaps (see limits)Supported with important gaps (see limits)
Analytics
Analytics API data sources: cascade_tool_usage (per-tool call counts such as RUN_COMMAND and MCP tool) and cascade_runs. These cover Devin Local and Devin CLI activity recorded after those agents started reporting. Individual-level analytics is an admin toggle.
Hooks
post_run_command, post_mcp_tool_use and post_cascade_response_with_transcript are the documented way to build an audit trail.
Admin audit log
No admin audit log of agent tool calls or permission decisions is documented.
Limits
Analytics are aggregate counts, not per-decision records. The Devin CLI does not report analytics in hybrid deployments.
Official documentation, changelogs and, for open-source agents, source code at the listed commits. Quotes are at most 25 words. "unknown" is never filled in from memory. Findings that may be undisclosed vulnerabilities are left out until they are reported to the vendor. Agents change monthly: if something here is out of date, open an issue (opens in a new tab).
Agent
Version and sources
Claude Code
Claude Code 2.1.296 (closed source: docs, changelog and --help only)
Codex CLI
Codex CLI rust-v0.162.1; source at main 044e74f
Gemini CLI
Gemini CLI v0.63.0; source at 5738466
OpenCode
OpenCode v1.19.0; source at 733b2c6 (OpenCode 2 not covered)
Cursor
Cursor 3.24 and Cursor CLI 2026.10.08 (docs only)
GitHub Copilot
Copilot CLI 1.0.96, VS Code 1.141 (source at 4d2e4b4), Copilot cloud agent docs
Windsurf (Devin Desktop)
Devin Desktop 3.10.48; Cascade is the legacy agent (docs only)
rookery’s check reads what agents may do from one .rookery.yml, and fails CI when a skill, command, settings file or MCP config breaks it. The same policy covers Claude Code, Codex, Cursor, Copilot, Gemini CLI and Windsurf.