<?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: Tony</title>
    <description>The latest articles on DEV Community by Tony (@21ideas).</description>
    <link>https://dev.to/21ideas</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%2F3888795%2F310a48ae-9cbb-49be-8d0d-878d4ffbfcef.png</url>
      <title>DEV Community: Tony</title>
      <link>https://dev.to/21ideas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/21ideas"/>
    <language>en</language>
    <item>
      <title>Secret Management for Vibe Coders: The System I Wish I Had a Year Ago</title>
      <dc:creator>Tony</dc:creator>
      <pubDate>Mon, 20 Apr 2026 21:31:40 +0000</pubDate>
      <link>https://dev.to/21ideas/secret-management-for-vibe-coders-the-system-i-wish-i-had-a-year-ago-531o</link>
      <guid>https://dev.to/21ideas/secret-management-for-vibe-coders-the-system-i-wish-i-had-a-year-ago-531o</guid>
      <description>&lt;p&gt;Yesterday Vercel &lt;a href="https://x.com/vercel/status/2045865072074035664?s=20" rel="noopener noreferrer"&gt;announced a security incident&lt;/a&gt;. This morning I sat down with my project and realized I didn't have a clear system for what to do next.&lt;/p&gt;

&lt;p&gt;I knew I was supposed to "rotate my keys." I'd done it before — because an AI told me to. But I'd never really understood &lt;em&gt;why&lt;/em&gt;, in what order, or whether I'd been storing secrets properly this whole time.&lt;/p&gt;

&lt;p&gt;I've been building with AI tools for just over a year. No CS degree, no coding books, no deep knowledge of any single language. Everything I know came from prompting Claude, Gemini, Cursor, and building through practice. &lt;a href="https://github.com/bitcoin21ideas" rel="noopener noreferrer"&gt;I've shipped real things&lt;/a&gt;. But secret management was always something I followed by instinct rather than actual understanding.&lt;/p&gt;

&lt;p&gt;Today I fixed that. Here's the full picture — plain English, no fluff — so you can skip straight to the good system.&lt;/p&gt;

&lt;h2&gt;
  
  
  First: Not Everything in Your .env File Is a Secret
&lt;/h2&gt;

&lt;p&gt;Worth getting clear on this because most tutorials treat the whole &lt;code&gt;.env&lt;/code&gt; file as one scary blob. There are actually three distinct types of values:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real secrets&lt;/strong&gt; — if someone gets these, they can impersonate your app, rack up your API bills, or read your users' data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database passwords&lt;/li&gt;
&lt;li&gt;API keys (your AI provider, payment processor, etc.)&lt;/li&gt;
&lt;li&gt;The key your app uses to sign user sessions&lt;/li&gt;
&lt;li&gt;Anything called a "signing key" or "encryption key"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Config values&lt;/strong&gt; — totally safe to share, they just control how your app behaves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A model name like &lt;code&gt;GEMINI_MODEL=gemini-3.0-flash&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Feature flags, version numbers, non-sensitive settings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Public values&lt;/strong&gt; — stuff you &lt;em&gt;intentionally&lt;/em&gt; expose to the browser. In Next.js these start with &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt;. Don't put anything sensitive here — it ends up in your frontend JavaScript where anyone can read it.&lt;/p&gt;

&lt;p&gt;Only the first category needs serious handling. The rest is just configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three-Layer Stack
&lt;/h2&gt;

&lt;p&gt;Here's the mental model that makes everything else make sense:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqjrfrkkyqz9z8z4z4qz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqjrfrkkyqz9z8z4z4qz.png" alt="Secrets stack" width="800" height="725"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One source of truth at the top — everything else is just a copy of it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 1 — Your password manager&lt;/strong&gt; is the boss. Every secret lives here with notes on what it is and when you last rotated it. I use Bitwarden (free, open source), but 1Password and Dashlane are equally solid. One folder per project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 2 — Your hosting platform's env vars&lt;/strong&gt; (Vercel, Railway, Render, Fly.io — they all have this). This is what your live app actually reads. Your code never touches these directly — it just calls &lt;code&gt;process.env.YOUR_VARIABLE_NAME&lt;/code&gt; and the platform fills it in automatically at runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 3 — &lt;code&gt;.env.local&lt;/code&gt;&lt;/strong&gt; on your laptop. A local copy for development only. It's gitignored — git completely ignores it, it never gets committed. You sync it from your hosting platform using their CLI.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; update the password manager first, then update the copies. That way there's always one place to go if something goes wrong.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The One File That Prevents Most Mistakes: &lt;code&gt;.env.example&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is the habit that pays off on every project.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.env.example&lt;/code&gt; is your &lt;code&gt;.env.local&lt;/code&gt; with all real values removed — just the variable names and a comment about what each one is. You commit this to git. You share it with collaborators. You paste it into AI sessions.&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;# .env.example&lt;/span&gt;
&lt;span class="c"&gt;# Copy this to .env.local and fill in real values from your password manager.&lt;/span&gt;
&lt;span class="c"&gt;# NEVER commit .env.local. NEVER paste real values into AI sessions.&lt;/span&gt;

