<?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: piman</title>
    <description>The latest articles on DEV Community by piman (@pmaind).</description>
    <link>https://dev.to/pmaind</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%2F3848653%2F9e54fac7-a8ef-4ec8-a028-708255ee5254.png</url>
      <title>DEV Community: piman</title>
      <link>https://dev.to/pmaind</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pmaind"/>
    <language>en</language>
    <item>
      <title>I built a pre-push git hook that catches leaked secrets before they hit GitHub</title>
      <dc:creator>piman</dc:creator>
      <pubDate>Sun, 29 Mar 2026 04:46:40 +0000</pubDate>
      <link>https://dev.to/pmaind/i-built-a-pre-push-git-hook-that-catches-leaked-secrets-before-they-hit-github-2n08</link>
      <guid>https://dev.to/pmaind/i-built-a-pre-push-git-hook-that-catches-leaked-secrets-before-they-hit-github-2n08</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git push origin main
&lt;span class="c"&gt;# A few seconds later, email from AWS&lt;/span&gt;
&lt;span class="c"&gt;# Next morning: $8,000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leaked API keys are a solved problem — in theory. In practice, they keep happening. GitHub says exposed credentials are scraped by bots within seconds of being pushed.&lt;/p&gt;

&lt;p&gt;The usual advice is "use a secrets manager" or "scan your repo with a tool." But the best time to catch a leaked secret is &lt;em&gt;before&lt;/em&gt; it ever leaves your machine.&lt;/p&gt;

&lt;p&gt;That's why I built &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/push-sentinel" rel="noopener noreferrer"&gt;push-sentinel&lt;/a&gt;&lt;/strong&gt; — a zero-dependency CLI that sits in your &lt;code&gt;pre-push&lt;/code&gt; hook and scans the exact diff being pushed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Install in one command
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;--yes&lt;/span&gt; &lt;span class="nt"&gt;--prefer-online&lt;/span&gt; push-sentinel@latest &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. From now on, every &lt;code&gt;git push&lt;/code&gt; runs the scan automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;

&lt;p&gt;Clean push:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[push-sentinel] ✓ No secrets detected.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When something is found:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[push-sentinel] ⚠ Potential secrets found:

  [HIGH] src/config.ts:12
  AKIAIO...
  → Risk: Full access to AWS resources. Attacker can create/delete
           instances, incur charges, or exfiltrate data.
  → To ignore this line: push-sentinel ignore src/config.ts:12

  Push continues. Double-check before sharing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default it warns and lets the push through — no friction, no temptation to reach for &lt;code&gt;--no-verify&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it detects
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Private Key (RSA, EC, OPENSSH, DSA, PKCS#8)&lt;/td&gt;
&lt;td&gt;🔴 HIGH&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Access Key (&lt;code&gt;AKIA...&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;🔴 HIGH&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Secret Key (variable name + entropy)&lt;/td&gt;
&lt;td&gt;🔴 HIGH&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Token (&lt;code&gt;ghp_&lt;/code&gt;, &lt;code&gt;github_pat_&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;🔴 HIGH&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic API Key (&lt;code&gt;sk-ant-...&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;🟡 MEDIUM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI API Key (&lt;code&gt;sk-...&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;🟡 MEDIUM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generic API Key (variable name + high entropy)&lt;/td&gt;
&lt;td&gt;🟢 LOW&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;.env&lt;/code&gt; file committed&lt;/td&gt;
&lt;td&gt;🟡 MEDIUM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The entropy check on generic keys keeps false positives low — short or repetitive strings are skipped even if the variable name matches.&lt;/p&gt;




&lt;h2&gt;
  
  
  False positives? One command to ignore
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;push-sentinel ignore src/config.ts:12          &lt;span class="c"&gt;# ignore a specific line&lt;/span&gt;
push-sentinel ignore &lt;span class="nt"&gt;--pattern&lt;/span&gt; OPENAI_API_KEY  &lt;span class="c"&gt;# ignore a pattern everywhere&lt;/span&gt;
push-sentinel ignore &lt;span class="nt"&gt;--list&lt;/span&gt;                    &lt;span class="c"&gt;# see all rules&lt;/span&gt;
push-sentinel ignore &lt;span class="nt"&gt;--remove&lt;/span&gt; OPENAI_API_KEY   &lt;span class="c"&gt;# remove a rule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rules go into &lt;code&gt;.push-sentinel-ignore&lt;/code&gt; at your repo root.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why warning-only by default?
&lt;/h2&gt;

&lt;p&gt;Hard blocks feel safer, but they train people to use &lt;code&gt;--no-verify&lt;/code&gt;. A warning at push time is early enough to catch real accidents, and people actually leave it installed.&lt;/p&gt;

&lt;p&gt;If you want hard blocking for HIGH findings:&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;# In .git/hooks/pre-push, change the scan line to:&lt;/span&gt;
npx &lt;span class="nt"&gt;--yes&lt;/span&gt; &lt;span class="nt"&gt;--prefer-online&lt;/span&gt; push-sentinel@latest scan &lt;span class="nt"&gt;--local-sha&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$local_sha&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--remote-sha&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$remote_sha&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--block-on-high&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Team or org-wide? Use the GitHub Action
&lt;/h2&gt;

&lt;p&gt;For enforcing secret scanning across PRs without relying on every developer having the hook installed, there's also a &lt;a href="https://github.com/Pmaind/push-sentinel-action" rel="noopener noreferrer"&gt;GitHub Action&lt;/a&gt;:&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;Pmaind/push-sentinel-action@v1&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It blocks merges on HIGH findings and posts a PR comment with a findings table.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it works under the hood
&lt;/h2&gt;

&lt;p&gt;Git passes pushed ref info to the pre-push hook via stdin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;local-ref&amp;gt; &amp;lt;local-sha&amp;gt; &amp;lt;remote-ref&amp;gt; &amp;lt;remote-sha&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;push-sentinel uses those SHAs to compute the exact range of commits being pushed (&lt;code&gt;git log remoteSha..localSha -p&lt;/code&gt;), so it only scans what's actually new. It handles edge cases too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New branch&lt;/strong&gt; (no remote SHA): scans commits not yet reachable from any remote&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ref deletion&lt;/strong&gt; (zero SHA for local): skips the scan, nothing is being pushed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual scan&lt;/strong&gt; (no SHAs): falls back through &lt;code&gt;@{u}..HEAD&lt;/code&gt; → staged → working tree → last commit&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Zero dependencies
&lt;/h2&gt;

&lt;p&gt;The entire tool uses only Node.js stdlib — no &lt;code&gt;node_modules&lt;/code&gt;, no supply chain risk. Node.js &amp;gt;= 16 required.&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/push-sentinel" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/push-sentinel&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/Pmaind/pre-push-secrets" rel="noopener noreferrer"&gt;https://github.com/Pmaind/pre-push-secrets&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Action: &lt;a href="https://github.com/Pmaind/push-sentinel-action" rel="noopener noreferrer"&gt;https://github.com/Pmaind/push-sentinel-action&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feedback, false positive reports, and PRs welcome.&lt;/p&gt;

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