Skip to content
All posts
8 min readBrian McManus

Cursor enterprise security settings: MCP allowlist, hooks, sandbox and the gaps

How Cursor's MCP allowlist matches servers, where hooks and run modes fit, and the four things an administrator still owns after the settings are deployed.

cursorenterprisemcppolicy

Cursor's MCP allowlist is an Enterprise setting in the team dashboard, under MCP Configuration. It matches an approved server by its full stdio command string or by its URL, and once an allowlist is active, servers that match no entry do not run. Around it sit hooks, per-server tool and network controls, and the Auto-review run mode. The details below come from Cursor's own documentation, and the section on gaps quotes how Cursor scopes those controls.

What the allowlist is, and what it is not

Two Cursor pages describe the same control from different angles. Model and integration management covers the mechanics. Security and privacy hardening puts it in context: layer controls for depth, and "pair best-effort guardrails (Auto-review, allowlists, .cursorignore) with deterministic ones (approvals, hooks, sandboxing) rather than relying on a single layer."

So the allowlist answers which servers Cursor will connect to. It does not decide what an approved server does once connected, and Cursor says so by grouping it with the best-effort half of its control set.

How matching works

Stdio servers are matched on the full command string: the command value plus every args value joined with spaces. For a server configured as npx -y @acme/mcp-tool@latest, the string under test is that, except most shells resolve npx to an absolute path, so the real value is closer to /usr/local/bin/npx -y @acme/mcp-tool@latest. Cursor documents a leading wildcard for exactly this case:

