How do you secure Cursor's agent?

Cursor runs hooks as commands, so a policy check needs a small wrapper script on preToolUse and beforeSubmitPrompt with failClosed set. With Arcjet, that wrapper applies the same policy you run on Claude Code, Copilot, and Codex before each tool call, and every decision appears in the Arcjet Console. Deploy it through enterprise MDM or Team hooks so that developers can't remove it, and commit it to the repository for cloud agents.

12 min read
In short: Cursor runs hooks as commands, so a policy check needs a small wrapper script on preToolUse and beforeSubmitPrompt with failClosed set. With Arcjet, that wrapper applies the same policy you run on Claude Code, Copilot, and Codex before each tool call, and every decision appears in the Arcjet Console. Deploy it through enterprise MDM or Team hooks so that developers can't remove it, and commit it to the repository for cloud agents.

How do you secure Cursor's agent?

To secure Cursor's agent, install a hook – a program that Cursor runs before it acts, which can refuse the action – that sends each tool call and prompt to a policy service. Put it on the preToolUse and beforeSubmitPrompt events with failClosed: true, so that a failed check denies the action. Deploy the hook file from a source that developers can't edit: an enterprise-managed hooks.json pushed by mobile device management (MDM), or Team hooks from the Cursor dashboard. Commit the same file to the repository so that cloud agents load it too.

Cursor's agent runs shell commands, reads and writes files, calls MCP servers (Model Context Protocol servers that give the agent extra tools), and starts subagents, all with the developer's access. Instructions in .cursorrules or AGENTS.md ask the model to behave, but they don't stop a tool call. A hook runs before the tool does and can refuse it, which makes the hook the enforcement point. For why instructions files aren't a control, see AGENTS.md is not a security control.

In practice, you do this with Arcjet coding agent hooks. A wrapper script sends each Cursor tool call and prompt to Arcjet, and Arcjet decides before the tool runs, from the hooks that Cursor already fires, with no SDK and no code change. The same policy runs on Claude Code, GitHub Copilot, and OpenAI Codex, so you don't maintain per-vendor rules, and every session and decision is recorded in the Arcjet Console.

This guide covers how the wrapper works, where to deploy it so that it stays in place, what it enforces, and what to pair it with. For controls across all four agents, see coding agent security.

Why does Cursor need a command wrapper?

Cursor runs hooks as commands and has no built-in HTTP hook type. A hook entry names a program. Cursor writes the hook payload to that program's standard input and reads the result from its standard output.

To call a remote policy service, you install a small script next to the hook configuration. The script reads the payload from stdin, posts it to the service, and prints the response. The Arcjet script posts to https://decide.arcjet.com/v1/agent-hooks/cursor with the event as a query parameter and the site key in an Authorization header. The machine that runs Cursor needs curl, or PowerShell on Windows, where a sibling arcjet-hook.ps1 does the same job.

The wrapper is also the fail-closed boundary: when it can't get an answer, it denies the action instead of allowing it. On any transport failure, non-2xx response, or response that isn't JSON, it prints Cursor's denial shape for that event and exits with code 2. A wrong key denies every Cursor prompt and tool call, so test the key before you roll it out.

The script reads the key from the ARCJET_KEY environment variable. Export it where Cursor runs or inject it with configuration management, and never commit a literal key.

Where should you deploy Cursor hooks?

Cursor loads hooks from four sources. When responses conflict, the higher-priority source wins, in this order: Enterprise, Team, Project, then User.

SourceWhere the file goesWhat it reachesWho can remove it
Enterprise (MDM)

/Library/Application Support/Cursor/hooks.json (macOS), /etc/cursor/hooks.json (Linux, WSL), or C:\ProgramData\Cursor\hooks.json (Windows), with the script beside it

That device: Agent Chat, Cmd+K, and the CLIA local administrator only
Team hooksThe Cursor dashboard, on the Enterprise planMembers of the team, including cloud agentsOnly from the dashboard
Project hooks

.cursor/hooks.json, committed to the repository

Sessions in that repository, including cloud agentsAny developer
User hooks~/.cursor/hooks.jsonThat developer's local IDE and CLIAny developer

For a control that developers can't turn off, deliver the Arcjet hook through the Enterprise or Team source. Enterprise MDM and Team dashboard hooks outrank a developer's project or user file, so a developer can't remove the hook without administrator access. Distribute the script with hooks.json so that the command path exists on every machine.

