<?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: JasperNoBoxDev</title>
    <description>The latest articles on DEV Community by JasperNoBoxDev (@jaspernoboxdev).</description>
    <link>https://dev.to/jaspernoboxdev</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3838576%2F9b5c7bf5-af54-40ce-9ffd-299316b12e8f.jpg</url>
      <title>DEV Community: JasperNoBoxDev</title>
      <link>https://dev.to/jaspernoboxdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jaspernoboxdev"/>
    <language>en</language>
    <item>
      <title>Stop Putting API Keys in .env Files — Use Your OS Keychain Instead</title>
      <dc:creator>JasperNoBoxDev</dc:creator>
      <pubDate>Thu, 26 Mar 2026 22:35:08 +0000</pubDate>
      <link>https://dev.to/jaspernoboxdev/stop-putting-api-keys-in-env-files-use-your-os-keychain-instead-50bc</link>
      <guid>https://dev.to/jaspernoboxdev/stop-putting-api-keys-in-env-files-use-your-os-keychain-instead-50bc</guid>
      <description>&lt;p&gt;Last year, a developer at a YC startup pushed a &lt;code&gt;.env&lt;/code&gt; file to a public GitHub repo. It contained their Stripe live key, an OpenAI API key, and a production database URL. Automated scanners picked it up in under 30 seconds. By the time GitHub's secret scanning revoked the tokens it recognized, someone had already racked up $14,000 in OpenAI API charges.&lt;/p&gt;

&lt;p&gt;This is not rare. GitGuardian's 2024 State of Secrets Sprawl report found &lt;strong&gt;12.8 million new secrets&lt;/strong&gt; exposed in public GitHub repositories in a single year. A 28% increase from the year before. And those are just the public repos — private ones are worse, because nobody is scanning them.&lt;/p&gt;

&lt;p&gt;12.8M&lt;br&gt;
    secrets exposed in public repos (2024)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;28%
increase year over year
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Your &lt;code&gt;.env&lt;/code&gt; file is a liability. Here is why, and what to do about it.&lt;/p&gt;
&lt;h2&gt;
  
  
  The dotenv pattern was never a security mechanism
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;dotenv&lt;/code&gt; pattern came out of the Ruby community in 2012. It solved a real problem: developers were hardcoding database passwords and API keys directly into source files. Moving them to a separate file that you &lt;code&gt;.gitignore&lt;/code&gt; was a genuine improvement.&lt;/p&gt;

&lt;p&gt;But here is what a typical &lt;code&gt;.env&lt;/code&gt; looks like on a developer's machine right now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;sk-proj-abc123...&lt;/span&gt;
&lt;span class="py"&gt;STRIPE_SECRET_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;sk_live_...&lt;/span&gt;
&lt;span class="py"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;postgresql://admin:password@prod-db.example.com:5432/main&lt;/span&gt;
&lt;span class="py"&gt;CLOUDFLARE_API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;v4x...&lt;/span&gt;
&lt;span class="py"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;wJalrXUtnFEMI...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five production credentials. Plaintext. No authentication to read them. No encryption at rest. No audit trail. Any process running as your user — a rogue npm postinstall script, a compromised VS Code extension, a careless &lt;code&gt;docker build&lt;/code&gt; — can read that file without you knowing.&lt;/p&gt;

&lt;p&gt;That is not security. That is a Post-it note on your monitor with the vault combination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why .env file security breaks down at scale
&lt;/h2&gt;