EntryMatches
*npx -y @acme/mcp-tool@latestnpx at any path, with these exact arguments
/usr/local/bin/npx -y @acme/mcp-tool@latestOnly that path
*npx -y @acme/*Any @acme-scoped package

URL servers are matched on the full URL, with wildcards on host and path: https://mcp.acme.com/sse is exact, https://*.acme.com/* covers subdomains and paths.

Per-user entries can ship as a managed file. ~/.cursor/permissions.json holds an mcpAllowlist array of strings in server:tool form, where server:* is every tool on one server, *:tool is a tool name across servers, and *:* is every MCP tool.

Resolution has three layers, and Cursor replaces rather than merges them: dashboard and other admin-controlled settings first, then ~/.cursor/permissions.json, then the allowlist in editor settings and the inline Add to allowlist action.

Two operational notes. Adding a server to the allowlist does not install it, so users still configure approved servers themselves or receive them from a team marketplace. And per-server tool controls live beside the entry: list the tools that may run in that server's Tools field, or leave the field empty to allow all of them.

Per-server network modes

Each approved stdio server has its own network policy, which is the part most allowlist projects miss: a filesystem server and a documentation server deserve different egress.

Network modeBehavior
Allow allNo egress restriction
AllowlistOnly listed destinations are reachable
Deny allRuns locally with no outbound network access
No sandboxNo command or network sandboxing

Remote, URL-based servers are held to the configured URL entry pattern instead.

Hooks, which are the deterministic half

Hooks receive JSON on stdin and return a decision. The ones that matter for MCP are beforeMCPExecution and afterMCPExecution, with preToolUse and postToolUse as the general pair and beforeShellExecution as the shell equivalent.

Configuration lives in hooks.json at one of five sources, in priority order:

  1. Enterprise, delivered by MDM: /Library/Application Support/Cursor/hooks.json on macOS, /etc/cursor/hooks.json on Linux and WSL, C:\ProgramData\Cursor\hooks.json on Windows.
  2. Team, from the dashboard on Enterprise plans, synced roughly every thirty minutes.
  3. Project, at <project>/.cursor/hooks.json, committed to the repository.
  4. User, at ~/.cursor/hooks.json.
  5. Plugin hooks, which run last.

All matching hooks from every source run, and responses merge with deny beating ask and ask beating allow. A beforeMCPExecution hook returning {"permission": "deny"} blocks the call, and exit code 2 does the same. The option worth setting is failClosed, because the default is fail-open:

{
  "version": 1,
  "hooks": {
    "beforeMCPExecution": [
      {
        "command": "/Library/Application Support/Cursor/hooks/mcp-gate.sh",
        "matcher": "delete_|write_|execute",
        "timeout": 10,
        "failClosed": true
      }
    ]
  }
}

Without failClosed, a crash, a timeout or a non-zero exit blocks nothing. A permission hook that returns invalid JSON, or a response that does not match the schema, blocks the action even when failClosed is false.

Run modes and the classifier

Auto-review is the run mode Cursor recommends over Run Everything: it runs allowlisted calls, sandboxes shell commands when it can, and routes the rest through a classifier. Two facts matter here. The classifier runs on Gemini 3.5 Flash Lite with Haiku as a fallback, so blocking Claude 4.5 Haiku in model access control can disable Auto-review in the IDE. And .cursorignore, which uses gitignore syntax, is a context control: Cursor's documentation states that the terminal and MCP server tools the Agent uses cannot honor it.

Team Rules are the other thing people lean on. Cursor's hardening guide files them under steering, not enforcement.

What the settings leave uncovered

Best-effort is a stated design choice. Cursor groups the allowlist with Auto-review and .cursorignore, and tells administrators to add deterministic controls beside them. A control that depends on a classifier is a good filter and a poor boundary.

Cloud agents run a different surface. HTTP MCP servers run on Cursor's backend rather than the agent VM. For their tools, preToolUse fires with tool_name set to MCP:<tool_name> and can deny the call, but Cursor ignores updated_input there, and beforeMCPExecution and afterMCPExecution are not available to cloud agents. User-level hooks are not loaded in cloud agents at all.

Everything here is Cursor. An allowlist entry governs the Cursor client. The same developer runs the Codex CLI, Claude Code and GitHub Copilot on the same machine, each with its own list and its own idea of a hook. Nothing in this configuration observes the MCP servers those clients start, including stdio servers that never make a network request.

The local process. An approved stdio server runs with the developer's permissions, so a server granted the home directory can read .ssh and every .env in every project. Approving a server is not confining it.

Audit is the fourth question, and the answer has three parts: audit logs for authentication and admin events, OpenTelemetry export for usage telemetry to your own collector, and hooks for development-activity metadata. A per-call policy decision record that leaves the endpoint carrying what was allowed, what was denied and why is a separate piece of work.

Where Alyria Beacon adds a layer

Alyria Beacon runs beside Cursor as a signed, user-space agent on macOS, Linux and Windows, with no kernel driver, and deploys read-only in monitor mode first. It inventories the AI tooling and MCP configuration on each endpoint, including the stdio servers that no allowlist in any one client can see, and keeps that inventory current across Cursor, Codex, Claude Code and GitHub Copilot. Lyra policy is evaluated locally on every MCP and inference call, so one rule set covers the machine instead of one rule set per vendor, and sequence policies cover multi-step flows such as a secret read followed by a network send. Every decision is signed into one audit chain, and Spectra exports the same signal to your SIEM over OpenTelemetry.

Keep the Cursor controls. They are precise, they are the vendor's own, and the endpoint layer is not a replacement for a client-side allowlist.

A deployment order

  1. Build the dashboard allowlist, and write command entries with the leading wildcard so the resolved path still matches.
  2. Add the Tools field and network mode for every approved server, denying egress by default where a server only reads local files.
  3. Ship an enterprise hooks.json with beforeMCPExecution and failClosed: true for the tool classes you cannot tolerate, and log every decision.
  4. Keep Auto-review on and keep the classifier model allowed.
  5. Inventory the fleet read-only for two weeks. The inventory tells you which rules you would have broken, and which other clients on the same laptops need the same treatment.

Frequently asked questions

Does the Cursor MCP allowlist cover every MCP client on the machine? No. It governs the Cursor client, including its CLI surfaces, and nothing else. Other agents on the same laptop read their own configuration files.

Can developers add a server we did not approve? With a dashboard or managed-file allowlist in place, only servers matching an entry run. Where no admin source exists, the editor settings list and the inline Add to allowlist action apply, and a developer controls those.

Do we need hooks if we have the allowlist? If you want a deterministic control over what an approved server does, yes. The allowlist decides which servers connect. beforeMCPExecution with failClosed decides whether a specific call runs, and leaves the record your SIEM asks for.

/integrations/cursor lists the Cursor controls beside the endpoint layer, and How to find every MCP server on your developer laptops covers the inventory question the allowlist leaves open.

How is your team using AI agents?

Pick your function and answer 20 questions on agent workflows and controls. Your result appears on the page.

Governed AI agents for the work your teams do.