Security: hooks system lacks visibility and write-time confirmation — silent global exfiltration surface
Summary
Claude Code's hooks field in ~/.claude/settings.json is a legitimate and useful feature. This is not a bug report. It's a request to harden a design surface that makes hooks trivially weaponizable without any user awareness.
A real-world case already exists. The package "Cozempic" (Ruya-AI/cozempic) was audited by community members in #48041 after its author was found dropping templated install prompts across 50+ issues in this repo. The audit found it: writes a global SessionStart hook into ~/.claude/settings.json on npm install, registers a PostToolUse hook with "matcher": "" giving visibility into every tool call, and runs uv pip install --upgrade cozempic --quiet on every session start. As one auditor noted: "Nothing in this list is currently malicious" — but the infrastructure for a one-click supply chain attack is pre-staged.
What hooks can do
A PreToolUse or PostToolUse hook registered in settings.json will:
- Fire on any tool call matching its matcher; an empty matcher matches all tool calls across all Claude Code sessions (local CLI, remote web environments, subagent sessions)
- Receive full tool input and output: file contents, bash stdout/stderr, web fetch results
- Receive a
transcript_pathpointing to the full session JSONL log (~/.claude/projects/<project-id>/<session-id>.jsonl) - Run as a shell command; output goes to debug logs by default and is not shown to the user unless the hook explicitly returns
additionalContext
The threat model
Any process running as the current user can write to ~/.claude/settings.json. There is no OS-level protection, no signature check, and no user confirmation prompt. Claude Code shows a spinner during hook execution, but there is no persistent indicator that hooks are registered at rest — a user has no way to know hooks exist without reading the documentation and manually inspecting settings.json.
| Attack vector | Mechanism |
|---|---|
| Malicious npm package | postinstall script writes a hook before the user opens their editor |
| Compromised Homebrew formula | Install-time script appends to settings.json |
| Untrusted Claude Code skill | Skills can write files; a skill can register itself as a hook |
| Malicious MCP server | Tool calls can produce side effects that stage a hook write |
Once registered, the hook receives every tool input/output: source files, environment variables echoed in bash output, API keys in web results, database query results from connected MCP servers. The user never sees a confirmation, a warning, or an indicator.
No persistent process required. The attacker writes one JSON key and disappears. Claude Code does the delivery. Unlike the standard "any process can read your files" risk, there is no process to kill, no daemon to detect, no shell rc entry to find. The only artifact is a JSON key in a file nobody checks.
Developers with MCP connections to internal databases, Slack, GitHub, or cloud providers pass company-wide data through Claude Code. A single compromised install taps everything that developer can reach through their MCP configuration.
What I'm not asking for
- Do not remove hooks. They are useful for automation, logging, and formatting.
- Do not require confirmation on every hook execution.
What I am asking for
1. Visible UI indicator when hooks are registered
When hooks are active, Claude Code should show a persistent, unambiguous indicator: a status bar item, startup notice, or badge. Users should not have to read the documentation to know their sessions are being intercepted.
2. Confirmation when an external process registers a hook
When anything outside Claude Code's own install flow writes to the hooks field in settings.json, Claude Code should prompt for confirmation on next launch. The dialog should show the full hook command.
3. Security documentation
The hooks documentation should include a callout covering: what data hooks receive, that any user-level process can register them, how to audit active hooks, and a recommendation to inspect settings.json after installing third-party tools.
Prior art
The same threat model (silent persistence, user-level, no visible process) has been addressed elsewhere:
macOS Login Items (Ventura+) is the closest analogy. When any app installs something that persists and runs on your behalf, macOS shows a notification: "X added items that can run in the background." Users manage them in System Settings > General > Login Items. Apple added this because the attack surface is identical.
VS Code workspace trust restricts extension execution for untrusted sources. Installed extensions are always visible in the sidebar.
Browser extensions (Chrome, Firefox, Safari) require an explicit permission dialog at install time listing declared capabilities. Nothing installs silently.
npm postinstall scripts are the gap: they run automatically with no capability declaration and no prompt. This is the delivery mechanism for the attack in #48041.
Claude Code already has this for enterprise
Server-managed settings already implement hook approval dialogs for enterprise deployments. When an admin pushes a hook via the managed settings console, users must explicitly approve it before it takes effect. The architecture and UX pattern already exist inside Claude Code.
The ask is to extend that approval mechanism to the local write path. Individual developers currently get nothing equivalent.
References
- Active case: #48041 (Cozempic supply chain audit)
- Related: #48100 (documentation warning request)
- Hooks documentation
- Server-managed settings
This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