Claude Code - automation - security - vibe coding
4 min lectura

Claude Code hooks: block accidental .env edits before they happen

A tested PreToolUse handler blocks accidental .env edits in Claude Code. Copy the Python script, verify exit codes, and learn its limits.

Claude Code hooks: block accidental .env edits before they happen

You ask Claude Code to repair your app’s login flow while a secret sits in .env.local. One broad edit can alter that file before you notice the mistake in a diff. “Please be careful” depends on the model. A hook can make a deterministic decision before a write. This guide rejects edits aimed at environment files.

I tested the handler with simulated JSON events: it rejected .env, .env.local, and .env.production, while allowing src/app.py and .env.example. It also rejected malformed JSON. I did not run a full Claude Code session, so the final in-product check belongs in your own installation. Anthropic’s official hooks guide documents PreToolUse and exit code 2 for blocking a tool operation.

Put the check before the write

Claude Code sends a JSON event to a hook on standard input before it invokes a tool. Our handler reads tool_input.file_path, extracts the final file name, and exits with 2 for .env or a variant such as .env.local. Other paths return 0. Registering it under PreToolUse means the decision happens before the edit. A PostToolUse hook could inspect the result, but it could not prevent that particular write.

The purpose is narrow and useful. A broad task like “connect Supabase” should not silently modify local credentials. This rule stops that edit path and explains why. It does not inspect or transmit file contents.

Hook flow diagram: .env.local blocked and normal code allowed

1. Add the handler

Save the following code as .claude/hooks/guard_sensitive.py in your project. It needs Python 3 on the computer running Claude Code. The complete script is:

#!/usr/bin/env python3
"""Claude Code PreToolUse guard for Edit and Write tool calls."""

import json
import pathlib
import sys


try:
    event = json.load(sys.stdin)
    raw = event["tool_input"]["file_path"]
    if not isinstance(raw, str) or not raw.strip():
        raise ValueError("missing file_path")
except (KeyError, TypeError, ValueError, json.JSONDecodeError):
    print("Cannot verify edit target; edit blocked.", file=sys.stderr)
    sys.exit(2)

name = pathlib.PurePosixPath(raw.replace("\\", "/")).name.lower()
if (name == ".env" or name.startswith(".env.")) and name != ".env.example":
    print(f"Sensitive file blocked: {name}", file=sys.stderr)
    sys.exit(2)

sys.exit(0)

.env.example remains editable because it is normally a template without secrets. If your team uses another sensitive name, such as credentials.json, add an explicit check. Never put the actual credentials in the hook.

2. Register it with Claude Code

Add this entry to the hooks object in .claude/settings.json. Keep any existing hooks when you merge the new entry; replacing the entire file could remove a rule your team already depends on.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard_sensitive.py\""
          }
        ]
      }
    ]
  }
}

The Edit|Write matcher limits this handler to those two tools. $CLAUDE_PROJECT_DIR points at the active project. Anthropic distinguishes shareable project settings from local settings. Review a committed hook like other code because it executes on each developer’s machine.

3. Run a test with an expected failure

From the project root, run the blocked case first. It should print a message about .env.local; echo $? should then print 2.

printf '%s' '{"tool_input":{"file_path":".env.local"}}' | python3 .claude/hooks/guard_sensitive.py
echo $?

Now try an ordinary source file. This time echo $? should print 0.

printf '%s' '{"tool_input":{"file_path":"src/app.py"}}' | python3 .claude/hooks/guard_sensitive.py
echo $?

Both outcomes matched in my local handler test. .env.example and a normal Windows path were allowed; invalid JSON was rejected. Next, open /hooks inside Claude Code and confirm that PreToolUse appears. In a disposable project with no real credentials, ask Claude to edit .env.local and check that it stays unchanged. That last in-product check is still pending; do not confuse a simulated event with a production test.

Know where the barrier ends

A Bash command that writes the same file does not pass through the Edit|Write matcher. Anthropic explains this in its matcher guide. The script also does not resolve a symbolic link to its final target or prevent another app from editing the file. Keep secrets out of Git, configure .gitignore, and review tool permissions. A mandatory rule across every write path needs broader controls.

For a small app, this is a useful first guard: it blocks a common accidental edit with a rule you can test without spending model tokens. Expand it only for the paths and tools your agent actually uses.

Comentarios

Aún no hay comentarios. Sé el primero.

Deja un comentario