Enforce in Claude Code (hook)
The MCP connector gives Claude Vulnify tools, and Claude decides when to call them. That is guidance, not enforcement. In Claude Code you can enforce it: a PreToolUse hook runs before each matching tool call and asks Vulnify first.
| Vulnify result | What Claude Code does |
|---|---|
ALLOW |
Normal permission flow. VULNIFY_HOOK_ALLOW_MODE=allow auto-approves instead |
REVIEW |
Denied with “waiting for approval in Vulnify” and the event id. Approve at app.vulnify.io/approvals, then ask Claude to retry |
BLOCK |
Denied with Vulnify’s reasons |
| Vulnify unreachable, bad key, timeout | Denied (fail closed). VULNIFY_HOOK_FAIL_OPEN=true lets it run with a “degraded” warning |
Set it up (one command)
Section titled “Set it up (one command)”npx @vulnify/cli hook claude-codeRun it in your project. It copies the hook to .claude/hooks/vulnify-hook.mjs and adds it to .claude/settings.json. Add --user to install it in ~/.claude for every project, or --uninstall to remove it.
The key comes from VULNIFY_API_KEY, or the command asks for it with hidden input. It is never printed. A key you type is saved in .claude/settings.local.json, which is git-ignored and readable only by you. With --user it goes in ~/.claude/settings.json. A TEST key from app.vulnify.io/api-keys works for trying it out.
Set it up by hand
Section titled “Set it up by hand”- Download
vulnify-hook.mjsinto your project as.claude/hooks/vulnify-hook.mjs. It is one Node 18+ file with no dependencies. - Export your key in the shell you start Claude Code from:
export VULNIFY_API_KEY=vln_.... Don’t commit it. - Add the hook to
.claude/settings.json, or~/.claude/settings.jsonfor every project:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash|Write|Edit|MultiEdit|NotebookEdit|WebFetch|mcp__.*", "hooks": [ { "type": "command", "command": "node", "args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/vulnify-hook.mjs"], "timeout": 30, "onFailure": "block" } ] } ] }}onFailure: "block" (Claude Code 2.1.295 or later) keeps the call blocked even if the script can’t start. Run /hooks in Claude Code to check that it loaded.
What gets checked
Section titled “What gets checked”Bash:rm,DROP TABLE,git push --forcebecomeDELETE_DATA(curl -X DELETE,aws s3 rmandkubectl deletego toEXTERNAL_API). Mail sends becomeSEND_EMAILtoEXTERNAL_EMAIL:mail,sendmail,swaks,msmtp,curl smtp://or--mail-rcpt,aws ses send-email, the SendGrid, Mailgun, Resend and Postmark APIs, andsmtplib. Uploads such ascurl -F/-T/-d,scp,rsync,git push,npm publish,aws s3 cpand Slack webhooks becomeEXPORT_DATAtoEXTERNAL_API. Other commands that can change things becomeWRITE_DATA. Read-only commands such asls,cat,grep, andgit statusare skipped.Write,Edit,MultiEdit,NotebookEdit:WRITE_DATAon the resourcelocal files.WebFetch:READ_DATAtoEXTERNAL_APIon the resourceweb.- MCP tools (
mcp__<server>__<tool>): judged by the tool name. Send and post tools becomeSEND_EMAILtoEXTERNAL_EMAILwhen the server is a mail server or the input has email addresses into,ccorrecipients, andEXPORT_DATAotherwise. Delete tools becomeDELETE_DATA. Create and update tools becomeWRITE_DATA. Read tools are skipped unlessVULNIFY_HOOK_CHECK_READS=true. The resource ismcp:<server>. Read,Glob,Grep,TodoWrite,WebSearch, and Vulnify’s own tools are skipped.
recordsAffected is an estimate. It comes from SQL LIMIT n, flags such as --limit, -n or --count, and the number of email recipients or files. A DELETE or UPDATE without WHERE, DROP or TRUNCATE counts as 10,000, and a recursive or wildcard rm as 100. For MCP calls it uses limit and count fields and the length of id or recipient lists. Anything else counts as 1. Policies on record counts, such as “more than 1,000 records goes to REVIEW”, apply to these estimates.
For send, export, delete, and WebFetch calls, the command or tool input is sent as content for the sensitive-data scan. It is scanned in memory and not stored. Set VULNIFY_HOOK_SEND_CONTENT=never to turn that off.
The agent name defaults to claude-code (VULNIFY_AGENT). New agent names register on first use and count toward your plan’s agent limit.
Every setting is listed in the example README.
Also: the Claude skill
Section titled “Also: the Claude skill”The vulnify-guard-actions skill tells Claude to call decide_action before consequential actions with any connector, in Claude and Claude Code. It is guidance for the model. The hook is the enforced path.

