beforeShellExecution hook: malformed JSON response silently allows command instead of blocking

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

beforeShellExecution hook: malformed JSON response silently allows command instead of blocking

Where does the bug appear (feature/product)?

Cursor IDE - Hooks (beforeShellExecution)

Describe the Bug

When a beforeShellExecution hook returns malformed JSON (e.g. due to unescaped special characters in the response), Cursor silently allows the command to execute instead of blocking it. This bypasses "permission": "deny" hooks without any indication to the user.

This is a security concern: hooks intended to block dangerous commands (like git push, rm -rf, etc.) can be silently bypassed by commands that contain quotes, newlines, or other characters that break the hook’s JSON output.

Steps to Reproduce

  1. Create a beforeShellExecution hook that blocks git push:
#!/bin/bash
input=$(cat)
command=$(echo "$input" | jq -r '.command // empty')

if echo "$command" | grep -qE 'git.*push'; then
    cat << EOF
{
  "continue": true,
  "permission": "deny",
  "user_message": "Git push blocked.",
  "agent_message": "Blocked command: $command"
}
EOF
else
    cat << EOF
{
  "continue": true,
  "permission": "allow"
}
EOF
fi
  1. Run a simple command – hook correctly blocks it:
git push
  1. Run a command with special characters – hook is bypassed, push succeeds:
git commit -m "$(cat <<'EOF'
some message
EOF
)" && git push

The $command variable contains quotes and newlines which, when interpolated into the heredoc JSON response, produce invalid JSON. Cursor fails to parse it and silently falls back to allowing the command.

Expected Behavior

When Cursor cannot parse a hook’s JSON response, it should default to deny (or at minimum ask), not allow. A beforeShellExecution hook exists specifically to gate command execution – failing open defeats its purpose.

Ideally Cursor should also surface a warning to the user that a hook returned invalid output.

Workaround

Use jq to build the JSON response instead of heredoc interpolation:

if echo "$command" | grep -qE 'git.*push'; then
    jq -n \
      --arg cmd "$command" \
      '{
        continue: true,
        permission: "deny",
        user_message: "Git push blocked.",
        agent_message: ("Blocked command: " + $cmd)
      }'
fi

Operating System

macOS (darwin 25.3.0)

Current Cursor Version

2.5.17

Steps to Reproduce

See above

Operating System

MacOS

Version Information

See above.

Does this stop you from using Cursor

Sometimes - I can sometimes use Cursor

Hey, thanks for the detailed report and the jq workaround. It really helps.

You’re right. This is a real issue. When a hook returns exit code 0 but outputs invalid JSON, Cursor currently falls through and allows the operation. For security-focused hooks, fail-open behavior is the wrong default.

I’ve passed this on to the team.

For now, the jq approach you mentioned is the best workaround. It properly escapes special characters and completely avoids the invalid JSON problem.

Let me know if you run into anything else.