&lt;p&gt;A single &lt;code&gt;.env&lt;/code&gt; file is manageable. Nobody has a single &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;We counted ours. &lt;strong&gt;47&lt;/strong&gt;. Some in active projects, some in archived repos, some in Docker contexts that got copied into images. The same Cloudflare API token appeared in six different files. When we rotated it, we updated three and missed the other three for weeks. (&lt;a href="https://noboxdev.com/blog/why-i-deleted-every-env-file" rel="noopener noreferrer"&gt;Full story of that audit here.&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;There is no expiry tracking. No way to know which secrets are still valid. No audit trail of which process read which key. When a breach happens, incident response starts with "wait, where do we even have this key?" and ends with &lt;code&gt;grep -r&lt;/code&gt; across the entire home directory.&lt;/p&gt;

&lt;p&gt;Warning&lt;br&gt;
  GitHub's push protection auto-revokes tokens from GitHub, AWS, Google Cloud, and a handful of other providers. But most services lack that integration. Your Stripe key, your Postmark token, your custom API credentials — if those hit a public repo, nobody revokes them automatically. You find out when the bill arrives or the data is gone.&lt;/p&gt;
&lt;h2&gt;
  
  
  AI agents turned .env files into a critical vulnerability
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;.env&lt;/code&gt; pattern assumed that only your application would read the file. That assumption broke in 2025.&lt;/p&gt;

&lt;p&gt;AI coding agents — Claude Code, Cursor, GitHub Copilot, Codex — routinely read project files to build context. Your &lt;code&gt;.env&lt;/code&gt; is a project file. When an agent reads it, every secret in that file enters the model's context window. From there, values can appear in generated code, debug output, error messages, or conversation logs.&lt;/p&gt;

&lt;p&gt;The agent is not being malicious. It is trying to help. "Here is the error — your DATABASE_URL returns connection refused" — and now your production database hostname and credentials are in the chat history. Shared with your team. Logged by the provider. Potentially used as training data.&lt;/p&gt;

&lt;p&gt;We wrote more about specific leak vectors in &lt;a href="https://noboxdev.com/blog/five-ways-ai-agents-leak-secrets" rel="noopener noreferrer"&gt;6 Ways AI Agents Leak Your Secrets&lt;/a&gt;. The short version: if a secret is in a file that an agent can read, assume it will be read.&lt;/p&gt;
&lt;h2&gt;
  
  
  The env file alternative: macOS Keychain with Touch ID
&lt;/h2&gt;

&lt;p&gt;The macOS Keychain is an encrypted credential store that has been on every Mac for over 20 years. It is backed by the Secure Enclave, protected by &lt;a href="https://noboxdev.com/blog/touch-id-api-keys" rel="noopener noreferrer"&gt;Touch ID&lt;/a&gt;, and used by Safari, SSH, and every Apple-signed application. Not a startup's custom vault — infrastructure that Apple maintains and audits.&lt;/p&gt;

&lt;p&gt;NoxKey is a thin layer on top of the Keychain, purpose-built for developer secrets:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before: plaintext .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Plaintext file anyone can &lt;span class="nb"&gt;read&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; .env
&lt;span class="go"&gt;STRIPE_SECRET_KEY=sk_live_51Hx...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;After: Keychain + Touch ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Encrypted &lt;span class="k"&gt;in &lt;/span&gt;Keychain, Touch ID on every access
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;noxkey &lt;span class="nb"&gt;set &lt;/span&gt;myorg/project/STRIPE_KEY &lt;span class="nt"&gt;--clipboard&lt;/span&gt;
&lt;span class="go"&gt;✓ Stored myorg/project/STRIPE_KEY

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;noxkey get myorg/project/STRIPE_KEY&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;→ Touch ID prompt → secret loaded into shell
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;→ raw value never written to disk
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$STRIPE_KEY&lt;/span&gt;
&lt;span class="go"&gt;sk_live_51Hx...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;One secret, one location, accessible from any project directory. No files on disk. Touch ID every time. When an AI agent calls &lt;code&gt;noxkey get&lt;/code&gt;, it detects the agent's &lt;a href="https://noboxdev.com/blog/process-tree-agent-detection" rel="noopener noreferrer"&gt;process tree&lt;/a&gt; and returns an encrypted handoff instead of the raw value — the secret reaches the environment but never enters the conversation context.&lt;/p&gt;

&lt;p&gt;Agent calls noxkey get → Process tree detected → AES-256-CBC encryption → Secret in env, never in context&lt;/p&gt;
&lt;h2&gt;
  
  
  Migrate from .env to Keychain in minutes
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Import your existing .env file
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;noxkey import myorg/project .env
&lt;span class="go"&gt;✓ Imported 5 secrets

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Verify everything landed
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;noxkey &lt;span class="nb"&gt;ls &lt;/span&gt;myorg/project/
&lt;span class="go"&gt;myorg/project/STRIPE_SECRET_KEY
myorg/project/OPENAI_API_KEY
myorg/project/DATABASE_URL
myorg/project/CLOUDFLARE_API_TOKEN
myorg/project/AWS_SECRET_ACCESS_KEY

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Peek at a value to confirm &lt;span class="o"&gt;(&lt;/span&gt;shows first 8 chars&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;noxkey peek myorg/project/STRIPE_SECRET_KEY
&lt;span class="go"&gt;sk_live_...

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Now delete the liability
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Do that for every project. We did &lt;a href="https://noboxdev.com/blog/why-i-deleted-every-env-file" rel="noopener noreferrer"&gt;47 in one afternoon&lt;/a&gt;. The &lt;code&gt;find&lt;/code&gt; command that started it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;find ~/dev &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;".env"&lt;/span&gt; &lt;span class="nt"&gt;-not&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; &lt;span class="s2"&gt;"*/node_modules/*"&lt;/span&gt; &lt;span class="nt"&gt;-not&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; &lt;span class="s2"&gt;"*/.git/*"&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;span class="go"&gt;47
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What about CI/CD?
&lt;/h2&gt;

&lt;p&gt;This approach is for local development — your machine, your secrets, your Touch ID. CI/CD systems have their own secrets management: GitHub Actions secrets, Cloudflare environment variables, AWS Parameter Store, Vault. Those are purpose-built for that environment and they work.&lt;/p&gt;

&lt;p&gt;The problem is the gap between "secrets managed in CI" and "secrets on your laptop." That gap is currently filled by plaintext files with no authentication, no encryption, and no access control. It should be filled by your operating system's credential store.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest tradeoffs of leaving dotenv behind
&lt;/h2&gt;

&lt;p&gt;This is macOS only. If your team is on Linux or Windows, NoxKey is not the answer for them (though the principle holds — use your OS credential store, not plaintext files).&lt;/p&gt;

&lt;p&gt;There is friction. Touch ID on every access means authenticating more than before. Session unlock (&lt;code&gt;noxkey unlock myorg/project&lt;/code&gt;) reduces this to one authentication per batch, but it is still more than the zero authentication that &lt;code&gt;.env&lt;/code&gt; offers. That friction is the point — it means something is actually guarding access.&lt;/p&gt;

&lt;p&gt;You will need to update your workflow. Instead of copying &lt;code&gt;.env.example&lt;/code&gt; and filling in values, you run &lt;code&gt;noxkey import&lt;/code&gt; once and use &lt;code&gt;eval&lt;/code&gt; commands. It took us about a day to stop reaching for &lt;code&gt;.env&lt;/code&gt; instinctively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your API keys deserve better than plaintext files
&lt;/h2&gt;

&lt;p&gt;12.8 million secrets exposed in public repos last year. AI agents reading every file in your project directory. Supply chain attacks targeting plaintext credentials. The &lt;code&gt;.env&lt;/code&gt; pattern was a good idea in 2012. It is a liability in 2026.&lt;/p&gt;

&lt;p&gt;Your operating system has an encrypted, hardware-backed credential store with biometric authentication. Use it.&lt;/p&gt;

&lt;p&gt;Key Takeaway&lt;br&gt;
  Your &lt;code&gt;.env&lt;/code&gt; file has no encryption, no authentication, and no access control. Every AI agent, every rogue script, and every accidental git push can read it. Move your secrets to the macOS Keychain — one location, &lt;a href="https://noboxdev.com/blog/touch-id-api-keys" rel="noopener noreferrer"&gt;Touch ID on every access&lt;/a&gt;, and AI agents never see the raw values. The migration takes minutes per project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;brew install no-box-dev/noxkey/noxkey
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Free. No account. No cloud. Your secrets stay on your machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Why are .env files insecure?&lt;/em&gt;&lt;/strong&gt;*&lt;br&gt;
  .env files store secrets as plaintext on disk with no encryption, no authentication, and no access control. Any process, script, or AI agent with file system access can read them. They frequently get committed to git — 12.8 million secrets were exposed on GitHub in 2024.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;What should I use instead of dotenv?&lt;/em&gt;&lt;/strong&gt;*&lt;br&gt;
  Use your operating system's credential store. On macOS, the Keychain provides hardware-encrypted storage with Touch ID authentication. &lt;a href="https://noxkey.ai" rel="noopener noreferrer"&gt;NoxKey&lt;/a&gt; wraps the Keychain in a developer-friendly CLI: &lt;code&gt;eval "$(noxkey get myorg/KEY)"&lt;/code&gt; replaces &lt;code&gt;process.env.KEY&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Can AI agents read .env files?&lt;/em&gt;&lt;/strong&gt;*&lt;br&gt;
  Yes. AI coding tools like Claude Code, Cursor, and Copilot have file system access and can read any .env file in your project directory. NoxKey prevents this by storing secrets in the Keychain and delivering them through encrypted handoff when an AI agent is detected.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;How do I migrate from .env to the macOS Keychain?&lt;/em&gt;&lt;/strong&gt;*&lt;br&gt;
  Install NoxKey (&lt;code&gt;brew install no-box-dev/noxkey/noxkey&lt;/code&gt;), then run &lt;code&gt;noxkey import myorg .env&lt;/code&gt; to move all secrets to the Keychain. Delete the .env file afterward. The migration takes about a minute per project.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;NoxKey is free and open source. &lt;code&gt;brew install no-box-dev/noxkey/noxkey&lt;/code&gt; — &lt;a href="https://github.com/No-Box-Dev/Noxkey" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://noxkey.ai" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How We Built Process-Tree Agent Detection</title>
      <dc:creator>JasperNoBoxDev</dc:creator>
      <pubDate>Thu, 26 Mar 2026 22:35:01 +0000</pubDate>
      <link>https://dev.to/jaspernoboxdev/how-we-built-process-tree-agent-detection-2pgg</link>
      <guid>https://dev.to/jaspernoboxdev/how-we-built-process-tree-agent-detection-2pgg</guid>
      <description>&lt;p&gt;How do you tell if a human or an AI agent is requesting a secret?&lt;/p&gt;

&lt;p&gt;This question sits at the center of NoxKey's security model. An AI agent needs your Stripe key to make API calls. A human needs it to paste somewhere. Both call &lt;code&gt;noxkey get&lt;/code&gt;. But the response should be fundamentally different — because what happens &lt;em&gt;after&lt;/em&gt; delivery depends entirely on who's asking.&lt;/p&gt;

&lt;p&gt;A human uses the value and moves on. An agent ingests it into a conversation context where it can be logged, echoed in debug output, included in generated code, or stored in a chat history on someone else's server. Same secret, wildly different risk profiles.&lt;/p&gt;

&lt;p&gt;We spent two weeks building the process tree detection system that powers NoxKey's agent access control. Here's exactly how it works, where it breaks, and why imperfect detection still beats no detection at all.&lt;/p&gt;

&lt;p&gt;CLI&lt;br&gt;
  noxkey get&lt;br&gt;
   Detection --&amp;gt;&lt;/p&gt;

&lt;p&gt;Agent Detection&lt;br&gt;
  Process Tree Walker&lt;br&gt;
  Dual Verification&lt;br&gt;
   Menu Bar --&amp;gt;&lt;/p&gt;

&lt;p&gt;Unix Socket&lt;/p&gt;

&lt;p&gt;Menu Bar App&lt;br&gt;
  Server + Touch ID&lt;br&gt;
   Keychain --&amp;gt;&lt;/p&gt;

&lt;p&gt;Keychain&lt;br&gt;
  Encrypted&lt;/p&gt;
&lt;h2&gt;
  
  
  Every process has a family tree
&lt;/h2&gt;

&lt;p&gt;Every process on macOS has a parent. Your shell was started by Terminal.app. Terminal.app was started by &lt;code&gt;launchd&lt;/code&gt;. When you type a command, your shell forks a child process to run it. This chain — child to parent to grandparent — is the process tree.&lt;/p&gt;

&lt;p&gt;When Claude Code runs a command, the chain looks like this:&lt;/p&gt;

&lt;p&gt;launchd PID 1    └─ claude ← Electron app (MATCH)      └─ node ← Claude Code runtime        └─ zsh ← spawned shell          └─ noxkey ← get org/proj/STRIPE_KEY&lt;/p&gt;

&lt;p&gt;When a human runs the same command from Terminal:&lt;/p&gt;

&lt;p&gt;launchd PID 1    └─ Terminal.app      └─ zsh ← login shell        └─ noxkey ← get org/proj/STRIPE_KEY&lt;/p&gt;

&lt;p&gt;The difference: one tree has a process named &lt;code&gt;claude&lt;/code&gt;. The other doesn't. That's the signal.&lt;/p&gt;
&lt;h2&gt;
  
  
  Walking the process tree in Swift
&lt;/h2&gt;

&lt;p&gt;NoxKey's server is a &lt;a href="https://noboxdev.com/blog/native-apps-are-an-advantage" rel="noopener noreferrer"&gt;native Swift menu bar app&lt;/a&gt;. The process tree walker uses macOS kernel APIs — specifically &lt;code&gt;proc_pidinfo&lt;/code&gt; and &lt;code&gt;sysctl&lt;/code&gt; with &lt;code&gt;KERN_PROC&lt;/code&gt; — to climb from any PID to &lt;code&gt;launchd&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The algorithm: start at the requesting process, get its parent PID, check the binary name, move up, repeat. Stop at PID 1 or on match.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;isAgentProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pid_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;currentPid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;maxDepth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;  &lt;span class="c1"&gt;// safety limit&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;currentPid&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="n"&gt;average&lt;/span&gt; &lt;span class="n"&gt;detection&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;


    &lt;span class="mf"&gt;1.6&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
    &lt;span class="n"&gt;worst&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt;


    &lt;span class="mi"&gt;20&lt;/span&gt;
    &lt;span class="n"&gt;ancestors&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;


&lt;span class="err"&gt;##&lt;/span&gt; &lt;span class="kt"&gt;The&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="n"&gt;signatures&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;

&lt;span class="kt"&gt;Detection&lt;/span&gt; &lt;span class="n"&gt;checks&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt; &lt;span class="n"&gt;ancestor&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;binary&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;against&lt;/span&gt; &lt;span class="n"&gt;known&lt;/span&gt; &lt;span class="kt"&gt;AI&lt;/span&gt; &lt;span class="n"&gt;coding&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="nv"&gt;signatures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;private let agentSignatures = [&lt;br&gt;
    "claude", "cursor", "codex",&lt;br&gt;
    "windsurf", "copilot", "cody",&lt;br&gt;
    "aider", "continue", "tabby"&lt;br&gt;
]&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Case-insensitive substring matching. If any ancestor's binary name contains any of these strings, the caller is classified as an agent.

Could an agent rename its binary to bypass detection? In theory, yes. In practice, agent binaries are code-signed, distributed through Homebrew or app stores, and installed to standard paths. Users don't rename them. And if an agent vendor deliberately tried to evade detection, the headline writes itself: "Cursor caught disguising itself to bypass credential controls."

Name-based detection works because the incentives align. Agent vendors *want* to be identified. Being detected means getting the encrypted handoff instead of being blocked entirely. The alternative — no detection, no access — is worse for everyone.

## Dual verification: never trust the client

The CLI performs process-tree detection client-side. But the CLI is a binary on the user's machine. A malicious script could bypass it entirely and talk to the Unix socket directly, claiming to be human.

So the server verifies independently.

How dual verification works
  When the CLI connects to NoxKey's Unix domain socket, the server resolves the peer's PID using the `LOCAL_PEERPID` socket option — a kernel-level credential, not something the client sends. The server then walks *that* process tree independently.

Both sides must agree. If the CLI says "human" but the server sees `claude` in the ancestry, the server's verdict wins. The more restrictive interpretation always takes precedence. A compromised CLI can't downgrade its own classification.

Same principle as server-side validation in web apps. The client can lie. The server checks anyway.

## The encrypted handoff

When detection confirms an agent caller, NoxKey doesn't refuse the secret. It changes *how* the secret is delivered. This is the critical design decision: agents need secrets to function. Blocking them entirely just pushes developers back to `.env` files. Instead, we make the secret available to the agent's process without exposing the raw value in its text context.

The handoff sequence:

1. Generate Key ← random AES-256-CBC key + IV &amp;amp;nbsp;&amp;amp;nbsp; └─ 2. Encrypt ← secret value encrypted with one-time key &amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp; └─ 3. Transmit ← payload + key + IV over Unix socket &amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp; └─ 4. Decrypt ← CLI decrypts via CommonCrypto &amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp; └─ 5. Write Script ← self-deleting temp script to /tmp (0600) &amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp; └─ 6. Output ← source '/tmp/noxkey_abc123.sh' to stdout &amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp; └─ 7. Cleanup ← file removed after 60s safety net

  - The server generates a random AES-256-CBC key and initialization vector
  - The secret value is encrypted with this one-time key
  - The encrypted payload, key, and IV return to the CLI over the Unix socket
  - The CLI decrypts using CommonCrypto (Apple's native crypto framework)
  - The CLI writes a self-deleting temp script to `/tmp` — containing `export KEY=value` followed by `rm -f "$0"`
  - The CLI outputs `source '/tmp/noxkey_abc123.sh'` to stdout
  - A background cleanup process removes the file after 60 seconds regardless

The agent runs `eval "$(noxkey get org/proj/STRIPE_KEY)"`. The eval sources the temp script, which exports the secret into the shell environment and deletes itself. The secret is now in `$STRIPE_KEY` — available to subprocesses — but the raw value never appeared in the agent's conversation context. It flowed through the OS, not through the chat.

The temp file exists on disk for milliseconds. Created with `0600` permissions (owner-only). The 60-second cleanup is a safety net for cases where the script isn't sourced.

## Defending against PID recycling attacks

PID Recycling Attack
  A legitimate process authenticates with Touch ID and gets a session. When it exits, macOS can recycle its PID. A new process inheriting that PID could hijack the authenticated session — accessing secrets without ever touching the fingerprint sensor. On a busy system with a 99999 PID space, recycling can happen within seconds.

NoxKey has session unlock: run `noxkey unlock org/proj`, authenticate with Touch ID once, and subsequent `get` calls under that prefix skip biometric auth for a configurable window. The session is bound to the PID that initiated it.

The attack scenario:

  - A legitimate process (PID 48201) calls `noxkey unlock org/proj` and authenticates with Touch ID
  - The session manager records: "PID 48201 has an active session for `org/proj/*`"
  - The legitimate process exits. PID 48201 is now free
  - An attacker spawns a new process. macOS assigns it PID 48201 — recycled
  - The attacker calls `noxkey get org/proj/DATABASE_URL` from PID 48201
  - The session manager sees the PID, finds an active session, skips Touch ID
  - The attacker gets the secret without ever authenticating

The fix: sessions are bound to PID *and* process start time. When a process calls `unlock`, the session manager records the PID and the boot-relative start timestamp from `kp_proc.p_starttime` (via `sysctl` with `KERN_PROC`). Every subsequent request checks both. A recycled PID has a different start time — microsecond precision makes collisions effectively impossible. The session check rejects it, and Touch ID is required again.

## Command-level blocking for AI agents

Process tree detection enables granular access control. When the caller is an agent, certain commands are blocked:

  - `--raw` — no plaintext stdout. Agents can't pipe raw values
  - `--copy` — no clipboard access for agents
  - `load`, `export`, `bundle`, `env` — no bulk secret operations

And certain commands remain available:

  - `ls` — agents can discover key names (no values shown)
  - `peek` — agents can see 8-character prefixes for verification
  - `get` — returns encrypted handoff, not raw values
  - `set --clipboard` — agents can store secrets from your clipboard

The CLI exits with a clear error: "This command is not available to AI agents." No ambiguity. The agent knows exactly why it was blocked and can tell the user.

## Honest limitations of process tree detection

This approach isn't perfect. We want to be upfront about where it breaks.

**Name-based matching has blind spots.** A new agent not in the signatures list won't be detected. We update the list with each release, but there's always a window. Obscure agents get treated as human callers.

**Detection is point-in-time.** It happens when the secret is requested. If an agent already has a secret in its environment from a previous session — before NoxKey was installed, or from a `.env` file it read earlier — detection can't revoke that access.

**This is macOS only.** The implementation uses `proc_pidinfo`, `sysctl`, and `LOCAL_PEERPID` — all macOS-specific APIs. The concept is portable to Linux (via `/proc`) and Windows (via `NtQueryInformationProcess`), but this code isn't.

**Sophisticated evasion is possible.** An attacker with root access could manipulate process names or inject into a legitimate process. But root access means your [Keychain secrets](https://noboxdev.com/blog/macos-keychain-for-developers) are already compromised regardless.

Despite these limitations — no other secrets manager distinguishes between human and agent callers at all. Every `.env` file, every `1password read` call, every `vault kv get` treats all callers identically. Imperfect detection that catches 95% of real-world agent access patterns is categorically better than zero detection. The [most common ways developers leak credentials](https://noboxdev.com/blog/credential-hygiene-for-developers) are all mitigated by this approach, even with its limitations.

We're not building an unbreakable wall. We're making the default safe.

When a developer installs NoxKey and an AI agent requests a secret, the right thing happens automatically — no configuration, no flags, no awareness required. That's the bar. Process-tree detection clears it.

Key Takeaway
  Process-tree agent detection uses macOS kernel APIs to walk from the requesting process to its ancestors, checking binary names against known AI coding tool signatures. Combined with dual verification (client + server via LOCAL_PEERPID), encrypted handoff delivery, and PID+start-time session binding, it catches 95% of real-world agent access patterns — without requiring any configuration from the developer.

---
*NoxKey is free and open source. `brew install no-box-dev/noxkey/noxkey` — [GitHub](https://github.com/No-Box-Dev/Noxkey) | [Website](https://noxkey.ai)*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>security</category>
      <category>macos</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>NoxKey — A macOS Secrets Manager With Touch ID and AI Agent Detection</title>
      <dc:creator>JasperNoBoxDev</dc:creator>
      <pubDate>Thu, 26 Mar 2026 22:34:59 +0000</pubDate>
      <link>https://dev.to/jaspernoboxdev/noxkey-a-macos-secrets-manager-with-touch-id-and-ai-agent-detection-fon</link>
      <guid>https://dev.to/jaspernoboxdev/noxkey-a-macos-secrets-manager-with-touch-id-and-ai-agent-detection-fon</guid>
      <description>&lt;p&gt;We were debugging a Stripe integration at 1am when Claude printed a live API key in its debug output. Full key. Right there in the conversation log.&lt;/p&gt;

&lt;p&gt;The agent was not malicious. The system was broken. The secret sat in a &lt;code&gt;.env&lt;/code&gt; file. The agent read the file and included the value in its response. That is what agents do — they read project files and use what they find. The &lt;code&gt;.env&lt;/code&gt; file was the vulnerability, not the agent.&lt;/p&gt;

&lt;p&gt;That night we started building NoxKey.&lt;/p&gt;

&lt;h2&gt;
  
  
  A macOS secrets manager built for developers
&lt;/h2&gt;

&lt;p&gt;NoxKey is a macOS menu bar app and CLI that stores secrets in the macOS Keychain, protected by Touch ID. No cloud. No accounts. No subscription. Your secrets live in Apple's encrypted storage on your machine and nowhere else.&lt;/p&gt;

&lt;p&gt;When an AI agent requests a secret, NoxKey detects it automatically and delivers the value through an encrypted handoff — the secret reaches the agent's process environment without ever appearing in its conversation context.&lt;/p&gt;

&lt;p&gt;Keychain storage. Touch ID authentication. Encrypted handoff for AI agents.&lt;/p&gt;

&lt;p&gt;0&lt;br&gt;
    cloud connections&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;30s
to install


0
config needed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Architecture: menu bar app + CLI over a Unix socket
&lt;/h2&gt;

&lt;p&gt;NoxKey has two pieces:&lt;/p&gt;

&lt;p&gt;Menu Bar App&lt;br&gt;
  SwiftUI · Native macOS&lt;/p&gt;

&lt;p&gt;Touch ID&lt;/p&gt;

&lt;p&gt;Agent Detection&lt;/p&gt;

&lt;p&gt;Keychain Access&lt;/p&gt;

&lt;p&gt;Session Manager&lt;/p&gt;

&lt;p&gt;Unix&lt;br&gt;
  Socket&lt;/p&gt;

&lt;p&gt;CLI&lt;br&gt;
  Swift · /usr/local/bin/noxkey&lt;/p&gt;

&lt;p&gt;get / set / ls&lt;/p&gt;

&lt;p&gt;import / unlock&lt;/p&gt;

&lt;p&gt;peek / strict&lt;/p&gt;

&lt;p&gt;Encrypted Handoff&lt;/p&gt;

&lt;p&gt;macOS Keychain&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The menu bar app&lt;/strong&gt; is a native SwiftUI application. It manages the Keychain, handles Touch ID prompts, performs &lt;a href="https://noboxdev.com/blog/process-tree-agent-detection" rel="noopener noreferrer"&gt;process-tree agent detection&lt;/a&gt;, and serves requests over a Unix domain socket. This is the server. It has Keychain entitlements, biometric access, and full control over what gets returned to whom.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The CLI&lt;/strong&gt; (&lt;code&gt;noxkey&lt;/code&gt;) talks to the menu bar app over that Unix socket. It does not touch the Keychain directly — every request goes through the server, which validates the caller independently.&lt;/p&gt;

&lt;p&gt;Why a Unix socket instead of XPC or HTTP? Unix sockets provide &lt;code&gt;LOCAL_PEERPID&lt;/code&gt; — the kernel tells the server exactly which process connected. No authentication tokens to manage. No port conflicts. No network exposure. The socket file lives at a user-specific path, accessible only to your user account.&lt;/p&gt;
&lt;h2&gt;
  
  
  Install this macOS secrets manager in 30 seconds
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;no-box-dev/noxkey/noxkey
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;==&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Downloading noxkey-0.6.43.tar.gz
&lt;span class="gp"&gt;==&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Installing noxkey
&lt;span class="gp"&gt;==&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Caveats
&lt;span class="go"&gt;NoxKey menu bar app installed to /Applications.
CLI installed to /usr/local/bin/noxkey.
&lt;/span&gt;&lt;span class="gp"&gt;==&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Summary
&lt;span class="go"&gt;  /Applications/NoxKey.app
  /usr/local/bin/noxkey
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Launch NoxKey.app from your Applications folder. It appears in the menu bar — a small key icon. That is the server running. The CLI works immediately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;noxkey &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="go"&gt;noxkey 0.6.43
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No account creation. No master password. No onboarding wizard. It uses your existing macOS login Keychain and your existing fingerprint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replace your .env files with Keychain storage
&lt;/h2&gt;

&lt;p&gt;If you have &lt;a href="https://noboxdev.com/blog/why-i-deleted-every-env-file" rel="noopener noreferrer"&gt;dozens of .env files scattered across your machine&lt;/a&gt;, the first step is importing them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;noxkey import myorg/api .env
&lt;span class="go"&gt;[Touch ID prompt]
Imported 6 secrets:
  myorg/api/STRIPE_KEY
  myorg/api/DATABASE_URL
  myorg/api/REDIS_URL
  myorg/api/CLOUDFLARE_TOKEN
  myorg/api/SENDGRID_KEY
  myorg/api/JWT_SECRET

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;noxkey &lt;span class="nb"&gt;ls &lt;/span&gt;myorg/api/
&lt;span class="go"&gt;  myorg/api/CLOUDFLARE_TOKEN
  myorg/api/DATABASE_URL
  myorg/api/JWT_SECRET
  myorg/api/REDIS_URL
  myorg/api/SENDGRID_KEY
  myorg/api/STRIPE_KEY

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Secrets are organized as &lt;code&gt;org/project/KEY&lt;/code&gt;. One secret, one location, accessible from any terminal in any project directory. No more duplicating the same Cloudflare token across six repos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Daily usage: Touch ID for every API key access
&lt;/h2&gt;

&lt;p&gt;The core pattern is one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;noxkey get myorg/api/STRIPE_KEY&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="go"&gt;[Touch ID prompt]
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$STRIPE_KEY&lt;/span&gt;
&lt;span class="gp"&gt;sk_live_...  #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;available &lt;span class="k"&gt;in &lt;/span&gt;your shell environment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Touch ID fires. The secret loads into your shell environment. Your script or application reads it from &lt;code&gt;$STRIPE_KEY&lt;/code&gt; like any environment variable.&lt;/p&gt;

&lt;p&gt;Session unlock eliminates friction&lt;br&gt;
  For batch operations — say you need five secrets to run your dev server — run &lt;code&gt;noxkey unlock myorg/api&lt;/code&gt; once with Touch ID. All subsequent &lt;code&gt;get&lt;/code&gt; calls under that prefix skip biometric auth for a configurable window. The session is bound to your PID &lt;em&gt;and&lt;/em&gt; process start time, so PID recycling cannot hijack it.&lt;/p&gt;

&lt;p&gt;Need multiple secrets at once? Session unlock handles that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;noxkey unlock myorg/api
&lt;span class="go"&gt;[Touch ID prompt — once]
Session unlocked for myorg/api/* (expires in 15 minutes)

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;noxkey get myorg/api/STRIPE_KEY&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;    &lt;span class="c"&gt;# no Touch ID&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;noxkey get myorg/api/DATABASE_URL&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;   &lt;span class="c"&gt;# no Touch ID&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;noxkey get myorg/api/REDIS_URL&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;      &lt;span class="c"&gt;# no Touch ID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One fingerprint, then flow. The session is bound to your process ID and its start time — &lt;a href="https://noboxdev.com/blog/process-tree-agent-detection" rel="noopener noreferrer"&gt;PID recycling cannot hijack it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For your most sensitive secrets, strict mode overrides sessions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;noxkey strict myorg/api/STRIPE_KEY
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;This key ALWAYS requires Touch ID, even during an active session
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How AI agent detection works
&lt;/h2&gt;

&lt;p&gt;When Claude Code runs &lt;code&gt;noxkey get&lt;/code&gt;, you do not configure anything. Detection is automatic.&lt;/p&gt;

&lt;p&gt;noxkey PID 61023 — get myorg/api/STRIPE_KEY    └─ zsh PID 61020 — spawned shell      └─ node PID 58401 — Claude Code runtime        └─ claude PID 58399 — MATCH! Agent detected Encrypted Handoff ← AES-256-CBC → temp script → source → self-delete Result: $STRIPE_KEY in shell env. Raw value never in conversation context.&lt;/p&gt;

&lt;p&gt;The agent can make API calls, run your test suite, and deploy your app. It just cannot see, log, or echo the raw secret value. If it tries to use &lt;code&gt;--raw&lt;/code&gt; or &lt;code&gt;--copy&lt;/code&gt;, the CLI blocks it.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://noboxdev.com/blog/five-ways-ai-agents-leak-secrets" rel="noopener noreferrer"&gt;six most common agent leak patterns&lt;/a&gt; — reading .env files, echoing secrets in debug output, storing values in conversation logs, hardcoding them in generated code, passing them to spawned processes — are all mitigated by this approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security, DX, and agent safety features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;#x1F91A;
Touch ID on every access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Not a password, not a PIN. Your fingerprint. &lt;a href="https://noboxdev.com/blog/touch-id-api-keys" rel="noopener noreferrer"&gt;Every time&lt;/a&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;#x1F512;
macOS Keychain storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://noboxdev.com/blog/macos-keychain-for-developers" rel="noopener noreferrer"&gt;Apple's Data Protection Keychain&lt;/a&gt;, backed by the Secure Enclave. Not a custom vault.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;#x1F6AB;
Zero network connections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;NoxKey never phones home. No telemetry, no sync. Your secrets never leave your machine.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;#x1F6E1;
Strict mode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;High-value secrets always require Touch ID, even during unlocked sessions.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;#x1F6A8;
DLP guard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Scans agent output for leaked secret values using 8-character fingerprints. Blocks leaks before they enter AI context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Developer experience
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;#x26A1;
One command
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;eval "$(noxkey get org/proj/KEY)"&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;#x1F513;
Session unlock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;One Touch ID, then batch operations flow without interruption.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;#x1F4E5;
.env import
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;noxkey import org/proj .env&lt;/code&gt; — migrate in one step.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;#x1F440;
Peek
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;noxkey peek org/proj/KEY&lt;/code&gt; — first 8 characters for verification, without exposing the full value.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;#x1F4C1;
Org/project hierarchy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Secrets are namespaced, searchable, and never duplicated across projects.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;#x1F5A5;
Menu bar UI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Browse, add, edit, and organize secrets without touching the terminal.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI agent security
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;#x1F916;
Automatic agent detection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://noboxdev.com/blog/process-tree-agent-detection" rel="noopener noreferrer"&gt;Process-tree walking&lt;/a&gt; identifies Claude, Cursor, Codex, Windsurf, Copilot, and others.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;#x1F510;
Encrypted handoff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Agents get secrets in their process environment, never in conversation context.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;#x1F6D1;
Command blocking
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;--raw&lt;/code&gt;, &lt;code&gt;--copy&lt;/code&gt;, &lt;code&gt;load&lt;/code&gt;, &lt;code&gt;export&lt;/code&gt;, &lt;code&gt;bundle&lt;/code&gt;, &lt;code&gt;env&lt;/code&gt; are all blocked for agent callers.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;#x1F50D;
DLP scanning
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Catches leaked values in agent output before they persist.&lt;/p&gt;

&lt;h2&gt;
  
  
  What NoxKey does not do
&lt;/h2&gt;

&lt;p&gt;Deliberate scope&lt;br&gt;
  NoxKey is for individual developers on macOS who need to keep API keys and tokens out of .env files and AI agent contexts. It is not a team vault, not cross-platform, not a password manager, and has no cloud sync.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not a team tool.&lt;/strong&gt; NoxKey is for individual developers. No shared vault, no role-based access, no audit log. For team secret management, look at Doppler or HashiCorp Vault.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not cross-platform.&lt;/strong&gt; macOS only. The security model depends on the macOS Keychain, Touch ID, and the Secure Enclave. These do not exist on Linux or Windows. The concepts are portable — this implementation is not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not a password manager.&lt;/strong&gt; NoxKey manages developer credentials — API keys, tokens, database URLs, webhook secrets. It does not autofill browser forms or sync across devices. Use 1Password or Bitwarden for that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No cloud sync.&lt;/strong&gt; Your secrets exist on one machine. If your laptop dies, you re-import. This is a feature — no server to breach, no sync protocol to exploit, no third party with your data. Backups are your responsibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we built a dotenv alternative for macOS
&lt;/h2&gt;

&lt;p&gt;We were using &lt;code&gt;.env&lt;/code&gt; files like everyone else. We had a &lt;code&gt;.gitignore&lt;/code&gt; entry. We thought we were fine.&lt;/p&gt;

&lt;p&gt;Then AI coding assistants became part of the daily workflow. Every project file became fair game for an agent to read, reference, and include in responses. The &lt;code&gt;.env&lt;/code&gt; file went from "slightly risky convenience" to "active liability." &lt;a href="https://noboxdev.com/blog/stop-putting-secrets-in-env-files" rel="noopener noreferrer"&gt;The dotenv pattern was designed in 2012&lt;/a&gt;, before AI agents existed. It assumes the only thing reading your project files is your code.&lt;/p&gt;

&lt;p&gt;We looked at existing options. 1Password CLI is solid but requires a subscription and has no agent detection. HashiCorp Vault targets infrastructure teams, not solo developers. Doppler is cloud-hosted. None of them distinguish between a human caller and an AI agent.&lt;/p&gt;

&lt;p&gt;The macOS Keychain was sitting right there. Encrypted. Biometric. Local. Already on every Mac. All it needed was a developer-friendly interface and awareness of AI agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&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;no-box-dev/noxkey/noxkey
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Free. No account. No cloud. Your Keychain and your fingerprint.&lt;/p&gt;

&lt;p&gt;Key Takeaway&lt;br&gt;
  NoxKey stores your secrets in the macOS Keychain with Touch ID protection and zero cloud connections. When an AI agent requests a secret, process-tree detection delivers the value through an encrypted handoff — the secret reaches the agent's shell environment without appearing in its conversation context. Install with &lt;code&gt;brew install no-box-dev/noxkey/noxkey&lt;/code&gt; and &lt;a href="https://noboxdev.com/blog/stop-putting-secrets-in-env-files" rel="noopener noreferrer"&gt;your .env files become obsolete&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Read more: &lt;a href="https://noboxdev.com/blog/why-i-deleted-every-env-file" rel="noopener noreferrer"&gt;how we migrated 47 .env files&lt;/a&gt;, &lt;a href="https://noboxdev.com/blog/touch-id-api-keys" rel="noopener noreferrer"&gt;how Touch ID protects your API keys&lt;/a&gt;, or &lt;a href="https://noboxdev.com/blog/five-ways-ai-agents-leak-secrets" rel="noopener noreferrer"&gt;six ways AI agents leak secrets&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://noxkey.ai" rel="noopener noreferrer"&gt;noxkey.ai&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;What is NoxKey?&lt;/em&gt;&lt;/strong&gt;*&lt;br&gt;
  NoxKey is a free, open-source macOS menu bar app that stores developer secrets (API keys, tokens, passwords) in the macOS Keychain with Touch ID. It replaces .env files with hardware-encrypted storage and adds AI agent detection with encrypted handoff.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;How does NoxKey detect AI agents?&lt;/em&gt;&lt;/strong&gt;*&lt;br&gt;
  NoxKey walks the macOS process tree when a secret is requested. If it detects an AI agent (Claude Code, Cursor, Copilot) in the calling chain, it switches to encrypted handoff — the secret reaches the agent's shell environment through a self-deleting encrypted script, never entering the conversation context.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Is NoxKey free?&lt;/em&gt;&lt;/strong&gt;*&lt;br&gt;
  Yes. NoxKey is MIT-licensed, open source, and completely free. No account, no subscription, no cloud. Install with &lt;code&gt;brew install no-box-dev/noxkey/noxkey&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;How do I migrate from .env files?&lt;/em&gt;&lt;/strong&gt;*&lt;br&gt;
  One command: &lt;code&gt;noxkey import myorg .env&lt;/code&gt;. This imports all key-value pairs into the macOS Keychain. Then delete the .env file. Access secrets with &lt;code&gt;eval "$(noxkey get myorg/KEY)"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Does NoxKey send data to the cloud?&lt;/em&gt;&lt;/strong&gt;*&lt;br&gt;
  No. NoxKey makes zero outbound network connections. All secrets are stored locally in the macOS Keychain, which uses Apple's Secure Enclave for hardware encryption. This is verifiable via macOS network monitoring.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;What's the difference between NoxKey and 1Password CLI?&lt;/em&gt;&lt;/strong&gt;*&lt;br&gt;
  NoxKey is local-only (no cloud, no account), free, and includes AI agent detection with encrypted handoff. 1Password CLI requires a cloud subscription and has no AI-specific security features. &lt;a href="https://noxkey.ai/compare/noxkey-vs-1password-cli.html" rel="noopener noreferrer"&gt;Full comparison&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;NoxKey is free and open source. &lt;code&gt;brew install no-box-dev/noxkey/noxkey&lt;/code&gt; — &lt;a href="https://github.com/No-Box-Dev/Noxkey" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://noxkey.ai" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>macos</category>
      <category>opensource</category>
      <category>cli</category>
    </item>
    <item>
      <title>6 Ways AI Agents Leak Your API Keys and Secrets</title>
      <dc:creator>JasperNoBoxDev</dc:creator>
      <pubDate>Sun, 22 Mar 2026 16:12:45 +0000</pubDate>
      <link>https://dev.to/jaspernoboxdev/6-ways-ai-agents-leak-your-secrets-dlb</link>
      <guid>https://dev.to/jaspernoboxdev/6-ways-ai-agents-leak-your-secrets-dlb</guid>
      <description>&lt;p&gt;We watched Claude Code include a Stripe secret key in a debug log. It was trying to help — we had asked it to figure out why a payment integration was failing, and it printed the full HTTP request, headers and all. &lt;code&gt;Authorization: Bearer sk_live_...&lt;/code&gt;, right there in the conversation context. Stored on Anthropic's servers, in the conversation history, visible in the terminal scrollback.&lt;/p&gt;

&lt;p&gt;That is when we built the DLP guard.&lt;/p&gt;

&lt;p&gt;AI coding assistants are the most productive tools we have ever used. They are also the biggest credentials risk most developers are not thinking about. Here are six ways your secrets leak through AI agents — with reproduction steps, severity ratings, and fixes for each one.&lt;/p&gt;

&lt;p&gt;6&lt;br&gt;
    leak vectors identified&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;30s
to install the DLP guard


0
false positives in production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Leak #1 — Reading your .env file&lt;/p&gt;
&lt;h2&gt;
  
  
  1. AI agents read your .env file Critical
&lt;/h2&gt;

&lt;p&gt;Every AI coding assistant with file access can read your &lt;code&gt;.env&lt;/code&gt; file. It is a plaintext file in your project directory. The agent reads project files to understand context. There is no access control, no authentication, no prompt asking "should this tool see your Stripe key?"&lt;/p&gt;
&lt;h3&gt;
  
  
  How to reproduce it
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file with a test secret. Open any AI coding assistant. Ask it to "explain the project structure" or "help me debug the API integration." Watch the agent's tool calls — it will read the &lt;code&gt;.env&lt;/code&gt; file as part of understanding your codebase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a test .env&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'TEST_SECRET=this-value-should-not-appear-in-chat'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .env

&lt;span class="c"&gt;# In Claude Code:&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"What API services does this project use?"&lt;/span&gt;
&lt;span class="c"&gt;# Agent reads .env → TEST_SECRET value is now in context&lt;/span&gt;

&lt;span class="c"&gt;# In Cursor (0.45+):&lt;/span&gt;
&lt;span class="c"&gt;# Open the project → Cmd+L → "Summarize the project config"&lt;/span&gt;
&lt;span class="c"&gt;# Cursor indexes .env as part of the workspace&lt;/span&gt;

&lt;span class="c"&gt;# In GitHub Copilot:&lt;/span&gt;
&lt;span class="c"&gt;# Open .env in a tab → Copilot Chat has access to open file contents&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent is not being malicious. It is doing what you asked: understand the codebase. Your &lt;code&gt;.env&lt;/code&gt; is part of the codebase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Do not have a &lt;code&gt;.env&lt;/code&gt; file. &lt;a href="https://noboxdev.com/blog/stop-putting-secrets-in-env-files" rel="noopener noreferrer"&gt;Store secrets in the macOS Keychain instead&lt;/a&gt;. Load them at runtime with &lt;code&gt;eval "$(noxkey get org/project/KEY)"&lt;/code&gt;. There is no file for the agent to read.&lt;/p&gt;

&lt;p&gt;Leak #2 — Debug output exposure&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Secrets in debug output Critical
&lt;/h2&gt;

&lt;p&gt;"The API call is failing, can you debug this?" The agent helpfully prints the full request to show you what is happening:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# You ask Claude Code:&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"The Stripe charge endpoint is returning 401, can you debug?"&lt;/span&gt;

&lt;span class="c"&gt;# Agent output:&lt;/span&gt;
Here&lt;span class="s1"&gt;'s the failing request:

  curl -X POST https://api.stripe.com/v1/charges \
    -H "Authorization: Bearer sk_live_51ABC...xYz" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "amount=2000&amp;amp;currency=usd"

The issue is that your API key has been rotated. The key
starting with sk_live_51ABC is no longer valid...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That key is now in your conversation history. On the AI provider's servers. In your terminal scrollback. If you are sharing your screen or recording a demo, it is in the recording too.&lt;/p&gt;

&lt;p&gt;The agent saw the value in &lt;code&gt;process.env.STRIPE_SECRET_KEY&lt;/code&gt; (referenced in your code) and included it because showing the full request seemed helpful for debugging. Right call for debugging. Wrong call for security.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; A DLP guard that scans agent output before it enters the conversation. NoxKey's guard matches against 8-character fingerprints of your stored secrets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set up the DLP guard (one-time)&lt;/span&gt;
noxkey guard &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# What it catches:&lt;/span&gt;
&lt;span class="c"&gt;# Agent output: "Authorization: Bearer sk_live_51ABC..."&lt;/span&gt;
&lt;span class="c"&gt;# Guard: "BLOCKED — output contains value matching myorg/project/STRIPE_KEY (peek: sk_live_5)"&lt;/span&gt;
&lt;span class="c"&gt;# The output never enters the conversation context.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Credentials stored in conversation logs High
&lt;/h2&gt;

&lt;p&gt;"Here is my config, help me fix this deployment." You paste your &lt;code&gt;.env&lt;/code&gt; into ChatGPT. Here is where that data goes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transmitted over HTTPS to the API endpoint&lt;/li&gt;
&lt;li&gt;Stored in the conversation database&lt;/li&gt;
&lt;li&gt;Logged for abuse detection and safety monitoring&lt;/li&gt;
&lt;li&gt;Potentially queued for human review if it triggers filters&lt;/li&gt;
&lt;li&gt;Retained per the data retention policy (which can change)&lt;/li&gt;
&lt;li&gt;Backed up across the provider's infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Six copies minimum, on infrastructure you do not control, with retention policies you did not agree to read. Even if you delete the conversation in the UI, the data was transmitted and stored. Deletion from the frontend does not guarantee deletion from logs, backups, or training pipelines.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to reproduce it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# In ChatGPT, Claude, or any AI chat:
&amp;gt; "Here's my .env file, can you help me debug?"
&amp;gt; DATABASE_URL=postgresql://admin:s3cretP@ss@db.example.com/prod
&amp;gt; STRIPE_KEY=sk_live_...

# That data is now stored on the provider's servers.
# You cannot un-send it. You cannot verify deletion.
# If you used a wrapper app or browser extension, add another copy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Never paste credentials into any chat interface. If the agent needs access to a secret, it should flow through the OS — Keychain to encrypted handoff to process environment — not through the conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. API keys hardcoded in generated code High
&lt;/h2&gt;

&lt;p&gt;You ask the agent to "set up the Stripe integration." It generates a config file. Because it saw your API key in the environment (or in a file it read earlier), it hardcodes the value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;You&lt;/span&gt; &lt;span class="nx"&gt;ask&lt;/span&gt; &lt;span class="nx"&gt;Cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Set up Stripe with the charge endpoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Cursor&lt;/span&gt; &lt;span class="nx"&gt;generates&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stripeConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;secretKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sk_live_51ABC...xYz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// High&lt;/span&gt;

&lt;span class="nx"&gt;AI&lt;/span&gt; &lt;span class="nx"&gt;agents&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt; &lt;span class="nx"&gt;execute&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="nx"&gt;spawn&lt;/span&gt; &lt;span class="nx"&gt;subprocesses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="nx"&gt;are&lt;/span&gt; &lt;span class="nx"&gt;inherited&lt;/span&gt; &lt;span class="nx"&gt;by&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="nx"&gt;processes&lt;/span&gt; &lt;span class="nx"&gt;by&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="nx"&gt;Your&lt;/span&gt; &lt;span class="nx"&gt;shell&lt;/span&gt; &lt;span class="nx"&gt;STRIPE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;sk_live_&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="nx"&gt;inherits&lt;/span&gt;
  &lt;span class="nx"&gt;claudeinherits&lt;/span&gt; &lt;span class="nx"&gt;STRIPE_KEY&lt;/span&gt;
  &lt;span class="nx"&gt;inherits&lt;/span&gt;
  &lt;span class="nx"&gt;nodeinherits&lt;/span&gt; &lt;span class="nx"&gt;STRIPE_KEY&lt;/span&gt;
  &lt;span class="nx"&gt;inherits&lt;/span&gt;
  &lt;span class="nx"&gt;bash&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;curl ...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;inherits&lt;/span&gt; &lt;span class="nx"&gt;STRIPE_KEY&lt;/span&gt;
  &lt;span class="nx"&gt;inherits&lt;/span&gt;
  &lt;span class="nx"&gt;curlhas&lt;/span&gt; &lt;span class="nx"&gt;full&lt;/span&gt; &lt;span class="nx"&gt;access&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;STRIPE_KEY&lt;/span&gt;

&lt;span class="nx"&gt;Every&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;tree&lt;/span&gt; &lt;span class="nx"&gt;has&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;Stripe&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="nx"&gt;did&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;steal&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="nx"&gt;inherited&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;same&lt;/span&gt; &lt;span class="nx"&gt;way&lt;/span&gt; &lt;span class="nx"&gt;every&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt; &lt;span class="nx"&gt;inherits&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;Unix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;When&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="nx"&gt;spawns&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="s2"&gt;`curl`&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt; &lt;span class="nx"&gt;access&lt;/span&gt; &lt;span class="s2"&gt;`$STRIPE_KEY`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="err"&gt;###&lt;/span&gt; &lt;span class="nx"&gt;How&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;reproduce&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Set a secret in your shell
&lt;/h1&gt;

&lt;p&gt;export TEST_SECRET="this-is-sensitive"&lt;/p&gt;

&lt;h1&gt;
  
  
  In Claude Code:
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;"Run: echo $TEST_SECRET"&lt;/p&gt;
&lt;h1&gt;
  
  
  Output: this-is-sensitive
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  The agent accessed an inherited environment variable
&lt;/h1&gt;

&lt;h1&gt;
  
  
  and printed it to the conversation context.
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Most agents do not actively exfiltrate credentials this way. But the capability is there. An agent with code execution and inherited environment variables has everything it needs to make authenticated API calls on your behalf.

**The fix:** [Process-tree detection](https://noboxdev.com/blog/process-tree-agent-detection) identifies when an AI agent — not a human — is requesting secrets. When an agent is detected, secrets are delivered via encrypted handoff instead of raw values. Bulk export commands are blocked. The agent gets scoped access instead of full environment inheritance.

Leak #6 — The vector nobody is talking about

## 6. Credentials exposed through MCP tool-use High


MCP (Model Context Protocol) tools and function-calling plugins let agents make HTTP requests, query databases, and interact with external services. When those tools need authentication, the credentials often flow through the agent's context.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  MCP server config (e.g., in .cursor/mcp.json or claude_desktop_config.json):
&lt;/h1&gt;

&lt;p&gt;{&lt;br&gt;
  "mcpServers": {&lt;br&gt;
    "database": {&lt;br&gt;
      "command": "npx",&lt;br&gt;
      "args": ["@modelcontextprotocol/server-postgres",&lt;br&gt;
               "postgresql://admin:s3cretP@&lt;a href="mailto:ss@db.example.com"&gt;ss@db.example.com&lt;/a&gt;/prod"]&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
That connection string — with the password — sits in a JSON config file. The agent reads it. The MCP server process inherits it. If the agent logs the connection for debugging, the password is in the conversation.

It gets worse with HTTP-based tools. An agent calling a REST API through an MCP tool might construct the request with an `Authorization` header. The tool call and its parameters — including the auth header — are part of the conversation context. Logged, stored, and visible in the conversation history.

### How to reproduce it

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Set up an MCP server with credentials in the config
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Ask the agent to "query the database for recent users"
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Watch the tool call — the connection string (including password)
&lt;/h1&gt;

&lt;h1&gt;
  
  
  appears in the agent's tool invocation log
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Or: configure an API tool with an auth header
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Ask the agent to "fetch my account details from the API"
&lt;/h1&gt;

&lt;h1&gt;
  
  
  The Authorization header appears in the tool call parameters
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
**The fix:** Never put credentials in MCP config files as plaintext. Use environment variable references that resolve at runtime. Better yet, have the MCP server pull credentials from the Keychain directly, so the agent never sees or transmits the auth values.

## The root cause behind every AI agent secret leak

All six leaks share one root cause: **secrets and agents occupy the same space**. The secret is in a file the agent reads, an environment it inherits, a config it parses, or a conversation it participates in.

Secrets and agents occupy the same space. The fix is separation.

The solution is separation. Secrets flow through secure channels — Keychain to encrypted handoff to process environment. Agents operate in their context — text, conversation, code generation. The two never mix.

Install the DLP guard in 30 seconds

## The DLP guard: catch leaked API keys automatically


The NoxKey DLP guard scans agent output against 8-character fingerprints of every secret in your Keychain. If any output contains a value matching a stored secret, it blocks the output before it enters the conversation.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Install the guard
&lt;/h1&gt;

&lt;p&gt;noxkey guard install&lt;/p&gt;

&lt;h1&gt;
  
  
  It runs automatically as a PostToolUse hook in Claude Code.
&lt;/h1&gt;

&lt;h1&gt;
  
  
  No config. No setup beyond the install command.
&lt;/h1&gt;

&lt;h1&gt;
  
  
  What it looks like when it catches something:
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Agent runs: curl -v &lt;a href="https://api.stripe.com/v1/charges" rel="noopener noreferrer"&gt;https://api.stripe.com/v1/charges&lt;/a&gt;
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Agent output contains: "Authorization: Bearer sk_live_51ABC..."
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Guard: BLOCKED — matches myorg/project/STRIPE_KEY
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Output is redacted before entering context.
&lt;/h1&gt;

&lt;h1&gt;
  
  
  You see: [REDACTED — credential detected]
&lt;/h1&gt;

&lt;h1&gt;
  
  
  The agent sees: nothing. The secret never entered the conversation.
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
This catches leaks 2, 3, and 4 — debug output, conversation logs, and generated code that includes credential values. It does not prevent the agent from accessing `.env` files or inheriting environment variables (leaks 1 and 5), which is why [eliminating .env files](https://noboxdev.com/blog/stop-putting-secrets-in-env-files) and using encrypted handoff are still necessary.

The guard matches on 8-character prefixes, so extremely short secrets or secrets that share prefixes with common strings could theoretically produce false positives. In practice, API keys and tokens are long enough that this is not an issue. We have been running it for months with zero false positives across ~200 stored secrets.

## How to secure your API keys from AI agents right now

The leaks are real. The fixes exist. Here is the priority order:

  - **Delete your .env files.** Move secrets to the Keychain. This eliminates leak #1 entirely. ([Here is how we did it.](https://noboxdev.com/blog/why-i-deleted-every-env-file))
  - **Install the DLP guard.** One command. Catches leaks #2, #3, and #4 automatically.
  - **Use encrypted handoff.** Process-tree detection + encrypted responses prevent leak #5.
  - **Audit your MCP configs.** Remove hardcoded credentials from tool server configurations.
  - **Stop pasting credentials into chats.** There is no technical fix for voluntarily sending secrets to a third party.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Install NoxKey and the DLP guard — 30 seconds total
&lt;/h1&gt;

&lt;p&gt;brew install no-box-dev/noxkey/noxkey&lt;br&gt;
noxkey guard install&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Key Takeaway

AI agents leak secrets through six vectors: reading .env files, debug output, conversation logs, generated code, process inheritance, and MCP tool-use. All six share the same root cause — secrets and agents occupy the same space. The fix is separation: [store secrets in the Keychain](https://noboxdev.com/blog/stop-putting-secrets-in-env-files), deliver them via encrypted handoff, and install the DLP guard to catch any values that slip through. [Touch ID](https://noboxdev.com/blog/touch-id-api-keys) ensures no agent can access a secret without your physical confirmation.

---
*NoxKey is free and open source. `brew install no-box-dev/noxkey/noxkey` — [GitHub](https://github.com/No-Box-Dev/Noxkey) | [Website](https://noxkey.ai)*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>devops</category>
      <category>macos</category>
    </item>
    <item>
      <title>Why We Deleted Every .env File — And What Replaced Them</title>
      <dc:creator>JasperNoBoxDev</dc:creator>
      <pubDate>Sun, 22 Mar 2026 15:54:51 +0000</pubDate>
      <link>https://dev.to/jaspernoboxdev/why-i-deleted-every-env-file-on-my-machine-3l6a</link>
      <guid>https://dev.to/jaspernoboxdev/why-i-deleted-every-env-file-on-my-machine-3l6a</guid>
      <description>&lt;p&gt;It started at 1am on a Tuesday. We were rotating a Cloudflare API token — routine stuff, the old one was about to expire. We updated the &lt;code&gt;.env&lt;/code&gt; in the active project, ran the deploy, everything worked. Then the staging environment for a different project broke. Same token, different &lt;code&gt;.env&lt;/code&gt;, still pointing to the old value.&lt;/p&gt;

&lt;p&gt;Fixed it. Then a third project broke the next morning.&lt;/p&gt;

&lt;p&gt;That is when we ran the command that changed everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;find ~/dev &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;".env"&lt;/span&gt; &lt;span class="nt"&gt;-not&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; &lt;span class="s2"&gt;"*/node_modules/*"&lt;/span&gt; &lt;span class="nt"&gt;-not&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; &lt;span class="s2"&gt;"*/.git/*"&lt;/span&gt;
&lt;span class="go"&gt;./boundless-learning/.env
./gitpulse/.env
./gitpulse/api/.env
./noxterm/website/.env
./blindspot/.env
./blindspot/api/.env
./112schade/.env
./bitz-snoek/.env
./playnist/.env
&lt;/span&gt;&lt;span class="c"&gt;...
&lt;/span&gt;&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;find ~/dev &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;".env"&lt;/span&gt; &lt;span class="nt"&gt;-not&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; &lt;span class="s2"&gt;"*/node_modules/*"&lt;/span&gt; &lt;span class="nt"&gt;-not&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; &lt;span class="s2"&gt;"*/.git/*"&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;span class="go"&gt;47
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forty-seven &lt;code&gt;.env&lt;/code&gt; files. On one machine.&lt;/p&gt;

&lt;p&gt;47&lt;br&gt;
    .env files on one machine&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;6
duplicated keys across projects


8 months
forgotten with live credentials
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  The .env file audit that started it all
&lt;/h2&gt;

&lt;p&gt;We spent the next hour opening every single one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The duplicates.&lt;/strong&gt; The Cloudflare API token — the one that just broke three projects — appeared in 6 different files. An OpenAI API key was in 8. The same Postmark server token sat in 4 projects, two of which had not been touched in over a year.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The expired ones.&lt;/strong&gt; A Stripe test key that had been rotated months ago was still sitting in three &lt;code&gt;.env&lt;/code&gt; files. It no longer worked, but nobody cleaned it up. If it had been accidentally used in production, the result would have been silent auth failures — mysterious 401s at 2am with no explanation.&lt;/p&gt;

&lt;p&gt;Critical&lt;br&gt;
  &lt;strong&gt;The dangerous ones.&lt;/strong&gt; A healthcare API project had a &lt;code&gt;.env&lt;/code&gt; with a production database connection string. Full admin credentials. The project was archived — untouched for 8 months. But the credentials were still valid. Anyone with access to the machine could have connected to a production database with patient-adjacent data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The forgotten ones.&lt;/strong&gt; A side project from early 2024 — a weekend experiment, completely forgotten — still had a live Stripe secret key in its &lt;code&gt;.env&lt;/code&gt;. Not a test key. The real one. Connected to a real account with a real credit card.&lt;/p&gt;

&lt;p&gt;Forty-seven plaintext files with zero authentication, scattered across the filesystem, containing credentials we could not even remember storing. Some valid, some expired, no way to tell which without checking each one manually.&lt;/p&gt;
&lt;h2&gt;
  
  
  Migrating from .env files to macOS Keychain
&lt;/h2&gt;

&lt;p&gt;We decided to move everything to the macOS Keychain using NoxKey and delete every &lt;code&gt;.env&lt;/code&gt; file on the machine. The whole process took one afternoon.&lt;/p&gt;

&lt;p&gt;The workflow for each project:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.env workflow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="c"&gt;# Plaintext file, no auth
&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="err"&gt;cat&lt;/span&gt; &lt;span class="err"&gt;.env&lt;/span&gt;
&lt;span class="py"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;postgresql://...&lt;/span&gt;
&lt;span class="py"&gt;OAUTH_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;abc...&lt;/span&gt;
&lt;span class="py"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;sk-proj-...&lt;/span&gt;

&lt;span class="c"&gt;# Hope you .gitignored it
# Hope no agent reads it
# Hope you remember to update it
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NoxKey workflow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Step 1: Import the .env file
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;noxkey import noboxdev/gitpulse .env
&lt;span class="go"&gt;✓ Imported 4 secrets

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Step 2: Verify everything landed
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;noxkey &lt;span class="nb"&gt;ls &lt;/span&gt;noboxdev/gitpulse/
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Step 3: Peek to confirm
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;noxkey peek noboxdev/gitpulse/OPENAI_API_KEY
&lt;span class="go"&gt;sk-proj-...

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Step 4: Test it works
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;noxkey get noboxdev/gitpulse/OPENAI_API_KEY&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Step 5: Delete the liability
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Import .env → Verify with ls → Peek to confirm → Test with eval → Delete .env&lt;/p&gt;

&lt;p&gt;For projects that shared secrets — like the Cloudflare token that lived in 6 places — we stored it once under a shared prefix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;noxkey &lt;span class="nb"&gt;set &lt;/span&gt;shared/CLOUDFLARE_API_TOKEN &lt;span class="nt"&gt;--clipboard&lt;/span&gt;
&lt;span class="go"&gt;✓ Stored shared/CLOUDFLARE_API_TOKEN
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One token. One location. Accessible from any project. When we rotate it next time, we update it once. Not six times. Not three-out-of-six times.&lt;/p&gt;

&lt;p&gt;The healthcare API credentials got an extra layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;noxkey strict noboxdev/healthcare-api/DATABASE_URL
&lt;span class="go"&gt;✓ Marked as strict — always requires Touch ID
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Strict mode means that secret always requires &lt;a href="https://noboxdev.com/blog/touch-id-api-keys" rel="noopener noreferrer"&gt;Touch ID&lt;/a&gt;, even during a session unlock. No shortcuts for credentials that could expose patient data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first week without .env files
&lt;/h2&gt;

&lt;p&gt;The first two days were friction city.&lt;/p&gt;

&lt;p&gt;Every time we opened a terminal, muscle memory reached for the &lt;code&gt;.env&lt;/code&gt; that was not there anymore. Instead of &lt;code&gt;source .env&lt;/code&gt; or letting &lt;code&gt;dotenv&lt;/code&gt; auto-load, the workflow was &lt;code&gt;eval "$(noxkey get noboxdev/project/KEY)"&lt;/code&gt; with a Touch ID prompt for each secret.&lt;/p&gt;

&lt;p&gt;Day two almost broke us. Debugging a webhook integration, restarting the server about fifteen times in an hour. Touch ID fifteen times. It felt excessive.&lt;/p&gt;

&lt;p&gt;Tip&lt;br&gt;
  Then we discovered session unlock:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;noxkey unlock noboxdev/blindspot
&lt;span class="go"&gt;✓ Session unlocked — Touch ID skipped for noboxdev/blindspot/* until session expires
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One Touch ID authentication, then every &lt;code&gt;get&lt;/code&gt; under that prefix skips the prompt for the rest of the session. Unlock at the start of a work session and forget about it.&lt;/p&gt;

&lt;p&gt;By day four, the new workflow felt natural. By end of week, it was invisible.&lt;/p&gt;

&lt;h2&gt;
  
  
  How AI agent security became the bigger win
&lt;/h2&gt;

&lt;p&gt;Two weeks after the migration, we were pair-programming with Claude Code on the Blindspot project. The agent needed the Postmark token to test an email integration. Old workflow: it would have read the &lt;code&gt;.env&lt;/code&gt; and the raw token would be sitting in the conversation context. Logged. Visible. Potentially leaked in an error message.&lt;/p&gt;

&lt;p&gt;Instead, the agent ran &lt;code&gt;noxkey get&lt;/code&gt;. NoxKey detected the agent by &lt;a href="https://noboxdev.com/blog/process-tree-agent-detection" rel="noopener noreferrer"&gt;walking the process tree&lt;/a&gt;, encrypted the value with AES-256-CBC, wrote a self-deleting temp script, and returned a &lt;code&gt;source&lt;/code&gt; command. The secret reached the shell environment, but the raw value never appeared in the conversation.&lt;/p&gt;

&lt;p&gt;We had not even been thinking about AI agent security during the migration. We deleted our &lt;code&gt;.env&lt;/code&gt; files because of the duplication and rotation mess. The agent safety was a side effect — and turned out to be the more important benefit. We use AI agents every day now. Every single session would have been reading plaintext secrets if those &lt;code&gt;.env&lt;/code&gt; files still existed.&lt;/p&gt;

&lt;p&gt;For more on this attack surface, we wrote about &lt;a href="https://noboxdev.com/blog/five-ways-ai-agents-leak-secrets" rel="noopener noreferrer"&gt;six specific ways agents can leak your secrets&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six months without a single .env file
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Key rotation is a non-event.&lt;/strong&gt; When the Cloudflare token expires, we update it in one place. Every project picks up the new value next time it runs &lt;code&gt;noxkey get&lt;/code&gt;. No hunting through directories. No grepping for old values. No "which three of the six copies did we forget to update?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We know exactly what we have.&lt;/strong&gt; &lt;code&gt;noxkey ls&lt;/code&gt; shows every secret on the machine, organized by project. No more discovering a forgotten Stripe key in an archived repo. If it is in the Keychain, we can see it. If it is not, it does not exist on this machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;noxkey &lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;span class="go"&gt;noboxdev/blindspot/POSTMARK_SERVER_TOKEN
noboxdev/blindspot/DATABASE_URL
noboxdev/gitpulse/DATABASE_URL
noboxdev/gitpulse/OAUTH_CLIENT_SECRET
noboxdev/gitpulse/OPENAI_API_KEY
noboxdev/healthcare-api/DATABASE_URL          [strict]
shared/CLOUDFLARE_API_TOKEN
shared/CLOUDFLARE_ACCOUNT_ID
&lt;/span&gt;&lt;span class="c"&gt;...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;New projects start clean.&lt;/strong&gt; No &lt;code&gt;.env.example&lt;/code&gt; to copy and fill in. Store secrets once with &lt;code&gt;noxkey set&lt;/code&gt; and load them with &lt;code&gt;eval&lt;/code&gt;. The project directory has zero credential files. Nothing to accidentally commit, nothing for an agent to read, nothing to forget about when the project gets archived.&lt;/p&gt;

&lt;p&gt;The background anxiety about plaintext credentials is gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The anxiety is gone.&lt;/strong&gt; This one was unexpected. We did not realize how much low-grade background worry those plaintext files caused. "Did we &lt;code&gt;.gitignore&lt;/code&gt; that correctly?" "Is that old project's &lt;code&gt;.env&lt;/code&gt; still sitting there with live keys?" "Did the AI just read our database credentials?" Those questions do not exist anymore. The secrets are in the Keychain, behind Touch ID.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest downsides of leaving .env behind
&lt;/h2&gt;

&lt;p&gt;It is macOS only. We work exclusively on macOS, so this is not a limitation for us. If you are on a mixed team, your Linux colleagues need a different solution. The principle is the same — use your OS credential store — but NoxKey will not help them.&lt;/p&gt;

&lt;p&gt;Onboarding new team members takes an extra step. Instead of "here is the &lt;code&gt;.env.example&lt;/code&gt;, fill in your keys," it is "install NoxKey, then &lt;code&gt;noxkey set&lt;/code&gt; each key." More steps the first time. Simpler every time after.&lt;/p&gt;

&lt;p&gt;Some tools expect &lt;code&gt;.env&lt;/code&gt; files. Docker Compose, certain Node.js frameworks, Vercel's local dev server. For those, we generate a temporary &lt;code&gt;.env&lt;/code&gt; from the Keychain, use it, and delete it. Not perfect. But the alternative is 47 plaintext files with no authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run this command right now
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find ~/dev &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;".env"&lt;/span&gt; &lt;span class="nt"&gt;-not&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; &lt;span class="s2"&gt;"*/node_modules/*"&lt;/span&gt; &lt;span class="nt"&gt;-not&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; &lt;span class="s2"&gt;"*/.git/*"&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whatever number you see — that is how many plaintext files with zero authentication are on your machine right now, containing credentials that any process, any agent, any accidental &lt;code&gt;git push&lt;/code&gt; can expose.&lt;/p&gt;

&lt;p&gt;NoxKey is not the only answer. But &lt;a href="https://noboxdev.com/blog/stop-putting-secrets-in-env-files" rel="noopener noreferrer"&gt;&lt;code&gt;.env&lt;/code&gt; files are the wrong answer&lt;/a&gt;. Use your Keychain. Use 1Password CLI. Use &lt;em&gt;something&lt;/em&gt; with actual authentication. Stop treating plaintext files as secret storage.&lt;/p&gt;

&lt;p&gt;Key Takeaway&lt;br&gt;
  47 .env files with zero authentication, scattered across one machine, containing credentials we could not even remember storing. The fix: import them into your OS Keychain with &lt;code&gt;noxkey import&lt;/code&gt;, verify with &lt;code&gt;noxkey ls&lt;/code&gt;, then delete every .env file. One afternoon of work eliminates plaintext secrets, duplicate key rot, and &lt;a href="https://noboxdev.com/blog/five-ways-ai-agents-leak-secrets" rel="noopener noreferrer"&gt;AI agent exposure&lt;/a&gt; — permanently.&lt;/p&gt;

&lt;p&gt;If NoxKey is the route you want to take:&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;no-box-dev/noxkey/noxkey
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Free, no account, no cloud, your secrets never leave your machine. The migration took one afternoon. Six months later, there is no going back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;How many .env files does the average developer have?&lt;/em&gt;&lt;/strong&gt;*&lt;br&gt;
  We found 47 across one machine — spanning active projects, archived repos, and forgotten prototypes. Many contained duplicate or expired keys. Running &lt;code&gt;noxkey scan .&lt;/code&gt; from your home directory will find yours.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;What replaces .env files?&lt;/em&gt;&lt;/strong&gt;*&lt;br&gt;
  The macOS Keychain. It's hardware-encrypted, protected by Touch ID, and already on your Mac. NoxKey provides a developer CLI on top: &lt;code&gt;noxkey import myorg .env&lt;/code&gt; migrates your secrets, then &lt;code&gt;eval "$(noxkey get myorg/KEY)"&lt;/code&gt; loads them into your shell.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Is it safe to delete .env files after migrating?&lt;/em&gt;&lt;/strong&gt;*&lt;br&gt;
  Yes, once you've verified the import with &lt;code&gt;noxkey ls myorg/&lt;/code&gt; and confirmed each key with &lt;code&gt;noxkey peek myorg/KEY&lt;/code&gt; (shows first 8 characters). We recommend keeping a backup for 48 hours, then deleting permanently.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Does this work with Docker and CI/CD?&lt;/em&gt;&lt;/strong&gt;*&lt;br&gt;
  NoxKey is for local development. For Docker and CI/CD, use your provider's secret management (GitHub Actions secrets, AWS Secrets Manager, etc.). NoxKey replaces the plaintext .env files on your development machine.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;NoxKey is free and open source. &lt;code&gt;brew install no-box-dev/noxkey/noxkey&lt;/code&gt; — &lt;a href="https://github.com/No-Box-Dev/Noxkey" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://noxkey.ai" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>macos</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