Project hooks run only in a trusted workspace. A developer who rejects workspace trust skips the repository file, which is another reason not to rely on it alone.

What can a Cursor hook enforce?

Two Cursor events can refuse an action:

  • preToolUse runs before every agent tool: Shell, Read, Write, Grep, Delete, Task, and MCP tools. A denial is permission: "deny" with a user and agent message.
  • beforeSubmitPrompt runs before a prompt reaches the model. A denial is continue: false, and Cursor honors it.

Cursor has no permission-request hook and no prompt-expansion hook. A tool-call policy still runs on preToolUse, which fires for every tool whether or not Cursor would have asked the developer.

Cursor also fires beforeShellExecution, beforeMCPExecution, and beforeReadFile. Those fire in addition to preToolUse, so installing them posts the same call twice. Use preToolUse alone, and leave out matcher: a matcher that lists only Shell or Write leaves reads, MCP tools, and Task calls outside the policy.

The following file installs the two enforcement entries:

{
"version": 1,
"hooks": {
"preToolUse": [
{
"command": "sh .cursor/hooks/arcjet-hook.sh cursor pre-tool-use",
"timeout": 5,
"failClosed": true
}
],
"beforeSubmitPrompt": [
{
"command": "sh .cursor/hooks/arcjet-hook.sh cursor user-prompt-submit",
"timeout": 5,
"failClosed": true
}
]
}
}

The full template in the Cursor install guide adds recording entries for postToolUse, postToolUseFailure, sessionStart, sessionEnd, subagentStart, subagentStop, stop, and preCompact. Those events can't block anything, but they record what the agent did. For an enterprise or user install, change the command to an absolute path.

Arcjet normalizes Cursor's tool names to a tool_kind: Shell is shell, Read and Grep are file_read, Write and Delete are file_write, and Task is agent. A policy that compares the kind works across Cursor, Claude Code, Copilot, and Codex, so you write one policy rather than one per tool. You write policies in Rego for fine-grained control, or start from the starter policies in the Arcjet Console. Dry run shows what a rule would deny before it goes live, and changes take effect in real time. For example policies, see block dangerous commands and stop coding agents reading secrets.

Arcjet answers an allow with an empty object. Cursor treats permission: "allow" as a grant that skips its own permission flow, so a policy service that answers allow explicitly turns a security control into a way around Cursor's approvals.

How do you make Cursor hooks fail closed?

Cursor hooks fail open by default: if the hook crashes, times out, or returns invalid JSON, the action goes through. Set failClosed: true on preToolUse and beforeSubmitPrompt so that any of those failures denies the action instead.

Set timeout on every entry as well. Five seconds is a reasonable ceiling for a policy decision. Each entry adds one request of latency to the event, and Arcjet evaluates the policy at the edge in over 300 data centers, so the added latency stays small.

With failClosed: true, Cursor denies prompts and tool calls whenever the policy service is unreachable, so plan for the service's availability as you would for the agent's. For how this compares with agents whose hooks fail open, see coding agent hooks fail open.

How do you secure Cursor cloud agents?

A cloud agent runs command hooks from the repository. Commit .cursor/hooks.json and .cursor/hooks/arcjet-hook.sh to the default branch so that the cloud VM can run them. On Enterprise plans, cloud agents also run Team hooks and enterprise-managed hooks from the dashboard. User-level ~/.cursor/hooks.json doesn't reach cloud agents, because the VM has no access to a developer's home directory.

Cursor cloud agents run preToolUse and don't run beforeSubmitPrompt, so Arcjet refuses tool calls in the cloud VM. The prompt itself was submitted before the VM existed. Cloud agents also skip sessionStart, sessionEnd, beforeMCPExecution, and afterMCPExecution.

Cloud agents have internet access by default. If you restrict outbound network access, add decide.arcjet.com to the allowlist of domains the VM can reach. Network access mode is set per user, team, and environment, so a developer who runs an agent under a stricter environment has to allow the domain there too.

Can you restrict which models Cursor uses?

Yes. Cursor has no model-switch hook, but preToolUse and beforeSubmitPrompt both carry the selected model. An Arcjet allowed-models policy refuses the prompt and every tool call while a model outside the list is selected.

