<?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: Safayet Hossain</title>
    <description>The latest articles on DEV Community by Safayet Hossain (@safayet404).</description>
    <link>https://dev.to/safayet404</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4161285%2Fdf1ed9aa-80d5-44e2-8c10-04f8ee9a347a.jpg</url>
      <title>DEV Community: Safayet Hossain</title>
      <link>https://dev.to/safayet404</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/safayet404"/>
    <language>en</language>
    <item>
      <title>I leaked my MongoDB password, so I built a tool that rotates it with zero downtime</title>
      <dc:creator>Safayet Hossain</dc:creator>
      <pubDate>Sun, 04 Oct 2026 09:34:09 +0000</pubDate>
      <link>https://dev.to/safayet404/i-leaked-my-mongodb-password-so-i-built-a-tool-that-rotates-it-with-zero-downtime-3amh</link>
      <guid>https://dev.to/safayet404/i-leaked-my-mongodb-password-so-i-built-a-tool-that-rotates-it-with-zero-downtime-3amh</guid>
      <description>&lt;p&gt;I pushed a &lt;code&gt;.env&lt;/code&gt; file to a public GitHub repo. It held the MongoDB password and the JWT secrets of Collabify, a real-time Kanban app I run on Vercel.&lt;/p&gt;

&lt;p&gt;Deleting the file doesn't help: it stays in the git history, and bots scrape new commits for credentials within minutes. The only real fix is to &lt;strong&gt;rotate&lt;/strong&gt;: replace each secret so the leaked one stops working.&lt;/p&gt;

&lt;p&gt;Rotating by hand is where people take their own app down. This post is about the order that avoids that, and the tool I built to follow it every time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsafayet404%2Fleakfix%2Fmain%2Fdocs%2Fdemo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsafayet404%2Fleakfix%2Fmain%2Fdocs%2Fdemo.gif" alt="leakfix rotating secrets: the first deploy fails and everything is rolled back, the second run succeeds" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The order that keeps the app up
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. prepare   create a NEW credential next to the leaked one (both work)
2. switch    put the new value in the deployment's environment variables
3. redeploy  one production redeploy for all changed variables
4. verify    call a health route that fails if the database is unreachable
5. revoke    delete the leaked credential
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two rules make it safe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Until step 5, everything can be undone.&lt;/strong&gt; If any step fails, the completed steps are undone in reverse: variables get their old values back, production is redeployed on the old config, and the new credential is deleted. The app keeps running on the old credential the whole time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Once step 5 starts, never roll back.&lt;/strong&gt; Rolling back would mean pointing production at the leaked credential again. If revoking fails, the tool says what to finish by hand instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I found the second rule the honest way: a test where the revoke step failed, and my first version "helpfully" rolled production back onto the leaked password.&lt;/p&gt;

&lt;p&gt;For MongoDB Atlas, step 1 means a new database user with the same roles (&lt;code&gt;app&lt;/code&gt; → &lt;code&gt;app-lf20261001&lt;/code&gt;), created through a service account. For a JWT secret, it's a new random value; there's nothing to revoke, because tokens signed with the old secret simply stop verifying (users sign in again).&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing against fake clouds
&lt;/h3&gt;

&lt;p&gt;You can't run hundreds of rotations against real Atlas and Vercel accounts. So the tests run against an in-memory imitation of each API leakfix calls: Atlas database users, Vercel env vars and deployments, Render services, and a health endpoint. Each fake can be told to fail: the next deploy errors, the health check returns 503, Atlas blocks the caller's IP.&lt;/p&gt;

&lt;p&gt;That makes failure paths ordinary test cases:&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="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rolls back and redeploys the old config when the health check fails&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetchImpl&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;healthy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;buildPlans&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;leaked&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetchImpl&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;plans&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rolledBack&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MONGODB_URI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LEAKED_URI&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// old value back&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;atlasUsers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;collabify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;              &lt;span class="c1"&gt;// old user still works&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are 22 of these now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Then reality
&lt;/h3&gt;

&lt;p&gt;Green tests didn't mean it worked. The first real run against my production app failed twice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Atlas returned 403.&lt;/strong&gt; My ISP had moved me from one IP to the next, and the service account only allowed the old one. leakfix stopped at step 1 and changed nothing. (Now &lt;code&gt;leakfix init&lt;/code&gt; suggests allowing the &lt;code&gt;/24&lt;/code&gt; range.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel's redeploy ended with &lt;code&gt;git_info_fail&lt;/code&gt;.&lt;/strong&gt; I had redeployed with only the old deployment's id; for a Git-connected project, Vercel needs the Git source (repo id and commit). leakfix had already switched three variables and created a new Atlas user. It put all three values back, deleted the new user, and production never noticed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After fixing both, the third run rotated everything with zero downtime. My fake Vercel now fails with &lt;code&gt;git_info_fail&lt;/code&gt; unless the request names the commit, so that bug can't come back.&lt;/p&gt;

&lt;p&gt;I later added Render and verified it on a live service, including a rollback: the app's &lt;code&gt;/health&lt;/code&gt; route prints a short hash of the current secret, so you can watch the new value arrive, and then watch the old one come back when the rotation is rolled back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx leakfix scan        &lt;span class="c"&gt;# which secrets are committed?&lt;/span&gt;
npx leakfix init        &lt;span class="c"&gt;# finds your Vercel/Render project and Atlas project, checks access&lt;/span&gt;
npx leakfix rotate      &lt;span class="c"&gt;# shows the plan; add --yes to run it&lt;/span&gt;
npx leakfix fix-repo    &lt;span class="c"&gt;# stops tracking .env, adds .gitignore entries and .env.example&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also runs as a GitHub Action that fails a pull request containing a secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;safayet404/leakfix@v0.2.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What it isn't:&lt;/strong&gt; a replacement for GitHub secret scanning or GitGuardian (they find leaks better), or for Vault, Doppler or Infisical (if your secrets already live there, use their rotation). leakfix is for the common case in between: secrets in &lt;code&gt;.env&lt;/code&gt; and platform environment variables, and an alert you need to act on now.&lt;/p&gt;

&lt;p&gt;Today it covers MongoDB Atlas, JWT secrets, Vercel and Render. Railway, Stripe keys and a GitHub App are next. Adding a platform is one file, and the &lt;a href="https://github.com/safayet404/leakfix/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;contributing guide&lt;/a&gt; walks through it.&lt;/p&gt;

&lt;p&gt;Code: &lt;a href="https://github.com/safayet404/leakfix" rel="noopener noreferrer"&gt;https://github.com/safayet404/leakfix&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What does your stack look like? That decides what I build next.&lt;/p&gt;

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