Skip to main content

Can You Trust Claude Code to Build Its Own Hooks? I Tested It With .env Files.

Michael 5 min read
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.

Creating a .env file with a secret key using Nano. The secret key value is ABC123

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.

Hook configuration Claude Code created

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:

Claude 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:

Claude is still reading the .env file

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

Claude is prevented from reading the .env file

Mission Accomplished, Right?

Not so fast. Hold my beer and watch this:

Claude Code can still read the the .env file

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:

Read denied

Tail

Attempting to use the tail command:

Read denied

Grep

Attempting to use the grep command:

The file was able to be read by grep because I didn't specify the name of the file in the command

Let's let Claude apply the fix and try again:

Attempting to use the grep command, take two:

The file was again able to be read by grep

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

My environment doesn't support OS-like sandboxing, so we're still unable to block all reads of .env

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.

Exam Prep

Preparing for the Claude Certified Architect Foundations exam? See what's covered and browse the full tutorial library mapped to all five exam domains.

Related Posts

How to Build Programmatic Prerequisites in Claude Code

Why Claude Code Needs Multiple Passes to Find All Your Bugs

← Back to all posts