&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;          &lt;span class="c"&gt;# Your database connection string&lt;/span&gt;
&lt;span class="nv"&gt;AUTH_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;           &lt;span class="c"&gt;# Run: openssl rand -base64 32&lt;/span&gt;
&lt;span class="nv"&gt;GEMINI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;        &lt;span class="c"&gt;# From Google AI Studio&lt;/span&gt;
&lt;span class="nv"&gt;STRIPE_SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;     &lt;span class="c"&gt;# From your Stripe dashboard&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variable names, no values, helpful comments. Safe to share anywhere.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Start every new project by creating this file before you write a single line of code.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Keeping Secrets Out of AI Sessions
&lt;/h2&gt;

&lt;p&gt;Here's something I didn't think about carefully enough until today — and I suspect most AI builders haven't either.&lt;/p&gt;

&lt;p&gt;AI coding tools like Cursor and Claude Code index your codebase as context. If &lt;code&gt;.env.local&lt;/code&gt; is open in a tab, those real values can end up in the AI's context window. That's not how you want your database password or Stripe key traveling around.&lt;/p&gt;

&lt;p&gt;Here's the before/after on what I changed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyox6zlych9nnu41c6g2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyox6zlych9nnu41c6g2.png" alt="setup checklist" width="800" height="651"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The AI only needs variable names to write correct code — it never needs the actual values.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three rules that make this a non-issue:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Never open &lt;code&gt;.env.local&lt;/code&gt; during an AI session.&lt;/strong&gt; You don't need to. Manage values in your hosting dashboard, sync locally via CLI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add &lt;code&gt;.env.local&lt;/code&gt; to your AI's ignore file.&lt;/strong&gt; Create a &lt;code&gt;.cursorignore&lt;/code&gt; (and/or &lt;code&gt;.claudeignore&lt;/code&gt; for Claude Code) in your project root:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.env.local
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The AI indexer will never touch that file even if you accidentally open it.&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start sessions by pasting &lt;code&gt;.env.example&lt;/code&gt;.&lt;/strong&gt; Just drop it in with a note: "here are my variable names, use &lt;code&gt;process.env.VARIABLE_NAME&lt;/code&gt; in any code you write." The AI produces perfectly correct code and never needs to know a single real value.  &lt;/p&gt;&lt;/li&gt;

&lt;/ol&gt;




&lt;h2&gt;
  
  
  What "Rotating Keys" Actually Means
&lt;/h2&gt;

&lt;p&gt;Rotating means: generate a new secret at the provider, update it everywhere, revoke the old one. That's it.&lt;/p&gt;

