Cursor hooks for security: block risky shell commands and MCP calls
· 5 min read
Cursor asks before it runs a terminal command or calls an MCP tool, but a prompt only protects you if you read every one. After the fiftieth "Run npm test?" most people click through, or switch on auto-run. Hooks move the rules that must never be skipped out of your attention and into code.
This post shows three practical uses: blocking risky shell commands, allowing only MCP servers you trust, and keeping sensitive files away from the model. For the wider picture, see the Cursor security guide.
What Cursor hooks are
A hook is a script Cursor starts at a set point in the agent loop. It receives JSON on stdin, and for permission hooks it answers on stdout with a decision: allow, deny or ask. The events that matter most for security:
beforeShellExecution: runs before the agent executes a terminal command.beforeMCPExecution: runs before the agent calls an MCP tool.beforeReadFile: runs before a file's contents are sent to the model, and can only allow or deny.
If any matching hook answers deny, the action is blocked, whatever the other hooks say.
Where the config lives
Project hooks go in .cursor/hooks.json at the project root and apply to that project only. User hooks go in ~/.cursor/hooks.json and apply everywhere. Enterprise and team configs take priority over project and user ones, and Cursor reloads the file when it changes.
Example 1: block network commands
This config runs a script before any shell command that matches the matcher regex, and blocks the command if the script fails.
{
"version": 1,
"hooks": {
"beforeShellExecution": [
{
"command": ".cursor/hooks/block-network.sh",
"matcher": "curl|wget|nc",
"failClosed": true
}
]
}
}
And the script, saved as .cursor/hooks/block-network.sh and made executable with chmod +x:
#!/bin/bash
cat > /dev/null
echo '{"permission":"deny","user_message":"Network commands are blocked in this workspace.","agent_message":"curl, wget and nc are not permitted here."}'
exit 0
The agent_message is shown to the agent, so it knows why the command failed and can pick another approach instead of retrying.
Example 2: allow only MCP servers you trust
MCP servers run code and return text the agent will act on, so each one is both a supply-chain risk and a prompt-injection channel. A beforeMCPExecution hook receives the tool name, its input and the server name. Cursor's docs advise treating a missing or unexpected server name as a deny.
{
"version": 1,
"hooks": {
"beforeMCPExecution": [
{
"command": ".cursor/hooks/mcp-allowlist.sh",
"failClosed": true
}
]
}
}
#!/bin/bash
# Requires jq. Replace the names with the servers in your own MCP config.
input=$(cat)
server=$(echo "$input" | jq -r '.mcp_server_name // empty')
case "$server" in
github|docs)
echo '{"permission":"allow"}'
;;
*)
echo '{"permission":"deny","user_message":"That MCP server is not on the allowlist.","agent_message":"This MCP server is not approved for this workspace."}'
;;
esac
One limit to know: beforeMCPExecution does not run in Cursor's cloud agents, while beforeShellExecution does.
Example 3: keep sensitive files away from the model
A beforeReadFile hook can deny a read before the contents reach the model. Use it for files like .env, private keys and customer exports. Pair it with a .cursorignore entry for the same paths, so the protection does not depend on a single mechanism.
Hooks are not a sandbox
Hooks make your rules enforceable, but they only see what Cursor tells them. A matcher on curl does not stop a script that the agent writes and then runs. Cursor describes its own Run Modes as best-effort guardrails rather than a hard security boundary, and the same applies here. Combine hooks with narrow allowlists, Privacy Mode, Workspace Trust for repositories you did not write, and version control so you can undo anything.
A repository's .cursor/hooks.json is also a script you are about to run. Read it before you open a project you do not trust.
Check what actually ships
Hooks protect your machine while you build. They say nothing about the app you deploy: keys that ended up in the JavaScript bundle, missing security headers or endpoints that answer without a login. Run a free FixPrompt scan on the live URL, then hand the report's prompt back to Cursor.
Prompt for your coding agent
Add Cursor hooks to this project to enforce our security rules:
1. In .cursor/hooks.json, add a beforeShellExecution hook with failClosed true that blocks
curl, wget and nc, using a small script that returns a deny decision with a clear message.
2. Add a beforeMCPExecution hook with failClosed true that only allows the MCP servers listed
in our MCP config and denies everything else.
3. Add a .cursorignore that covers .env files, private keys and customer data.
Explain each file you create and how I can test that the hooks block what they should.
Check your app in minutes
Paste your URL and get a plain-English report with prioritized risks and a fix prompt for your AI. Your first scan is free.
Scan my app free