On Windows, Cursor’s hook stdin JSON payload includes a UTF-8 BOM that breaks standard JSON.parse(), causing security guards to silently degrade to allowing commands across all agent channels

Where does the bug appear (feature/product)?

Something else… — Workspace hooks (.cursor/hooks.json, beforeShellExecution)

Describe the Bug
On Windows, the JSON payload delivered to hook scripts via stdin is prefixed
with a UTF-8 BOM (\uFEFF). Any hook that does a standard
JSON.parse(readFileSync(0, "utf8")) throws on it. If the script handles the
parse error defensively (e.g. catch { return {} }), a security guard
silently degrades to allow-everything — no error surfaces anywhere.

We initially chased a phantom “subagents bypass beforeShellExecution” bug;
raw-logging stdin revealed the hook fires correctly on every channel (main
agent AND Multitask subagents, each with its own conversation_id) and the
payload is complete — it just starts with a BOM. So this is purely an
encoding issue, not a lifecycle one.

Steps to Reproduce

  1. Create .cursor/hooks.json with a beforeShellExecution hook pointing to
    a Node .mjs script.
  2. In the script, log the raw stdin:
    appendFileSync("log.txt", JSON.stringify(readFileSync(0, "utf8")))
    and then JSON.parse it without stripping anything.
  3. Ask the agent to run any shell command.
  4. Inspect log.txt: the payload starts with \uFEFF{. The JSON.parse
    throws SyntaxError: Unexpected token.

Workaround that fixes it:

const raw = readFileSync(0, "utf8").replace(/^\uFEFF/, "");

Expected Behavior
The stdin payload should be plain UTF-8 JSON without a BOM (or the BOM should
be documented so hook authors know to strip it). Given that hooks are used as
security guardrails, a silently unparseable payload is a safety issue: the
natural defensive catch turns the guard into allow-all.

Operating System
Windows 10/11 (repo checkout on the Windows filesystem; also verified the
same BOM from both PowerShell and WSL-side inspection)

Version Information

Cursor 3.13.10 (Windows 11)

For AI issues: which model did you use?
N/A — not model-related (reproduced identically with the hook fired by
main-agent and Multitask subagent sessions).

Additional Information
The hook lifecycle itself works correctly on Windows: we verified with a
controlled matrix that beforeShellExecution fires for the main agent and
for Multitask background subagents, and that deny responses are respected on
both channels once the BOM is stripped. The integrated (human) terminal
correctly does not trigger hooks. Only the BOM prefix is the problem.

Does this stop you from using Cursor?
No - Cursor works, but with this issue (though it can silently neutralize
security-oriented hooks, so the impact is worse than it looks).

Hi there!

We detected that this may be a bug report, so we’ve moved your post to the Bug Reports category.

To help us investigate and fix this faster, could you edit your original post to include the details from the template below?

Bug Report Template - Click to expand

Where does the bug appear (feature/product)?

  • Editor, Tab & Chat (autocomplete, Composer, in-editor agent)
  • Terminal & commands
  • Models, pricing & API keys (availability, Auto/Max, BYOK/Bedrock)
  • MCP & tools
  • Cloud Agents & Automations (cursor.com/agents, scheduled/event)
  • BugBot & Code Review
  • Cursor CLI
  • Cursor Mobile
  • Remote (SSH / Dev Containers / WSL)
  • Account, billing & login
  • Something else…

Describe the Bug
A clear and concise description of what the bug is.


Steps to Reproduce
How can you reproduce this bug? We have a much better chance at fixing issues if we can reproduce them!


Expected Behavior
What is meant to happen here that isn’t working correctly?


Screenshots / Screen Recordings
If applicable, attach images or videos (.jpg, .png, .gif, .mp4, .mov)


Operating System

  • Windows 10/11
  • MacOS
  • Linux

Version Information

  • For Cursor IDE: Menu → About Cursor → Copy
  • For Cursor CLI: Run agent about in your terminal
IDE:
Version: 2.xx.x
VSCode Version: 1.105.1
Commit: ......

CLI:
CLI Version 2026.01.17-d239e66

For AI issues: which model did you use?
Model name (e.g., Sonnet 4, Tab…)


For AI issues: add Request ID with privacy disabled
Request ID: f9a7046a-279b-47e5-ab48-6e8dc12daba1
For Background Agent issues, also post the ID: bc-…


Additional Information
Add any other context about the problem here.


Does this stop you from using Cursor?

  • Yes - Cursor is unusable
  • Sometimes - I can sometimes use Cursor
  • No - Cursor works, but with this issue

The more details you provide, the easier it is for us to reproduce and fix the issue. Thanks!

Hey, thanks for the report. The diagnostics are great, and they’re fully confirmed. This is a bug on our side. On Windows, the payload really does get sent to the hook’s stdin with a UTF-8 BOM \uFEFF at the start, due to how PowerShell pipes text into a native command. The temp file with the payload is clean. The BOM shows up only at the piping step.

Your workaround is correct, so please keep it:

const raw = readFileSync(0, "utf8").replace(/^\uFEFF/, "");

Also, failClosed: true is the right call for guard hooks. By default, beforeShellExecution is fail-open, so a swallowed parsing error silently degrades to allow-everything. You already found this. Just confirming it’s expected fail-open behavior, not a separate lifecycle bug, and that the main-agent and Multitask channels use the same path.

I’ve filed the bug internally, and I also noted that the hooks docs example currently hits this same issue on Windows. I can’t share an ETA for a fix yet. Once I have an update, I’ll reply in the thread.