&lt;p&gt;You do it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A provider announces a security incident (hi, Vercel)&lt;/li&gt;
&lt;li&gt;Someone who had access leaves your team&lt;/li&gt;
&lt;li&gt;You want a regular security sweep (once a year is fine for small projects)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The same five steps work for every secret:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate a new value at the provider's dashboard&lt;/li&gt;
&lt;li&gt;Update your password manager first&lt;/li&gt;
&lt;li&gt;Update your hosting platform's env vars&lt;/li&gt;
&lt;li&gt;Redeploy&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;vercel env pull .env.local&lt;/code&gt; (or your platform's equivalent) to sync locally&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Rotate in this order&lt;/strong&gt; — highest damage potential first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Database password (full data access if leaked)&lt;/li&gt;
&lt;li&gt;Session/auth secrets (user account hijacking)&lt;/li&gt;
&lt;li&gt;Encryption keys ← &lt;em&gt;read the warning below before touching these&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;OAuth app secrets (account impersonation)&lt;/li&gt;
&lt;li&gt;Everything else (API keys, billing abuse)&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Encryption key warning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're encrypting data in your database, you can't just swap the key — existing encrypted records become unreadable. The right move: version your keys (&lt;code&gt;ENCRYPTION_KEY_V1&lt;/code&gt;, &lt;code&gt;ENCRYPTION_KEY_V2&lt;/code&gt;), keep the old one available for decryption, and write a migration script to re-encrypt all records with the new key before retiring the old one. If you haven't encrypted any production data yet, rotate freely — no migration needed.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  How OAuth Actually Works
&lt;/h2&gt;

&lt;p&gt;This is the part I'd been fuzzy on: "Google handles security for me, right?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not quite&lt;/strong&gt;. Here's the actual picture:&lt;/p&gt;

&lt;p&gt;Your app has its own permanent identity with Google — a Client ID and Client Secret that live in your env vars just like any other API key. They prove to Google that requests are coming from &lt;em&gt;your&lt;/em&gt; app.&lt;/p&gt;

&lt;p&gt;Then, when a user connects their account ("Sign in with Google"), Google issues two tokens that &lt;em&gt;you&lt;/em&gt; store in &lt;em&gt;your&lt;/em&gt; database:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A short-lived access token (expires in hours)&lt;/li&gt;
&lt;li&gt;A long-lived refresh token (valid until the user revokes it)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Google stores nothing on your behalf. You're responsible for those tokens in your database, which means your database security matters a lot for OAuth too.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Setting Up a New Project the Right Way
&lt;/h2&gt;

&lt;p&gt;Four steps, five minutes, done:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffz4t2zhushfdgxsz6ab3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffz4t2zhushfdgxsz6ab3.png" alt="Setup" width="800" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And every time you add a new secret going forward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create the credential at the provider&lt;/li&gt;
&lt;li&gt;Add it to your password manager &lt;strong&gt;first&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Add it to your hosting platform's env vars&lt;/li&gt;
&lt;li&gt;Sync locally with the CLI&lt;/li&gt;
&lt;li&gt;Add the variable name (no value) to &lt;code&gt;.env.example&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two minutes per secret. Scales cleanly as your project grows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optional Nerd Check: Did Any Secrets Ever Leak Into Git?
&lt;/h2&gt;

&lt;p&gt;If you've accidentally committed an &lt;code&gt;.env&lt;/code&gt; file, you'd probably remember — it tends to be a memorable moment. But if you want to be certain, run these in your project root:&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;# Was any .env file ever committed?&lt;/span&gt;
git log &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;--full-history&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s2"&gt;".env*"&lt;/span&gt;

&lt;span class="c"&gt;# Search history for anything that looks like a real secret value&lt;/span&gt;
git log &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"(API_KEY|SECRET|PASSWORD)&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s2"&gt;*=&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s2"&gt;*['&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]?[a-zA-Z0-9]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing returned — you're clean.&lt;/p&gt;

&lt;p&gt;If something does come back: &lt;strong&gt;rotate the exposed secret first&lt;/strong&gt; (that's what actually protects you), then clean up the history with &lt;code&gt;git filter-repo&lt;/code&gt; and force push. Rotating is the urgent action. The cleanup is secondary hygiene.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Gist
&lt;/h2&gt;

&lt;p&gt;Every secret your app uses is a credential that proves your app's identity to another service. Treat it like a password: store it in a password manager, keep it out of your codebase, rotate it after incidents, and don't hand it to an AI assistant.&lt;/p&gt;

&lt;p&gt;The system is four things: &lt;strong&gt;password manager&lt;/strong&gt; (source of truth) → &lt;strong&gt;hosting platform&lt;/strong&gt; (production copy) → &lt;strong&gt;&lt;code&gt;.env.local&lt;/code&gt;&lt;/strong&gt; (local copy) → &lt;strong&gt;&lt;code&gt;.env.example&lt;/code&gt;&lt;/strong&gt; (the safe version you share everywhere). Get these four right and you're doing secret management properly — the same way any experienced developer does it.&lt;/p&gt;

&lt;p&gt;No degree required. Just a bit of intentional setup at the start of each project. Now go create that &lt;code&gt;.env.example&lt;/code&gt;. &lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building something with AI tools? I'd love to hear what you're working on — drop it in the comments. I write about vibe coding, building in public, and the AI-first dev workflow.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
