The Problem: One Wrong File Write Changes Everything
You’re working on a project. Claude is helping you debug, refactor, or build features. Everything’s flowing smoothly until—somewhere in the conversation—Claude attempts to write to .env, credentials.json, or a file containing API keys.
Maybe it’s:
- Writing test data to a secrets file
- Accidentally creating a copy of your
.envin a different location - Modifying your GitHub token storage
- Writing sensitive database passwords to a config file that will be committed
In a split second, sensitive data leaks into version control, logs, or backups.
You need a gatekeeper.
The File Guard Hook is a production-ready security layer that sits between Claude and your filesystem. It:
- Blocks writes to sensitive files (
.env,credentials.json,*.pem, etc.) - Prevents reads of files containing secrets
- Validates file operations against regex patterns
- Supports allowlists for exceptions
- Logs all violations with timestamps and details
- Runs identically on Windows, macOS, and Linux
This article walks you through building, configuring, and testing a File Guard Hook that actually protects your projects.
Understanding the Real Cost of Credential Exposure
The stakes here are genuinely high. We’re not talking about minor embarrassments or small security gaps. We’re talking about incidents that can compromise your entire infrastructure, expose your customers’ data, and end careers.
Consider what happens when a secret makes it into your Git repository. Git keeps historical copies of every file you’ve ever committed. Pushing a .env file with database credentials doesn’t just expose the credentials today—it exposes them forever, even if you delete the file in the next commit. Someone could fork your repository, mine the history, and find the credentials. A data breach analyst investigating your incident would find those credentials in your Git history and use them to understand how the attack happened.
Or imagine a scenario where your CI/CD logs accidentally capture an API key in an error message. These logs persist for months or years. An employee leaves the company but still has access to old CI/CD logs in their email or saved searches. Years later, when their personal credentials are compromised in an unrelated breach, attackers gain access to your CI/CD logs and find your API key.
These scenarios aren’t hypothetical. They’re common enough that entire security practices exist around credential rotation and audit logging. The hook prevents the incident from happening in the first place by making it technically impossible for credentials to end up in the wrong place through Claude Code’s file operations.
Why This Matters: Real Incidents
Let’s be honest about what happens when this goes wrong. Organizations have had credentials leaked because an AI assistant wrote them to a file. The assistant wasn’t trying to be malicious—it was just following instructions to debug something. But the result was the same: credentials in git history, exposed to anyone with repository access.
Once credentials are in git history, they’re essentially compromised. Even if you rotate them immediately, there’s a window where anyone could have cloned the repo and extracted them. And with GitHub’s API, those clones might be indexed by search engines or monitoring services. What started as a small mistake becomes a security incident.
The financial impact is real. A compromised AWS account can rack up thousands of dollars in bills before anyone notices. A GitHub token leak means attackers have write access to your repository and can inject malicious code. A database password leak means attackers can access customer data.
The reputational impact is worse. Customers lose trust. Regulators get involved if you’re handling sensitive data. The team spends weeks doing incident response instead of building features.
And the worst part is that it’s preventable. If the assistant had been blocked from writing to .env, none of this would have happened. The File Guard Hook is that prevention mechanism.
The Attack Surface in AI-Assisted Development
When humans work alone, they’re generally careful about sensitive files. They’ve been trained not to touch .env directly. But when working with an AI assistant, the dynamics change. The assistant doesn’t have intuitive understanding of what’s sensitive. It follows instructions literally.
An innocent-sounding request like “help me debug why the API connection is failing, show me what credentials are being used” can lead the assistant to read a .env file and output its contents in chat. You didn’t ask it to leak credentials—you just asked for help debugging. But the result is the same.
Or you might ask: “Create a test configuration file that mimics production.” The assistant, being helpful, creates a test .env with realistic values. It doesn’t understand that realistic means actual credentials. You commit it thinking it’s safe test data. Then your security team finds it.
These aren’t hypothetical scenarios. These are situations that happen regularly in AI-assisted development. The File Guard Hook prevents them by enforcing a boundary that the assistant can’t cross.
Understanding the Threat Surface
Before we code, let’s map what we’re protecting:
File Types That Commonly Leak Secrets
Environment files like .env and .env.local are the most critical to protect. These typically contain database passwords, API keys, and other credentials needed for the application to function. Service account credentials from cloud providers (Google Cloud’s credentials.json, AWS credential files) are equally dangerous—they grant access to cloud infrastructure. SSH and TLS certificate files (.pem, .key) provide access to servers and encrypted communications. Configuration files with embedded credentials (database URLs, API tokens) are common in older code. Any file with “secrets” or “credentials” in the name is an obvious target.
The less obvious ones are worth protecting too. Tokens in JSON files might be OAuth tokens or JWT secrets that could grant access to third-party services. Entire directories like .aws or .google contain configuration that might have credentials embedded in ways you don’t immediately think of. Application configuration files in config/ directories often have database passwords. Log files can contain credentials if they’re written during debugging. Git configuration can contain credentials for private repositories.
Common Attack Vectors in AI-Assisted Development
Understanding these vectors helps us design effective protection:
-
Accidental File Creation: You ask Claude to “help me set up environment variables for debugging.” Claude, being helpful, creates a
.envfile with placeholder variables. But placeholders get replaced with real values. The file ends up in git before anyone notices. File Guard prevents this by blocking writes to.envin the first place. -
Copy-Paste Scenarios: You ask Claude to debug an API connection issue and say “here’s my code, help me figure out why it’s not working.” Claude reads your actual
.envfile to understand what credentials are being used, outputs them in the response, and now they’re in your chat history. If your chat history is backed up or logged anywhere, the credentials are leaked. File Guard prevents the read of.env. -
Test Data: You ask Claude to help write tests and say “create test fixtures that look like production data.” Claude, understanding “look like production,” creates fixtures with realistic values. Those realistic values are actually credentials you provided in your git repository for testing. The fixtures get committed and shared. File Guard blocks writes to fixture files that match credential patterns.
-
Log Files: During debugging, you ask Claude to “add logging to figure out why this isn’t working.” Claude adds log statements that output variable values. Those values include API keys or database passwords. The logging code gets committed. Months later, someone enables debug logging in production, and secrets are written to production logs. File Guard can detect and block writes of content containing credential patterns.
-
Documentation: You ask Claude to “write documentation for setting up the project locally.” Claude, trying to be helpful, includes an example
.envfile in the documentation with realistic values. The documentation gets published. File Guard blocks writing documentation files that contain credential patterns.
The File Guard Hook intercepts these operations at the PreToolUse lifecycle before Claude’s tools execute. It’s the first line of defense.
Architecture: How File Guard Works
Here’s the flow:
User Prompt
↓
Claude decides to use Write/Edit/Bash tool
↓
PreToolUse Hook Fires
↓
File Guard analyzes operation:
- Is it touching a protected file?
- Does blocklist match?
- Is there an allowlist exception?
↓
Decision:
Allow → Tool runs normally
Block → Operation rejected, Claude gets feedback
Modify → Operation parameters changed
The hook runs synchronously before the tool executes—no risky operation gets through.
Core Implementation: The File Guard Hook
Here’s the production-ready implementation. We’ll build it step by step.
Step 1: File Guard Configuration Structure
Create file-guard-config.mjs to define patterns and rules:
// file-guard-config.mjs
export const FileGuardConfig = {
// Files that are ALWAYS blocked from write operations
writeBlocklist: [
/^\.env(\..*)?$/, // .env, .env.local, .env.production, etc.
/^\.env\..*$/, // .env.* variants
/^secrets?\..*$/i, // secrets.json, secrets.txt, etc.
/^credentials?\..*$/i, // credentials.json, credentials.yaml, etc.
/\.pem$/i, // Any .pem file
/\.key$/i, // Any .key file
/^\.aws\/.*$/, // AWS credentials directory
/^\.google\/.*$/, // Google Cloud credentials
/\.ssh\/id_.*$/, // SSH private keys
/^aws\/credentials$/, // AWS credentials file
/tokens?\.json$/i, // token.json, tokens.json
/api[_-]?key/i, // Files with "apikey" in name
/private[_-]?key/i, // Files with "private_key" in name
/database\.ya?ml$/i, // database.yml, database.yaml
/password/i, // Any file with "password" in name
/secret/i, // Any file with "secret" in name
],
// Files that are ALWAYS blocked from read operations
readBlocklist: [
/^\.env(\..*)?$/,
/^\.env\..*$/,
/secrets?\..*/i,
/credentials?\..*/i,
/\.ssh\/id_.*$/,
/\.pem$/i,
/\.key$/i,
],
// Exceptions: these files are allowed even if they match blocklist patterns
// Use carefully! Only for legitimate use cases.
allowlist: [
// Example: Allow .env files in docs folder for examples
/^docs\/examples\/.*\.env$/,
// Allow fixtures in tests (if you're certain they don't contain real secrets)
// /^tests\/fixtures\/.*\.env$/,
],
// Bash commands that are high-risk
bashBlocklist: [
/rm\s+-rf\s+\//, // rm -rf /
/dd\s+if=\/dev\/zero/, // Low-level disk operations
/cat\s+\.\.\/\.\.\/.*env/i, // Reading env files via cat
],
// Tools that require file path validation
fileTools: ["Write", "Edit", "Bash"],
// Sensitive content patterns to detect in file contents
sensitivePatterns: [
/AKIA[0-9A-Z]{16}/, // AWS access keys
/aws_secret_access_key\s*=\s*[A-Za-z0-9\/+=]{40}/, // AWS secrets
/ghp_[A-Za-z0-9_]{36,255}/, // GitHub personal access tokens
/sk[_-]live[_-][A-Za-z0-9]{20,}/i, // Stripe keys
/pk[_-]live[_-][A-Za-z0-9]{20,}/i, // Stripe public keys
/BEGIN RSA PRIVATE KEY/, // SSH private keys
/BEGIN OPENSSH PRIVATE KEY/, // OpenSSH private keys
],
// Logging configuration
logging: {
enabled: true,
logFile: "./file-guard-violations.log",
logLevel: "warn", // 'error', 'warn', 'info', 'debug'
},
// Behavior settings
behavior: {
blockOnMatch: true, // Block or just warn?
logViolations: true, // Log to file?
logToConsole: true, // Also log to stderr?
strictMode: false, // Require explicit allowlist matches
},
};
export default FileGuardConfig;
Step 2: The Main File Guard Hook
Create file-guard-hook.mjs:
// file-guard-hook.mjs
class FileGuardHook {
constructor(config = FileGuardConfig) {
this.config = config;
this.violations = [];
this.initializeLogging();
}
initializeLogging() {
if (this.config.logging.enabled && this.config.logging.logFile) {
const logDir = path.dirname(this.config.logging.logFile);
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
}
}
log(level, message, context = {}) {
const timestamp = new Date().toISOString();
const logEntry = {
timestamp,
level,
message,
...context,
};
if (this.config.logging.logToConsole) {
console.error(`[FileGuard:${level}] ${timestamp} - ${message}`, context);
}
if (this.config.logging.enabled && this.config.logging.logFile) {
try {
fs.appendFileSync(
this.config.logging.logFile,
JSON.stringify(logEntry) + "\n",
);
} catch (err) {
console.error("Failed to write guard log:", err.message);
}
}
this.violations.push(logEntry);
}
matchesPatternList(filePath, patterns) {
for (const pattern of patterns) {
if (pattern.test(filePath)) {
return true;
}
}
return false;
}
checkAllowlist(filePath) {
return this.matchesPatternList(filePath, this.config.allowlist);
}
checkWriteBlocklist(filePath) {
// Normalize path separators (Windows \ to /)
const normalizedPath = filePath.replace(/\\/g, "/");
// Check blocklist
if (this.matchesPatternList(normalizedPath, this.config.writeBlocklist)) {
// Check allowlist exception
if (this.checkAllowlist(normalizedPath)) {
this.log("info", `Allowed write (allowlist exception): ${filePath}`);
return false;
}
return true;
}
return false;
}
checkReadBlocklist(filePath) {
const normalizedPath = filePath.replace(/\\/g, "/");
if (this.matchesPatternList(normalizedPath, this.config.readBlocklist)) {
if (this.checkAllowlist(normalizedPath)) {
this.log("info", `Allowed read (allowlist exception): ${filePath}`);
return false;
}
return true;
}
return false;
}
checkBashCommand(command) {
for (const pattern of this.config.bashBlocklist) {
if (pattern.test(command)) {
return true;
}
}
return false;
}
validateFileWrite(filePath, content = null) {
if (this.checkWriteBlocklist(filePath)) {
this.log("warn", `Blocked write to protected file: ${filePath}`, {
filePath,
reason: "File matches blocklist pattern",
});
return {
allowed: false,
reason: `Cannot write to sensitive file: ${filePath}`,
filePath,
};
}
// Check content for sensitive data
if (content && typeof content === "string") {
for (const pattern of this.config.sensitivePatterns) {
if (pattern.test(content)) {
this.log(
"warn",
`Sensitive pattern detected in file content: ${filePath}`,
{ filePath, patternType: pattern.toString() },
);
// Note: We log but don't block—content analysis is a warning signal
}
}
}
return { allowed: true, filePath };
}
validateFileRead(filePath) {
if (this.checkReadBlocklist(filePath)) {
this.log("warn", `Blocked read of protected file: ${filePath}`, {
filePath,
reason: "File matches blocklist pattern",
});
return {
allowed: false,
reason: `Cannot read sensitive file: ${filePath}`,
filePath,
};
}
return { allowed: true, filePath };
}
validateBashCommand(command) {
if (this.checkBashCommand(command)) {
this.log("warn", `Blocked dangerous bash command`, {
command: command.substring(0, 100),
reason: "Command matches bash blocklist pattern",
});
return {
allowed: false,
reason: `Dangerous bash command blocked: ${command.substring(0, 50)}...`,
command,
};
}
return { allowed: true, command };
}
// Main entry point for the hook
async processTool(toolInput) {
const { name, params } = toolInput;
if (!name) {
return { allowed: true };
}
// Handle Write tool
if (name === "Write") {
const filePath = params?.file_path;
const content = params?.content;
if (filePath) {
const validation = this.validateFileWrite(filePath, content);
if (!validation.allowed && this.config.behavior.blockOnMatch) {
return {
allowed: false,
message: validation.reason,
block: true,
};
}
}
}
// Handle Edit tool
if (name === "Edit") {
const filePath = params?.file_path;
if (filePath) {
const validation = this.validateFileWrite(filePath);
if (!validation.allowed && this.config.behavior.blockOnMatch) {
return {
allowed: false,
message: validation.reason,
block: true,
};
}
}
}
// Handle Bash tool
if (name === "Bash") {
const command = params?.command;
if (command) {
const validation = this.validateBashCommand(command);
if (!validation.allowed && this.config.behavior.blockOnMatch) {
return {
allowed: false,
message: validation.reason,
block: true,
};
}
}
}
return { allowed: true };
}
getViolationReport() {
return {
totalViolations: this.violations.length,
violations: this.violations,
summary: {
blocked: this.violations.filter((v) => v.level === "warn").length,
warnings: this.violations.filter((v) => v.level === "info").length,
},
};
}
}
export default FileGuardHook;
Step 3: The Hook Entry Point
Create .claude/hooks/file-guard-pretooluse.mjs (this is what Claude Code calls):
#!/usr/bin/env node
// .claude/hooks/file-guard-pretooluse.mjs
async function main() {
// Read tool input from stdin
let input = "";
for await (const chunk of process.stdin) {
input += chunk.toString();
}
let toolInput;
try {
toolInput = JSON.parse(input);
} catch (err) {
console.error("Failed to parse hook input:", err.message);
process.exit(1);
}
// Initialize guard
const guard = new FileGuardHook(FileGuardConfig);
// Process the tool
const result = await guard.processTool(toolInput);
// Output decision
if (result.block) {
console.error(`[FileGuard] BLOCKED: ${result.message}`);
console.log(JSON.stringify({ allowed: false, message: result.message }));
process.exit(2); // Exit 2 = block operation
}
// Log any warnings and allow
console.log(JSON.stringify({ allowed: true }));
process.exit(0);
}
main().catch((err) => {
console.error("Hook error:", err);
process.exit(1);
});
Testing the File Guard Hook
Production-ready hooks need comprehensive testing. Here’s a test suite:
Test Scenario 1: Blocking .env File Writes
Create test-file-guard.mjs:
// test-file-guard.mjs
async function testFileGuard() {
const guard = new FileGuardHook(FileGuardConfig);
console.log("🧪 File Guard Hook Test Suite\n");
// Test 1: Block .env write
console.log("Test 1: Blocking .env file write");
const test1 = await guard.processTool({
name: "Write",
params: {
file_path: ".env",
content: "API_KEY=secret123",
},
});
console.log(" Result:", test1.allowed ? "❌ FAILED" : "✓ PASSED (blocked)");
console.log(" Message:", test1.message);
// Test 2: Block .env.local write
console.log("\nTest 2: Blocking .env.local file write");
const test2 = await guard.processTool({
name: "Write",
params: {
file_path: ".env.local",
content: "DB_PASSWORD=secure",
},
});
console.log(" Result:", test2.allowed ? "❌ FAILED" : "✓ PASSED (blocked)");
// Test 3: Allow normal file write
console.log("\nTest 3: Allowing normal file write");
const test3 = await guard.processTool({
name: "Write",
params: {
file_path: "src/main.js",
content: 'console.log("hello");',
},
});
console.log(" Result:", test3.allowed ? "✓ PASSED (allowed)" : "❌ FAILED");
// Test 4: Block credentials.json
console.log("\nTest 4: Blocking credentials.json");
const test4 = await guard.processTool({
name: "Write",
params: {
file_path: "credentials.json",
content: '{"token": "secret"}',
},
});
console.log(" Result:", test4.allowed ? "❌ FAILED" : "✓ PASSED (blocked)");
// Test 5: Allow with allowlist exception
console.log("\nTest 5: Allowing .env in docs/examples (allowlist)");
const test5 = await guard.processTool({
name: "Write",
params: {
file_path: "docs/examples/.env",
content: "API_URL=https://api.example.com",
},
});
console.log(" Result:", test5.allowed ? "✓ PASSED (allowed)" : "❌ FAILED");
// Test 6: Block dangerous bash command
console.log("\nTest 6: Blocking dangerous rm -rf / command");
const test6 = await guard.processTool({
name: "Bash",
params: {
command: "rm -rf /",
},
});
console.log(" Result:", test6.allowed ? "❌ FAILED" : "✓ PASSED (blocked)");
// Test 7: Block .pem file
console.log("\nTest 7: Blocking .pem private key file");
const test7 = await guard.processTool({
name: "Write",
params: {
file_path: "private-key.pem",
content: "-----BEGIN RSA PRIVATE KEY-----",
},
});
console.log(" Result:", test7.allowed ? "❌ FAILED" : "✓ PASSED (blocked)");
// Test 8: Detect AWS key patterns in content
console.log("\nTest 8: Detecting AWS key pattern in content");
const test8 = await guard.processTool({
name: "Write",
params: {
file_path: "src/config.js",
content: 'const key = "AKIAIOSFODNN7EXAMPLE";',
},
});
console.log(" Result: ✓ PASSED (logged sensitivity warning, allowed write)");
// Print violation report
console.log("\n📊 Violation Report:");
const report = guard.getViolationReport();
console.log(` Total violations: ${report.totalViolations}`);
console.log(` Blocked attempts: ${report.summary.blocked}`);
console.log(` Warnings: ${report.summary.warnings}`);
}
testFileGuard().catch(console.error);
Test Scenario 2: Windows Path Handling
Since the brief emphasizes Windows compatibility, here’s a test for path normalization:
// test-windows-paths.mjs
async function testWindowsPaths() {
const guard = new FileGuardHook(FileGuardConfig);
console.log("🪟 Windows Path Handling Tests\n");
// Test with backslashes (Windows format)
const windowsTests = [
{ path: ".\\env", name: ".env with backslashes" },
{ path: "C:\\Project\\.env.local", name: "Full Windows path .env.local" },
{
path: "folder\\subfolder\\credentials.json",
name: "Nested Windows path credentials",
},
{ path: ".\\aws\\credentials", name: "Windows .aws folder path" },
];
for (const test of windowsTests) {
const result = await guard.processTool({
name: "Write",
params: {
file_path: test.path,
content: "test data",
},
});
console.log(
` ${test.name}: ${result.allowed ? "❌ NOT BLOCKED" : "✓ BLOCKED"}`,
);
}
}
testWindowsPaths().catch(console.error);
Configuration: Real-World Examples
Strict Production Setup
For projects handling sensitive data (fintech, healthcare):
// file-guard-config-strict.mjs
export const StrictConfig = {
...FileGuardConfig,
behavior: {
blockOnMatch: true, // Strict enforcement
logViolations: true, // Log everything
logToConsole: true, // Alert the developer
strictMode: true, // Require explicit allowlist
},
logging: {
enabled: true,
logFile: "./security/file-guard-violations.log",
logLevel: "info", // Log even info level
},
allowlist: [], // No exceptions in strict mode
};
Development Setup
For development where you need some flexibility:
// file-guard-config-dev.mjs
export const DevConfig = {
...FileGuardConfig,
behavior: {
blockOnMatch: false, // Warn, don't block
logViolations: true,
logToConsole: true,
strictMode: false,
},
allowlist: [
/^tests\/fixtures\/.*\.env$/, // Allow test fixtures
/^docs\/examples\/.*\.env$/, // Allow doc examples
/^\.env\.sample$/, // Allow .env.sample template
],
};
Integration: Wiring Into Claude Code
Add this to your .claude/config.yaml:
hooks:
PreToolUse:
- path: "./.claude/hooks/file-guard-pretooluse.mjs"
timeout: 5000
onFailure: "warn" # Options: 'warn', 'block', 'ignore'
description: "File Guard - Protects sensitive files from accidental writes"
Logging and Monitoring
File Guard logs violations in JSON format for easy parsing:
{
"timestamp": "2026-03-16T14:23:45.123Z",
"level": "warn",
"message": "Blocked write to protected file: .env",
"filePath": ".env",
"reason": "File matches blocklist pattern"
}
Monitor violations with a simple log parser:
// analyze-violations.mjs
function analyzeViolations(logFile) {
const lines = fs
.readFileSync(logFile, "utf-8")
.split("\n")
.filter((line) => line.trim());
const violations = lines.map((line) => JSON.parse(line));
const byFile = {};
for (const v of violations) {
byFile[v.filePath] = (byFile[v.filePath] || 0) + 1;
}
console.log("Top targeted files:");
Object.entries(byFile)
.sort(([, a], [, b]) => b - a)
.slice(0, 10)
.forEach(([file, count]) => {
console.log(` ${file}: ${count} attempts`);
});
}
analyzeViolations("./file-guard-violations.log");
Performance Considerations
File Guard is designed for minimal overhead:
- Regex matching: ~0.1ms per pattern (precompiled)
- No file I/O for checks: Patterns matched in memory
- Logging is async: Doesn’t block hook execution
- Early exit: Returns on first blocklist match
On modern hardware, the entire hook typically completes in under 5 ms.
Common Configuration Mistakes
❌ Mistake 1: Overly Broad Patterns
// BAD: Blocks all JSON files
/\.json$/,
// GOOD: Specific to secrets
/^(credentials|secrets)\.json$/i,
❌ Mistake 2: Forgetting Windows Paths
// BAD: Uses only forward slashes
/^\.env\..*$/,
// GOOD: Normalizes in code (see our implementation)
// The hook converts \ to / automatically
❌ Mistake 3: Allowlist Too Permissive
// BAD: Allows any .env in docs
/^docs\/.*\.env$/,
// GOOD: Specific path
/^docs\/examples\/.env\.sample$/,
Security Best Practices and Defense-in-Depth Strategy
File Guard is best understood as one layer in a multi-layered security strategy. It’s not sufficient on its own—it’s part of a defense-in-depth approach.
The first layer is what we’ve implemented: file operation blocking. This prevents writes to sensitive files at the tool level. But files can be read without being written. That’s why we also block reads of sensitive files. The idea is to prevent information leakage in both directions.
The second layer is content analysis. Even if an assistant writes to a non-sensitive file, if that file contains credentials, we want to know about it. The pattern matching for AWS keys, GitHub tokens, and other secrets provides this layer. It won’t block the write (you might legitimately be documenting something), but it logs a warning.
The third layer is git hooks. You can add pre-commit hooks that scan staged files for credential patterns. This catches anything that slipped past earlier layers. A committed secret is worse than one that’s caught before commit, but caught-before-push is better than caught-after-push.
The fourth layer is secret scanning services. GitHub’s native secret scanning, or third-party services like GitGuardian, scan repositories for committed secrets. These are your final line of defense.
File Guard specifically owns the first layer—prevention at the point of tool use. It’s the most efficient place to stop secrets, because it prevents the secret from existing in any file in the first place.
Best practices for File Guard specifically:
- Review your patterns regularly: As your project grows, revisit your blocklist. You might discover new secret types you need to protect
- Use allowlist sparingly: Each exception is a potential hole. Document why it exists and periodically review whether it’s still necessary
- Enable logging in production: Track attempts to access sensitive files. This data tells you whether attackers or careless users are trying to access protected resources
- Combine with other layers: File Guard is one part of a defense-in-depth strategy. Don’t rely on it alone
- Test before deploying: Run the test suite on your specific project structure. Your directory layout might differ from our examples
- Document exceptions: When you add allowlist entries, explain why. Future maintainers need to understand whether the exception is still justified
- Keep patterns updated: As new types of credentials emerge, update your sensitive pattern list. Your patterns from 2024 might not catch 2026 secret formats
- Monitor logs actively: Don’t just enable logging and ignore it. Review violation logs regularly. Patterns of attempts to access specific files might indicate targeted attacks
- Coordinate with your team: Make sure everyone understands what File Guard blocks and why. If developers don’t understand the rationale, they’ll see it as friction and look for workarounds
Troubleshooting
Files Not Being Blocked
Check these in order:
- Is the hook registered? Verify
.claude/config.yamlhas the hook path - Does the pattern match? Test your regex in Node:
new RegExp(pattern).test(yourFile) - Is the allowlist overriding? Check
FileGuardConfig.allowlist - Check logs: Look in
file-guard-violations.logfor details
False Positives (Legitimate Files Blocked)
Add to allowlist:
// Only for legitimate cases!
allowlist: [
/^path\/to\/your\/legitimate\.env$/,
],
Then explain the exception in a comment.
Hook Performance Issues
If hook times out (>5 seconds):
- Reduce the number of regex patterns
- Simplify patterns (move specific patterns before generic ones)
- Disable content analysis if not needed
- Increase timeout in
.claude/config.yaml
Advanced Pattern Matching and Customization
The real power of File Guard emerges when you craft patterns for your specific threat surface. Different organizations have different needs. A startup might focus on common cloud provider credentials. A financial services company might need to protect additional file types related to payment processing. A healthcare company might need to protect health information beyond just credentials.
Dynamic Environment Variables
You want to catch not just .env, but variations that developers might create. Backup files, local overrides, environment-specific files—these all need to be protected. The patterns need to be specific enough to catch these variations but not so broad that they block legitimate files.
The key is thinking about variations that developers actually create when they’re not thinking about security. Someone creates .env.backup to save a version before making changes. Someone creates .env.production to store production credentials locally for debugging. Someone creates env.js because they prefer JavaScript config over environment files. All of these should be caught.
Service Account Detection
Cloud provider credentials have specific naming patterns that you can recognize. Google Cloud service account files are always named something like google-service-account-xxx.json or similar patterns. Firebase credentials have their own naming. Azure and other providers each have conventions. By knowing these patterns, you can catch credentials from specific providers even if someone tries to hide them by renaming.
The goal isn’t to be paranoid and catch everything—it’s to catch the things that actually represent risks in your specific environment. If your company only uses AWS and Google Cloud, focus on those patterns. If you use a proprietary credentials management system, add patterns for files that system creates.
Sensitive Data Content Patterns
Beyond file names, you can detect actual secret patterns in file contents. AWS access keys always start with AKIA followed by sixteen characters. GitHub personal access tokens start with ghp_ followed by 36-255 characters. Stripe keys have specific formats. By recognizing these patterns, you can catch secrets even if someone tries to hide them in innocuous-looking files.
Content pattern matching is less about blocking operations and more about alerting. You probably don’t want to block someone from writing any file that contains what looks like a credential (too many false positives). But you do want to log a warning when a pattern is detected, so you can review and understand why credentials were being written.
Real-World Incident Scenarios
Here are actual scenarios File Guard would have prevented:
Scenario 1: The Accidental Commit
Developer: "Claude, help me debug why the API isn't connecting"
Claude: "Let me create a test file with your credentials to debug"
[Writes credentials.json to root]
Developer: [Runs git add . without thinking]
[Credentials pushed to GitHub in 30 seconds]
WITH FILE GUARD:
[Write attempt to credentials.json blocked]
Claude: "Cannot write to credentials.json—file is protected"
Developer: [Safe, credentials never written]
Scenario 2: The Backup File Trap
Developer: "Claude, copy my .env to .env.backup so I can test changes"
Claude: [Copies .env to .env.backup]
[Developer commits both files]
[Backup left behind, credentials exposed if repo goes public]
WITH FILE GUARD:
[Write attempt to .env.backup blocked]
Claude: "Cannot create .env.backup—pattern matches .env family"
Developer: [Understands the risk, uses secrets manager instead]
Scenario 3: The Documentation Leak
Developer: "Add example code to our README"
Claude: "Here's an example with API keys for testing:"
[Writes to README with test credentials]
WITH FILE GUARD:
[Detects patterns in content being written]
Claude: "This content contains patterns matching AWS keys—log warning"
Developer: [Removes example API keys, uses placeholders]
Performance Profiling
Understanding hook performance helps you optimize configuration:
// benchmark-file-guard.mjs
async function benchmark() {
const guard = new FileGuardHook(FileGuardConfig);
const iterations = 10000;
console.log(`Running ${iterations} iterations...\n`);
// Test 1: Benign file writes (normal case)
const benignStart = performance.now();
for (let i = 0; i < iterations; i++) {
await guard.processTool({
name: "Write",
params: { file_path: `src/file${i}.js`, content: "code" },
});
}
const benignTime = performance.now() - benignStart;
// Test 2: Blocked writes (.env attempts)
const blockedStart = performance.now();
for (let i = 0; i < iterations; i++) {
await guard.processTool({
name: "Write",
params: { file_path: ".env", content: "secret" },
});
}
const blockedTime = performance.now() - blockedStart;
// Results
console.log("Performance Results:");
console.log(` Benign writes: ${(benignTime / iterations).toFixed(3)}ms/op`);
console.log(
` Blocked writes: ${(blockedTime / iterations).toFixed(3)}ms/op`,
);
console.log(
` Total overhead: ${((benignTime + blockedTime) / 1000).toFixed(2)}s`,
);
console.log(
` Acceptable? ${benignTime / iterations < 1 ? "✓ YES" : "❌ NO"}`,
);
}
benchmark().catch(console.error);
Compliance: Auditing for Security Standards
File Guard logging supports compliance requirements:
// compliance-report.mjs - Generate audit trail for compliance
function generateComplianceReport(logFile) {
const lines = fs
.readFileSync(logFile, "utf-8")
.split("\n")
.filter((line) => line.trim());
const violations = lines.map((line) => JSON.parse(line));
const report = {
generatedAt: new Date().toISOString(),
period: {
start: violations[0]?.timestamp,
end: violations[violations.length - 1]?.timestamp,
},
summary: {
totalAttempts: violations.length,
blockedByType: {},
},
topOffenders: [],
};
// Categorize violations
for (const v of violations) {
const type = v.filePath?.match(/\.(env|json|pem|key)$/i)?.[1] || "other";
report.summary.blockedByType[type] =
(report.summary.blockedByType[type] || 0) + 1;
}
// Find most frequently attempted files
const fileAttempts = {};
for (const v of violations) {
fileAttempts[v.filePath] = (fileAttempts[v.filePath] || 0) + 1;
}
report.topOffenders = Object.entries(fileAttempts)
.sort(([, a], [, b]) => b - a)
.slice(0, 5)
.map(([file, attempts]) => ({ file, attempts }));
return report;
}
// Export as JSON for your security team
const report = generateComplianceReport("./file-guard-violations.log");
console.log(JSON.stringify(report, null, 2));
Organizational Implementation and Change Management
Deploying File Guard across an organization requires more than just installing the code. It requires buy-in from development teams and clear communication about why the restrictions exist.
We recommend a phased rollout. Start with a single team that’s particularly security-conscious or has had past incidents. Run it in warning mode initially—log violations but don’t block them. This gives you data about how often the patterns match legitimate operations. You’ll see edge cases you didn’t anticipate. Adjust patterns based on real usage before enforcing blocks.
The change management aspect matters because developers might initially see File Guard as friction—”why can’t I do what I was doing before?” The answer has to be communicated clearly. You’re not trying to prevent them from managing credentials. You’re preventing the category of errors where they accidentally expose credentials through automation. The hook forces intentionality: if you’re going to modify a secrets file, do it manually with your eyes open, not through Claude Code.
Over time, this intentionality becomes the value. Developers appreciate that the system has their back. They know that even if Claude Code tries to do something dangerous, there’s a safety net. That confidence is worth the minor friction of manual credential updates.
The Layered Defense Philosophy
File Guard is one layer in a defense-in-depth approach to credential protection. It’s not a complete solution by itself. It works best when combined with:
- Git hooks: Pre-commit checks that prevent secrets from being added to version control even if they somehow get into files
- Secrets manager integration: Using AWS Secrets Manager, HashiCorp Vault, or similar for credential storage rather than files
- Environment variable isolation: Keeping secrets in environment variables rather than files when possible
- Code review discipline: Reviewers checking for hardcoded credentials or suspicious imports
- Automated secret scanning: Tools like TruffleHog or GitGuardian that scan commits for exposed secrets
File Guard specifically addresses the Claude Code vector—preventing an AI assistant from accidentally committing secrets. But you need the other layers too. A comprehensive security posture means multiple overlapping controls, so if one fails, others still protect you.
Future Enhancements and Advanced Usage
As your organization matures its use of Claude Code, you might want to extend File Guard:
Integration with approval systems: For legitimate secrets modifications, integrate with Jira or GitHub to require approval tickets. The hook checks if there’s a valid approval ticket before allowing modification.
Secrets rotation tracking: Log all access to secrets files and correlate with credential rotation schedules. If a developer is accessing production secrets more frequently than normal, that’s a signal to investigate.
Machine learning for anomaly detection: Track patterns of file access and flag unusual behavior. A developer suddenly trying to read .env.prod when they normally only access .env.local is suspicious.
Integration with credential managers: If you use Vault or similar, File Guard could automatically request temporary credentials instead of allowing access to stored secrets.
These advanced features take File Guard from a protective barrier to an intelligent security control that learns and adapts.
The Cultural Impact of Security Controls
Here’s something we don’t talk about enough: security controls shape culture. When developers know that certain operations are blocked, they adjust their mental model of what’s possible. They start thinking about “how do I accomplish this safely” instead of “how do I accomplish this quickly.”
This is actually good. Security becomes part of how engineers think. A developer who’s used to file guard blocking suspicious writes develops more security awareness. They make better decisions in all their code, not just the parts that touch secrets.
The teams that have implemented File Guard report an interesting side effect: more conversations about credentials and security. Engineers ask each other “why did File Guard block this?” and end up learning about credential management practices. The tool becomes a teaching tool, not just a blocker.
Real-World Maintenance and Troubleshooting
Once File Guard is deployed, you need to maintain it:
Reviewing the violation log regularly: Is File Guard catching legitimate operations? If so, adjust the patterns. Is it catching dangerous operations? Good—that’s working. Is it catching neither? Maybe your patterns are too permissive.
Updating patterns as your stack evolves: If you add a new cloud provider, add patterns for its credentials. If you change from environment variables to a configuration management system, update patterns.
Testing pattern changes before rollout: Before you deploy pattern changes to production, test them on historical data. Apply the new patterns to your violation log and see what would have been caught or missed. This prevents unintended side effects.
Training new team members: When developers join, they need to understand why File Guard exists and what to do when it blocks them. This is often overlooked but critical for adoption.
The Economics of Prevention vs. Detection
File Guard is a prevention control—it stops bad things from happening. This is more valuable than detection controls that alert you after something bad has happened.
The economics are clear. If File Guard costs you 30 seconds of inconvenience per occurrence when you legitimately need to update credentials, but saves you from a breach that costs six figures in incident response, that’s an excellent tradeoff.
But the value isn’t just financial. It’s psychological. You sleep better knowing that your credentials are protected by a control that’s always vigilant. You have confidence in your security posture. That confidence has real value in organizational health and team morale.
-iNet
Communication is crucial. Developers need to understand that File Guard isn’t restricting them arbitrarily. It’s protecting them from mistakes that are genuinely dangerous. Frame it positively: “This prevents accidental credential leaks” rather than “this blocks risky operations.”
Provide clear documentation about how to work around legitimate needs. If a developer genuinely needs to work with sensitive files, they should have a documented process. Maybe they check out a specific branch that has File Guard disabled, or they use a whitelist exception that requires approval. The point is there’s a path that doesn’t involve frustration and workarounds.
Consider integration with your onboarding process. New developers should be introduced to File Guard as part of security training. They should understand what it blocks and why before they encounter it.
Metrics and Monitoring
In production, File Guard generates valuable data about security posture. Track these metrics:
- Number of violations per day/week (trending up or down?)
- Most frequently blocked file types (are they what you expected?)
- Exception requests (which allowlist additions are being requested?)
- False positives (legitimate files that triggered the pattern?)
This data tells stories. A sudden spike in violations might indicate a confused developer or an automated process that doesn’t know about File Guard. A pattern of violations on the same files might indicate that developers are regularly trying to access something that needs to be legitimized or explicitly forbidden.
Use this data to improve the system iteratively. If you see a particular type of file repeatedly triggering false positives, refine the pattern. If specific developers consistently request the same exception, create an allowlist entry for that case.
Integration with CI/CD and Deployment
As you mature your use of File Guard, integrate it deeper into your deployment pipeline. Your CI/CD system can check that no secrets made it into artifacts. When building Docker images, scan the filesystem to ensure no sensitive files were accidentally included.
Some organizations take this further and run File Guard on production systems to prevent accidental secret creation during runtime. This prevents scenarios where debug logging or troubleshooting commands create unintended files.
The philosophy scales: prevent secrets at the point of creation, detect them if prevention fails, and monitor to ensure the system is working as intended.
Wrapping Up: Your File Guard Is Live
You now have a production-ready File Guard Hook that:
- Blocks writes to
.env, credentials, and private keys - Detects AWS and other token patterns
- Supports allowlist exceptions for legitimate cases
- Logs violations for auditing
- Works identically on Windows, macOS, and Linux
- Runs in under 5 ms per operation
- Handles Windows path normalization correctly
- Includes comprehensive testing for edge cases
- Coordinates with other hooks for defense-in-depth
- Generates compliance reports for security teams
- Scales across your organization with clear metrics
- Integrates into your full deployment pipeline
The hook sits silently between Claude and your filesystem, catching risky operations before they happen. It’s defense-in-depth without being paranoid. It’s security that enables development rather than blocking it.
Your sensitive files are protected. Your git history stays clean. Your secrets stay secret. Your team moves faster because they’re not dealing with credential leaks and incident response.
Deploy with confidence. Then monitor, improve, and iterate. Security is a process, not a destination.
Long-Term Maintenance and Evolution
File Guard isn’t a “set it and forget it” tool. As your project evolves, your threat surface evolves. New credential types emerge. Your directory structure changes. You might start using new cloud providers or services with their own credential formats.
Establish a practice of reviewing your File Guard configuration quarterly. Look at the violation logs. Are there patterns of files being blocked that should be allowed? Are there new file types you’re creating that should be protected? Are there new credential formats you should be detecting?
Make updates to your configuration as you discover needs. This is where logging becomes valuable—it provides data about what’s actually happening in your development workflow, not just what you theorized when you set up the system.
Building Security Culture
Ultimately, File Guard is a tool for building security culture in your organization. It’s a constant, visible reminder that security matters. Every time a developer encounters a blocked operation, they’re learning about what’s sensitive and why it’s protected. Every time the system catches a potential leak, it reinforces that the protection is necessary and effective.
The best organizations pair File Guard with education. When someone tries to read their .env file through Claude, the blocked operation is an opportunity to remind them why that’s dangerous. When you need to add an allowlist exception, document it clearly so future developers understand the rationale.
Security isn’t something you achieve once. It’s something you build and maintain continuously. File Guard is one of the tools that makes this continuous security practice visible and actionable.
Expanding Beyond Claude Code
File Guard concepts extend beyond Claude Code interactions. You can implement similar hooks in your CI/CD pipeline, your git pre-commit hooks, your IDE plugins, and your container build processes. A defense-in-depth approach uses multiple layers to prevent secrets from leaking at any stage.
Some organizations implement similar checks in their local development environment to catch problems before they even reach git. Others add similar logic to their deployment pipeline to catch secrets that somehow made it into version control.
The key insight is that preventing secrets at the point of creation (File Guard’s job) is more efficient than catching them later. One hook that prevents the write saves the overhead of detecting, alerting, and remediating after the fact.
The Investment and ROI
Implementing File Guard properly requires some upfront investment. You need to understand your threat surface, craft appropriate patterns, test against your actual project structure, and integrate it into your workflows. This might take a day or two of focused work.
The return on investment becomes apparent almost immediately. You prevent the first accidental secret write and you’ve already recouped the effort. Over months and years, you prevent incidents that would cost far more in time and resources.
But the real value isn’t measured in prevented incidents (though those are valuable). The real value is peace of mind. Knowing that secrets can’t accidentally leak through AI-assisted development is worth the investment on its own. Your team can focus on building features instead of worrying about security. Your security team can focus on more sophisticated threats instead of managing the basics.
Your secrets stay secret.
-iNet