Hey, thanks for the detailed report (and for the trash panda, I appreciated it).
Good news: your hooks are actually running. Bad news: on Windows the deny rule isn’t applied for a specific reason. The JSON payload that the CLI pipes to the hook via stdin on Windows gets prefixed with a UTF-8 BOM \uFEFF. Because of that, a standard JSON.parse fails, and the defensive catch in the script quietly falls back to allow. That’s why your manual test denies (stdin has no BOM), but the real run doesn’t. This also explains why failClosed behaved inconsistently.
Workaround: strip the BOM before parsing:
const raw = require("fs").readFileSync(0, "utf8").replace(/^\uFEFF/, "");
const payload = JSON.parse(raw);
After this, your deny fence and failClosed should work correctly on Windows. You should apply the same fix to Claude-imported hooks from ~/.claude too, they fail for the same reason.
This is a known issue we’re tracking, detailed in this thread: 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. I can’t give an ETA yet, but I shared this with the team as confirmation and from the CLI side too.
The MCP-from-plugin not loading in the CLI, and the “include third-party plugins, skills and other configs” setting being ignored by the CLI, are separate issues and not related to the BOM. To avoid mixing everything in one thread and so each can be tracked, please open separate threads for them with your CLI version and repro steps. We’ll look into each one.
Let me know if stripping the BOM helped.