Matching is exact after lowercasing. auto, default, and inherit are real selector states on Cursor, so a session on Auto is denied unless you list auto. For the policy and the per-vendor differences, see restrict models in coding agents.

What do you pair with Arcjet on Cursor?

Cursor's hooks control one client, not one person. Pair Arcjet with the following controls for the paths that Cursor's hooks don't reach:

  • Personal Cursor accounts. A developer signed in with a personal account loads none of your hooks. Refuse that on the device with the AllowedTeamId MDM policy. For the steps, see block personal AI accounts on work laptops.
  • Cursor CLI API keys. A Cursor CLI session that uses CURSOR_API_KEY is a separate credential path from editor sign-in. Treat those keys as a separate allowlist.
  • Local administrators. A local administrator can remove an MDM-managed file, so on devices where developers have local administrator rights, the hook controls the managed client, not the person. To find sessions that ran without the hook, compare Arcjet decisions with other telemetry.
  • Read-only cloud turns. Cursor cloud agents sometimes start in a read-only environment for early exploratory turns, and Cursor doesn't run hooks during those turns. Hooks start running after the agent has a writable environment.
  • Tool results. None of the supported agents offers a point where a hook can withhold a tool result, so a poisoned web page or MCP response has already reached the model by the time a hook sees it. Stop the call before it happens instead: Arcjet threat intelligence scores the hosts an agent is about to contact. For more information, see detect malicious URLs in AI agents.
  • Other agents on the same laptop. Install Arcjet hooks for Claude Code, Copilot, and Codex as well, with the same policies.

The X-Arcjet-Principal header that the script sends is $USER or $USERNAME. It attributes the session to a developer, but it's an assertion, not authentication, so treat it as metadata.

Where does Arcjet fit in securing Cursor?

With Arcjet, Cursor's tool calls and prompts on managed devices are checked against your policy before they run, and each session is recorded alongside your other coding agents. The following capabilities apply to Cursor:

  • One policy across Cursor, Claude Code, GitHub Copilot, and OpenAI Codex, decided from the hooks the agents already fire.
  • Delivery through MDM or Team hooks, so developers can't remove the hook without administrator access.
  • Rego policies, starter policies in the Arcjet Console, and dry run before a rule goes live.
  • Every session and decision in the Arcjet Console, with export to Datadog, Splunk, SentinelOne, Panther, and Amazon S3 (Enterprise plan) for detection and alerting.

To get started, follow the Cursor install guide, then publish the coding-agent.destructive-command starter policy in dry run and read its would-be denials in the Arcjet Console. For the payload fields a policy can read, see the coding agent policies reference.

Frequently asked questions

Do I need MDM to enforce policy on Cursor?

No. Team hooks from the Cursor dashboard, on the Enterprise plan, also outrank a developer's own files and reach cloud agents. MDM places an enterprise hooks.json on each device that only a local administrator can remove. A committed .cursor/hooks.json works without either, but any developer can delete it or skip it by rejecting workspace trust.

Can a developer bypass Cursor hooks?

A developer can edit or remove project and user hooks. Enterprise MDM hooks can be removed only by a local administrator, and Team hooks only from the dashboard. A personal Cursor sign-in or a CLI session using CURSOR_API_KEY is a separate path, so pair hooks with the AllowedTeamId MDM policy.

Does Cursor fail open if the hook times out?

By default, yes: a crash, timeout, or invalid JSON lets the action through. Set failClosed: true on the preToolUse and beforeSubmitPrompt entries so those failures deny instead.

Do Cursor hooks run in cloud agents?

Cloud agents run command hooks committed to the repository, plus Team and enterprise-managed hooks on Enterprise plans. They run preToolUse but not beforeSubmitPrompt, so tool calls are refused and the initial prompt isn't. User-level ~/.cursor/hooks.json doesn't reach them.

Is there one policy for Cursor or one per tool?

One policy covers every tool. Arcjet normalizes Cursor's tool names such as Shell, Read, and Write to a tool_kind, so the same policy applies to Cursor, Claude Code, Copilot, and Codex.

AI runtime security in your code

Protect your AI agent workflows with Arcjet

Install Arcjet hooks for Cursor and enforce one set of policies on every prompt and tool call.