HookStackGitHub
Back to catalogue
SecurityPreToolUse· ReadPreToolUseBefore tool execution · can block⚡ blocking

Env file read guard

Your .env secrets never enter the model context

Blocks Read calls on .env files (except .env.example, .sample, .template, .dist) so secrets never enter the model context, where they could leak into logs, transcripts or generated code. The block message suggests safe alternatives: read .env.example for variable names, or grep for a key name without exposing its value. Complements write-side protection (protected paths) with read-side coverage.

What does the Env file read guard hook do?

Env file read guard is a Claude Code PreToolUse hook matching Read. It fires automatically at that lifecycle event — outside the model, so it can't be skipped or forgotten. Your .env secrets never enter the model context.

Use cases

  • Keeping production credentials out of session transcripts
  • Preventing secrets from being echoed into generated code or docs
  • Nudging the agent toward .env.example instead of the real file

Tags

#security#secrets#env#context

settings.json fragment

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read",
        "hooks": [
          {
            "type": "command",
            "command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/pre-read-env-guard.mjs"
          }
        ]
      }
    ]
  }
}

Script · .claude/hooks/pre-read-env-guard.mjs

#!/usr/bin/env node
// Bloque la lecture des fichiers .env — les secrets ne doivent pas entrer dans le contexte (PreToolUse Read)
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { basename } from 'path';

const SAFE_SUFFIXES = ['.example', '.sample', '.template', '.dist'];

export function run(input) {
  if (input.tool_name !== 'Read') return null;

  const filePath = input.tool_input?.file_path ?? '';
  if (!filePath) return null;

  const name = basename(filePath);
  if (!/^\.env(\..+)?$/.test(name)) return null;
  if (SAFE_SUFFIXES.some((s) => name.endsWith(s))) return null;

  return {
    decision: 'block',
    reason: `[env-guard] \`${name}\` likely contains secrets — they must not enter the model context (risk of leaking into logs, transcripts or generated code). Read \`.env.example\` for the variable names, or check a key exists without its value: \`grep -c '^MY_VAR=' ${name}\`.`,
  };
}

/* v8 ignore next 5 */
if (process.argv[1] === fileURLToPath(import.meta.url)) {
  const input = JSON.parse(readFileSync(0, 'utf8'));
  const result = run(input);
  if (result) process.stdout.write(JSON.stringify(result));
}