<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Erik Nascimento</title>
    <description>The latest articles on DEV Community by Erik Nascimento (@erik_nascimento_f32c73bc3).</description>
    <link>https://dev.to/erik_nascimento_f32c73bc3</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4235108%2Fac8203ec-cb76-4c4e-a1e6-814e90414117.png</url>
      <title>DEV Community: Erik Nascimento</title>
      <link>https://dev.to/erik_nascimento_f32c73bc3</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/erik_nascimento_f32c73bc3"/>
    <language>en</language>
    <item>
      <title>How to keep Claude Code and Cursor from reading your .env</title>
      <dc:creator>Erik Nascimento</dc:creator>
      <pubDate>Mon, 09 Nov 2026 12:16:40 +0000</pubDate>
      <link>https://dev.to/erik_nascimento_f32c73bc3/how-to-keep-claude-code-and-cursor-from-reading-your-env-480b</link>
      <guid>https://dev.to/erik_nascimento_f32c73bc3/how-to-keep-claude-code-and-cursor-from-reading-your-env-480b</guid>
      <description>&lt;p&gt;Ask a coding agent why your Stripe checkout fails and there's a good chance its first move is to open &lt;code&gt;.env&lt;/code&gt;. It's the reasonable thing to do: the config is there. But now your live key is in the conversation, in the agent's logs, and on someone else's servers.&lt;/p&gt;

&lt;p&gt;Here are the options I found for stopping that, from the simplest to the one I ended up building.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Tell the agent not to read it
&lt;/h2&gt;

&lt;p&gt;Claude Code has permission rules in &lt;code&gt;.claude/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deny"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Read(./.env)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Read(./.env.*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Read(./secrets/**)"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cursor has &lt;code&gt;.cursorignore&lt;/code&gt;, which keeps files out of indexing and, on a best-effort basis, out of the agent's reach.&lt;/p&gt;

&lt;p&gt;This is worth doing today, and it costs nothing. The gaps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rules match paths, not contents.&lt;/strong&gt; A &lt;code&gt;config.local.js&lt;/code&gt; with a pasted key isn't covered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The shell is a side door.&lt;/strong&gt; &lt;code&gt;cat .env&lt;/code&gt;, &lt;code&gt;grep KEY .env&lt;/code&gt;, &lt;code&gt;python -c "print(open('.env').read())"&lt;/code&gt; or &lt;code&gt;env&lt;/code&gt; can still show the values unless you deny those too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You have to remember it in every project&lt;/strong&gt;, and for every agent you use.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Don't keep the secrets in files at all
&lt;/h2&gt;

&lt;p&gt;If there's no &lt;code&gt;.env&lt;/code&gt; on disk, there's nothing to read. Tools like 1Password's &lt;code&gt;op run&lt;/code&gt;, Doppler or Infisical inject variables into the process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;op run &lt;span class="nt"&gt;--env-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.env.tpl &lt;span class="nt"&gt;--&lt;/span&gt; npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app gets the values in its environment; the repository only has references. This also helps with the classic "committed &lt;code&gt;.env&lt;/code&gt;" accident.&lt;/p&gt;

&lt;p&gt;The catch with agents: if the agent can run &lt;code&gt;op read&lt;/code&gt; or &lt;code&gt;printenv&lt;/code&gt; itself, it can still pull the value into the chat. Injection is necessary but not enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Check every request before the agent runs it
&lt;/h2&gt;

&lt;p&gt;Both Claude Code and Cursor now have hooks that run &lt;em&gt;before&lt;/em&gt; a tool call and can allow it, deny it or ask you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code: a &lt;code&gt;PreToolUse&lt;/code&gt; hook gets &lt;code&gt;tool_name&lt;/code&gt; and &lt;code&gt;tool_input&lt;/code&gt; (the file for &lt;code&gt;Read&lt;/code&gt;, the command for &lt;code&gt;Bash&lt;/code&gt;) and answers with &lt;code&gt;permissionDecision: "deny"&lt;/code&gt; and a reason the model sees.&lt;/li&gt;
&lt;li&gt;Cursor: &lt;code&gt;beforeReadFile&lt;/code&gt; and &lt;code&gt;beforeShellExecution&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A hook can do what static rules can't: look at the command (&lt;code&gt;cat&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;curl -d @.env&lt;/code&gt;, &lt;code&gt;sh -c "…"&lt;/code&gt;), and look at the &lt;em&gt;contents&lt;/em&gt; of the file the agent wants to read. The reason it returns matters too: if it says "use &lt;code&gt;npm run dev&lt;/code&gt; with the variables injected instead", the agent usually adapts instead of retrying.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;I wanted all three at once, so I built Safe8bit, a secrets vault for the Mac. The part relevant here is the Shield, a hook for Claude Code and Cursor that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;denies reading &lt;code&gt;.env&lt;/code&gt; files, private keys and credential files (&lt;code&gt;.netrc&lt;/code&gt;, &lt;code&gt;~/.aws/credentials&lt;/code&gt;, SSH keys…), whether through &lt;code&gt;Read&lt;/code&gt; or through &lt;code&gt;cat&lt;/code&gt;/&lt;code&gt;grep&lt;/code&gt;/&lt;code&gt;python&lt;/code&gt;/&lt;code&gt;curl @file&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;denies reading any file whose contents match a value stored in the vault, and writing those values into code;&lt;/li&gt;
&lt;li&gt;asks you before &lt;code&gt;printenv&lt;/code&gt;, &lt;code&gt;env&lt;/code&gt;, &lt;code&gt;export -p&lt;/code&gt; or pulling a value out of the vault, including when they're wrapped in &lt;code&gt;sh -c&lt;/code&gt; or &lt;code&gt;safe8bit run --&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;tells the agent to run the app with &lt;code&gt;safe8bit run -- npm run dev&lt;/code&gt;, which injects the variables after Touch ID. Touch ID asks &lt;em&gt;you&lt;/em&gt;, so the agent can start the app but never sees the keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's what that looks like in Claude Code when the agent reaches for the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;● Read(.env)
  └ Safe8bit: .env (.env file) isn't read by agents. Use environment variables
    with safe8bit run (e.g. safe8bit run -- npm run dev) instead of reading the value.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The vault itself is one file encrypted with AES-256-GCM, with the key derived with Argon2id and protected by the Secure Enclave. No account, no server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Its limits
&lt;/h2&gt;

&lt;p&gt;A hook is a guardrail against accidents, not a sandbox. Code the agent runs inside &lt;code&gt;safe8bit run&lt;/code&gt; can read its own environment (&lt;code&gt;node -e 'console.log(process.env)'&lt;/code&gt;), and a determined agent could write a script that reads a file in a way no rule anticipates. What it fixes is the common case: an agent that reads your config because that's the obvious next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Whatever tool you use, do step 1 today. If you're on a Mac and want the rest, Safe8bit's free tier includes the vault and the Shield in log-only mode, so you can see what your agent would have read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; safe8bit-app/tap/safe8bit
safe8bit shield &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More, including a 30-second demo: &lt;a href="https://safe8bit.com/en/" rel="noopener noreferrer"&gt;https://safe8bit.com/en/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How do you keep secrets away from your agents? I'd like to know what I'm missing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I'm the developer of Safe8bit. This article was drafted with AI assistance and reviewed by me.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>macos</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
