What it is

Cursor's agent runs commands with your permissions. Whether a hijacked session can reach your keys, the network or a repository's code depends on your sandbox.json, your allowlists, your hooks and CLI settings, and on what the project ships in .cursor/, which adds to all of them. The audit checks it against the Security for Cursor Development guide and tells you exactly what to change.

Cursor security audit v1.0.0 (read-only, offline)

FAIL  [4] The project's .cursor/permissions.json allowlists risky commands or every MCP tool
              terminal: python
WARN  [1] Workspace Trust is off (the default)
              Every folder you open is trusted, so its project hooks, MCP servers and allowlists apply at once.
WARN  [4] .cursor/mcp.json declares MCP servers
              helper: node ./tools/helper.js
PASS  [2] The sandbox is on (workspace_readwrite)
PASS  [3] No risky commands in the terminal allowlist
PASS  [3] The Cursor CLI denies reading .env files
...
Summary: 10 pass, 2 warn, 1 fail, 2 info

Save a report with how-to-fix steps? [h]tml, [t]ext, [c]sv, [j]son, [n]o (default n):

Every finding shows, in brackets, the section of the Cursor guide that explains the fix.

Why you can trust it

Small and knownOfficial Alpine Linux 3.24.2 pinned by digest, plus only bash and jq from Alpine's repositories, pinned to exact versions. A small awk script reads Cursor's JSON with comments.
OpenEverything that runs is the Dockerfile, audit.sh, its mappings file and the shared audit library. You build it yourself from source.
OfflineRuns with --network none: it cannot send anything anywhere.
Read-onlyYour files are mounted read-only and the container's own filesystem is read-only. It changes nothing.
What it can read~/.cursor/sandbox.json, permissions.json, mcp.json, hooks.json and cli-config.json; your Cursor user settings.json; your shell startup files; and the whole project folder you run it from, including any secrets kept in that project. It doesn't mount the rest of ~/.cursor, Cursor's app state or the rest of your home folder. Its output names settings, files and commands, and masks values that look like secrets.
UnprivilegedRuns as you, with every Linux capability dropped, and is removed when it exits.

Run it

You need Docker Desktop, Docker Engine or Podman. Nothing else is installed on your machine.

macOS, Linux and WSL2

git clone https://github.com/aisecuritylabs-org/coding-agent-security.git ~/coding-agent-security
cd ~/code/my-app            # the project you want to audit
bash ~/coding-agent-security/cursor/audit/run.sh

Windows: Command Prompt

git clone https://github.com/aisecuritylabs-org/coding-agent-security.git "%USERPROFILE%\coding-agent-security"
cd /d C:\Projects\my-app
powershell -NoProfile -ExecutionPolicy Bypass -File "%USERPROFILE%\coding-agent-security\cursor\audit\run.ps1"

Windows: PowerShell

git clone https://github.com/aisecuritylabs-org/coding-agent-security.git "$HOME\coding-agent-security"
Set-Location C:\Projects\my-app
powershell -NoProfile -ExecutionPolicy Bypass -File "$HOME\coding-agent-security\cursor\audit\run.ps1"

Replace the project folder with the one you want to audit. When cursor is on your PATH, the launcher also passes your Cursor version to the audit. On Windows it also checks who can writeC:\ProgramData\Cursor, which the container can't see.

What it checks

AreaExamples
Trust and sandboxWorkspace Trust on; sandbox not insecure_none; network denied by default with no wildcard domains; no home or credential paths
Allowlists and hooksNo shells, interpreters, deletes or network tools allowlisted; no whole-server MCP entries; no hooks that run remote code; security hooks that fail closed
Cursor CLIApprovals on; sandbox on; no broad allow entries; .env reads denied; no --yolo or --force aliases
ExtensionsMCP server inventory; unpinned servers; servers that read outsider-written content; secrets written into mcp.json
RepositoriesA .cursor/sandbox.json or permissions.json that widens yours; project MCP servers, hooks and CLI permissions; rules files with fetch instructions or hidden Unicode; setup commands; disguised symlinks; .git/config keys that run programs; .env not ignored
VersionCursor 3.1.2 or later

The exit code is 0 with no FAIL results and 1 otherwise, so the audit also works in scripts and CI. Your run mode is chosen in the Cursor app and team policies live in the dashboard, which an offline audit can't see; the checklist covers them.

Real attacks it covers

AttackWhat it didCheck
CurXecute (CVE-2025-54135)A prompt injection wrote .cursor/mcp.json to run commandsP03
MCPoison (CVE-2025-54136)An approved MCP server was changed later without a new promptM03, P05
CVE-2026-26268 (Novee)A git hook ran code outside the sandboxP14
Rules File Backdoor (Pillar Security)Hidden Unicode in rules files steered the agentP10
AgentjackingFake error reports told the agent to run the attacker's commandM02, P04

Reports with how-to-fix steps

After the on-screen summary, the audit asks whether to save a report as HTML,text, CSV or JSON. Every finding explains why it matters, gives the exact fix, maps the risk to OWASP Top 10 for LLM Applications 2025, OWASP Top 10 for Agentic Applications 2026, MITRE ATLAS 2026.09 and NIST AI RMF 1.0, and lists its sources. Reports are saved to cursor-audit-reports in your home folder, never inside the project. Reports mask values that look like secrets, such as tokens, passwords and credentials in URLs, but review a report before you share it.

