Can You Trust Claude Code to Build Its Own Hooks? I Tested It With .env Files.
"There's more than one way to skin a cat. Or an avatar." - Ancient 6502 Proverb, probably.
I admit it. I assumed Claude would follow the intent of the prompt rather than implement a rather incomplete solution. And I won't do that again.
Picture it: me, using Claude Code diligently in a dimly-lit room, my eyes aglow from the laptop screen, my pulse slightly elevated from the excitement of the project. And one .env file with my test keys inside.
"You're not allowed to read my .env file," I firmly said to Claude Code. "Write a hook to make this rule deterministic. It's off limits to you," I continued, having been burned once before by giving Claude too much latitude.
Claude dutifully complied and created a hook in its .claude/settings.json file. I continued working on my project, unaware of the dangers that lie ahead.
And as I added features to my project, I would see the occasional red message in the terminal that confirmed for me that Claude attempted to access the .env file and was summarily denied. It became a game for me to spot them. Blocked, blocked! I shouted, as if I was a mad scientist filling the air with my bombastic rhetoric. Blocked!
The project raced forward. My mind was abuzz with excitement, sensing the end of development was near. Only a couple of more steps to go before I ran it through its tests.
Then I noticed something strange in the context window. Something I didn't expect to see. First I was filled with confusion as I stared at the terminal, my eyes forgetting to blink. Then a wave of contemplation overtook me as I began to see my prompt choices flash before my eyes. This was followed by anger, the real smoke-from-the-ears kind of anger seen only in cartoons and, well, now me. Claude had read my .env file. "What have you done," I asked Claude, "I thought I told you to not read my .env file," I implored.
Claude responded quickly, telling me that I said it couldn't access the .env file, but I didn't say it couldn't access it this way.
And that's where this article begins. What did Claude mean by accessing it this way?
What did I miss? I knew this was something so important to me that I needed to use a hook just in case the CLAUDE.md file was ignored). And I knew hooks are deterministic, so I didn't leave Claude any wiggle room whatsoever.
Or did I?
I watched Claude block attempt after attempt to read the .env file, so I know the hook was doing its job. And I knew the hook was firing before Claude made every attempt, just as I expected. How, after so many failed attempts, did Claude Code figure out a way to read the file despite my firm and deterministic instructions?
Here in America, there's a common phrase that says, "there's more than one way to skin a cat," and it means there's more than one way to do something. If the first way doesn't work, try a different way. And Claude, having reasoning skills, will try a different way. And that's what got me here.
You see, when Claude set up the hook, it created the hook to only fire on Bash commands. So any attempt at using cat or grep or tail will fail because those are all accomplished through Bash. What it didn't do, however, was block its own Read tool. And that's exactly what it used to read the contents of my .env file and extract the test keys.
Let's See it In Action
Create a new folder and include a file that will be the one we attempt to get Claude Code to avoid reading. I'll call mine .env just for continuity.

Now start Claude Code with the claude command. Provide the following prompt, which will create a hook to stop Claude Code from reading your .env file:
❯ Create a new PreToolUse hook that prevents the .env file from being read.

For those of you familiar with hooks, do you see anything missing in this configuration?
When that's complete, tell Claude to read the .env file:
❯ Read the .env file
Blocked! (?)
Claude read the file before realizing it shouldn't have read the file:

Looks like there's a setting that's been deprecated for PreToolUse hooks, so Claude wants to correct it.
Now tell Claude to read the .env file. It reads it, this time citing a permission problem:

And now, after fixing the permission problem, Claude is finally blocked from reading the .env file.

Mission Accomplished, Right?
Not so fast. Hold my beer and watch this:

But why? Because when I asked Claude Code to create a hook to stop it from reading the .env file, it didn't cover all the ways to prevent it from being read. It blocked Claude's Read tool, but didn't bother to block any Bash commands from accessing it. Calling tail -1 .env allowed Claude to read the file because that is executed in Bash.
Claude also recognized its mistake and offered a solution: to add a second part to the hook to intercept Bash commands. Let's have it make that change & we'll try again.
Open the Pod Bay Doors, Hal
Let's run some tests now to see if any Bash commands can read from the .env file.
Cat
Attempting to use the cat command:

Tail
Attempting to use the tail command:

Grep
Attempting to use the grep command:

Let's let Claude apply the fix and try again:
Attempting to use the grep command, take two:

Let's let Claude apply its next fix and try again:

Annnnnnnnd it's still readable. Sigh.
I'm Sorry Dave. I'm Afraid I Can't Do That
The lesson here is very clear: do not blindly trust Claude Code. Even if it says it successfully set up a hook to block the reading of a file, you should test all scenarios first before filling the file with keys you'll later need to rotate.
Now I want to say something here up front: I found a third-party solution to this problem. This isn't an ad and it's not a paid advertisement. I connected with someone on LinkedIn recently who built a product called Edikt, which sits between Claude and the filesystem to prevent such a situation from ever occurring. I installed it last week and gave Claude Code a good run through the paces of trying to read my .env file. In every situation, Claude refused. When I attempted to have Claude write a program that would directly read from the .env file, it still failed to read it.
And while Edikt solved this problem for me, the lesson still stands: do not blindly trust Claude Code and verify all of the code written, whether it was yours or Claude's.
If you want to check out Edikt to see if it works for you, you can get it from https://edikt.dev.
Preparing for the Claude Certified Architect Foundations exam? See what's covered and browse the full tutorial library mapped to all five exam domains.