Fix what it finds

Most findings are fixed by four files, below as examples to adapt and test. Every key is from Cursor's sandbox.json, permissions.json and CLI configuration references.

Workspace Trust

Cursor ships with Workspace Trust disabled. Merge this into your user settings.json, then open folders you didn't create in restricted mode.

// Merge into your Cursor user settings.json (Command Palette: "Preferences: Open
// User Settings (JSON)"). Cursor ships with Workspace Trust disabled, so every
// folder you open, with its hooks, MCP servers and allowlists, is trusted.
// Test these settings before you rely on them; see cursor/README.md.
{
  "security.workspace.trust.enabled": true
}

sandbox.json

Save as ~/.cursor/sandbox.json, list the registries you use, and choose sandbox.json Only as the network mode so Cursor's default domains aren't added. A project's own.cursor/sandbox.json takes priority over this file, which is why the audit checks it.

{
  "type": "workspace_readwrite",
  "networkPolicy": {
    "default": "deny",
    "allow": [
      "registry.npmjs.org",
      "pypi.org",
      "files.pythonhosted.org"
    ]
  },
  "disableTmpWrite": false
}

permissions.json

Save as ~/.cursor/permissions.json. Allowlisted commands run outside the sandbox without asking, so it lists only read-only commands. The block instructions steer Auto-review; Cursor says they are "steering, not enforcement".

{
  // Allowlisted commands run outside the sandbox without asking: keep this list
  // to read-only commands. Allowlists are not a security boundary.
  "terminalAllowlist": [
    "git status",
    "git diff",
    "git log",
    "ls",
    "pwd"
  ],
  // No MCP tool runs without asking. Add specific read-only tools only,
  // e.g. "github:get_issue", never "*:*".
  "mcpAllowlist": [],
  "autoRun": {
    "allow_instructions": [],
    "block_instructions": [
      "Anything that reads .env files, ~/.ssh, ~/.aws or other credential files should go through approval.",
      "Any command that deletes files outside the build output, pushes to git, publishes a package or deploys should go through approval.",
      "Any command that sends data to a host other than a package registry should go through approval."
    ]
  }
}

Cursor CLI

Merge into ~/.cursor/cli-config.json. Deny rules take precedence over allow rules. To prove it all works in a live session, run the ten-minute self-test.

{
  "version": 1,
  "editor": { "vimMode": false },
  "approvalMode": "allowlist",
  "permissions": {
    "allow": [
      "Shell(ls)",
      "Shell(git:status*)",
      "Shell(git:diff*)",
      "Shell(git:log*)"
    ],
    "deny": [
      "Read(**/.env*)",
      "Write(**/.env*)",
      "Read(~/.ssh/**)",
      "Read(~/.aws/**)",
      "Write(**/*.key)",
      "Shell(rm)",
      "Shell(sudo)"
    ]
  }
}

Windows: lock the ProgramData folder

Cursor on Windows loads machine-wide hooks from C:\ProgramData\Cursor\hooks.json. The folder isn't created or locked by default, so any standard user could create it and plant hooks that run for every Cursor user on the machine. Cymulate reported this and it was unresolved at publication (Cymulate). The audit reports it as W01.

Run this in PowerShell opened with Run as administrator; it's also in the repository as cursor/config/lock-programdata.ps1, and the last line shows the result. If the folder already existed and you didn't create it, check what's inside first and remove any hooks.jsonyour administrators didn't put there.

# Run in PowerShell opened with "Run as administrator".
$p = 'C:\ProgramData\Cursor'
New-Item -ItemType Directory -Force -Path $p | Out-Null

# A fresh ACL: owned by Administrators, no inherited or extra entries.
$admins = [Security.Principal.SecurityIdentifier]'S-1-5-32-544'
$acl = New-Object Security.AccessControl.DirectorySecurity
$acl.SetOwner($admins)
$acl.SetAccessRuleProtection($true, $false)
$grants = @(
    @('S-1-5-32-544', 'FullControl'),      # Administrators
    @('S-1-5-18',     'FullControl'),      # SYSTEM
    @('S-1-5-32-545', 'ReadAndExecute')    # Users: read only
)
foreach ($g in $grants) {
    $sid = [Security.Principal.SecurityIdentifier]$g[0]
    $acl.AddAccessRule((New-Object Security.AccessControl.FileSystemAccessRule($sid, $g[1], 'ContainerInherit, ObjectInherit', 'None', 'Allow')))
}
Set-Acl -LiteralPath $p -AclObject $acl

# Anything already inside takes the same owner and permissions.
Get-ChildItem -LiteralPath $p -Recurse -Force | ForEach-Object {
    icacls $_.FullName /setowner '*S-1-5-32-544' /c /q | Out-Null
    icacls $_.FullName /reset /c /q | Out-Null
}
icacls $p

Delete it when you are done

The container is already gone when the audit finishes. To remove the image as well:

docker image rm cursor-audit