<?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: KeyVault Edge</title>
    <description>The latest articles on DEV Community by KeyVault Edge (@keyvault_edge).</description>
    <link>https://dev.to/keyvault_edge</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%2F4163606%2F7c31fed2-8c8c-4c5c-93b1-c696963ab1f4.png</url>
      <title>DEV Community: KeyVault Edge</title>
      <link>https://dev.to/keyvault_edge</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/keyvault_edge"/>
    <language>en</language>
    <item>
      <title>Your VITE_ API key is public. Here's what happens next, and three ways to fix it</title>
      <dc:creator>KeyVault Edge</dc:creator>
      <pubDate>Fri, 23 Oct 2026 12:32:43 +0000</pubDate>
      <link>https://dev.to/keyvault_edge/your-vite-api-key-is-public-heres-what-happens-next-and-three-ways-to-fix-it-2p1b</link>
      <guid>https://dev.to/keyvault_edge/your-vite-api-key-is-public-heres-what-happens-next-and-three-ways-to-fix-it-2p1b</guid>
      <description>&lt;p&gt;If your frontend reads an AI or payments key from &lt;code&gt;import.meta.env.VITE_SOMETHING&lt;/code&gt;, that key is in the JavaScript bundle every visitor downloads. Same for &lt;code&gt;NEXT_PUBLIC_*&lt;/code&gt; in Next.js and &lt;code&gt;REACT_APP_*&lt;/code&gt; in Create React App. Putting it in a &lt;code&gt;.env&lt;/code&gt; file changes where it's stored on your laptop, not where it ends up.&lt;/p&gt;

&lt;p&gt;You can check in ten seconds: open your deployed site, open DevTools → Sources, and search the bundle for your key's prefix (&lt;code&gt;sk-&lt;/code&gt;, &lt;code&gt;AIza&lt;/code&gt;, &lt;code&gt;sk_live_&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens after a key ships in a bundle
&lt;/h2&gt;

&lt;p&gt;Automated scanners crawl deployed sites and public repos for those prefixes. Developers have described the results publicly this year on Google's own forum, for example &lt;a href="https://discuss.ai.google.dev/t/6-000-in-unauthorized-gemini-api-charges-from-a-vite-built-client-bundle-requesting-billing-review/142877" rel="noopener noreferrer"&gt;about $6,000 in roughly 40 minutes&lt;/a&gt; from an AI Studio sample exported to Vite, and &lt;a href="https://discuss.ai.google.dev/t/urgent-2200-in-unauthorized-gemini-api-charges-from-leaked-client-side-key-requesting-refund/141634" rel="noopener noreferrer"&gt;about $2,200&lt;/a&gt; from a &lt;code&gt;VITE_GEMINI_API_KEY&lt;/code&gt; used as a shared fallback.&lt;/p&gt;

&lt;p&gt;Providers have added protections (Google now rejects unrestricted Gemini keys and offers project spend caps; OpenAI has project limits), but those work per project or account, not per key, and they don't stop the key from being copied.&lt;/p&gt;

&lt;h2&gt;
  
  
  If it already leaked: the first hour
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Revoke or regenerate the key in the provider console. Don't wait to check whether it was used.&lt;/li&gt;
&lt;li&gt;Check usage for the window since the deploy: unfamiliar models, regions or volume.&lt;/li&gt;
&lt;li&gt;Restrict every remaining key to only the APIs it needs, and set a budget alert.&lt;/li&gt;
&lt;li&gt;Remove the key from the bundle (below) before you deploy again.&lt;/li&gt;
&lt;li&gt;If it was ever committed, it's also in git history. Rewrite history or make the repo private, and turn on GitHub push protection.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Three ways to keep the key out of the bundle
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. A small backend endpoint.&lt;/strong&gt; A serverless or edge function holds the key as a secret; your frontend calls the function. Free, and the right answer if you'll also add rate limiting and per-user budgets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The provider's own client-side option, if it has one.&lt;/strong&gt; For example, Firebase App Check with Firebase AI Logic for Gemini on mobile and web.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. An edge proxy with per-key limits.&lt;/strong&gt; You store the real key once and your app sends a stand-in token instead. The proxy adds the real key and forwards the call, and each token carries its own rate limit, allowed endpoints, spend cap and an off switch. I build one of these, &lt;a href="https://keyvaultedge.com/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=vite-key-public" rel="noopener noreferrer"&gt;KeyVault Edge&lt;/a&gt;. You can watch it allow one request and block two misuse attempts on a &lt;a href="https://keyvaultedge.com/demo?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=vite-key-public" rel="noopener noreferrer"&gt;live demo, no signup&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One honest limit for option 3 (and for any token a browser can see): the token can still be copied and replayed from a server. The point is that a leaked token has hard limits and can be revoked in one click without rotating the provider key, so the damage is bounded.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] No provider key in any &lt;code&gt;VITE_&lt;/code&gt;, &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; or &lt;code&gt;REACT_APP_&lt;/code&gt; variable&lt;/li&gt;
&lt;li&gt;[ ] Bundle searched for key prefixes after every deploy&lt;/li&gt;
&lt;li&gt;[ ] Each key restricted to the APIs it needs&lt;/li&gt;
&lt;li&gt;[ ] Budget alert and spend limit set at the provider&lt;/li&gt;
&lt;li&gt;[ ] Push protection on for the repo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How did your key end up in the frontend? I'd like to hear what made it the easiest option at the time.&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>ai</category>
    </item>
    <item>
      <title>How to audit your codebase for exposed API keys (free tools)</title>
      <dc:creator>KeyVault Edge</dc:creator>
      <pubDate>Mon, 05 Oct 2026 11:13:20 +0000</pubDate>
      <link>https://dev.to/keyvault_edge/how-to-audit-your-codebase-for-exposed-api-keys-free-tools-27po</link>
      <guid>https://dev.to/keyvault_edge/how-to-audit-your-codebase-for-exposed-api-keys-free-tools-27po</guid>
      <description>&lt;p&gt;Accidental credential commits are often found weeks or months after they happen, long after anyone who was watching had time to use them. This guide walks through a systematic audit of your repositories, git history, Docker images and CI pipelines using free, open-source tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you're scanning and why
&lt;/h2&gt;

&lt;p&gt;A credential audit is not just scanning your current working tree. Secrets committed to git are &lt;strong&gt;permanent in history&lt;/strong&gt; unless you rewrite it. Deleting a file does not remove the secret: the commit with the original file is still reachable via &lt;code&gt;git log&lt;/code&gt; and &lt;code&gt;git show&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A proper audit covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full git history&lt;/strong&gt;: every commit, including deleted files and reverted changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All branches and tags&lt;/strong&gt;: feature branches often hold secrets that never reached main&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker image layers&lt;/strong&gt;: build-time &lt;code&gt;ARG&lt;/code&gt; and &lt;code&gt;ENV&lt;/code&gt; instructions that baked secrets in&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD configs&lt;/strong&gt;: &lt;code&gt;.github/workflows&lt;/code&gt;, &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;, and environment variables echoed into logs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lock files&lt;/strong&gt;: rarely, npm/yarn lockfiles contain registry auth tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build artifacts&lt;/strong&gt;: compiled frontends that embedded env vars at build time&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Scanning git history with Gitleaks
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/gitleaks/gitleaks" rel="noopener noreferrer"&gt;Gitleaks&lt;/a&gt; (MIT) is one of the most widely used open-source secret scanners, with built-in rules for OpenAI, AWS, Stripe, GitHub, Twilio and most major providers.&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;# Install (macOS)&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;gitleaks

&lt;span class="c"&gt;# Install (Linux)&lt;/span&gt;
curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks_linux_x64.tar.gz | &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xz&lt;/span&gt;
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;gitleaks /usr/local/bin/

&lt;span class="c"&gt;# Scan full git history of the current repo&lt;/span&gt;
gitleaks detect &lt;span class="nt"&gt;--source&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--verbose&lt;/span&gt;

&lt;span class="c"&gt;# Write a JSON report&lt;/span&gt;
gitleaks detect &lt;span class="nt"&gt;--source&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--report-path&lt;/span&gt; gitleaks-report.json &lt;span class="nt"&gt;--report-format&lt;/span&gt; json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;detect&lt;/code&gt; scans every commit. On large repos it can take a few minutes; &lt;code&gt;--log-opts&lt;/code&gt; accepts any &lt;code&gt;git log&lt;/code&gt; options, e.g. &lt;code&gt;--log-opts="--since=2025-01-01"&lt;/code&gt; to scan recent history only.&lt;/p&gt;

&lt;p&gt;Add it as a pre-commit hook so new secrets never land:&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="c1"&gt;# .pre-commit-config.yaml&lt;/span&gt;
&lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/gitleaks/gitleaks&lt;/span&gt;
    &lt;span class="na"&gt;rev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v8.21.0&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitleaks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pre-commit &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deep scanning with TruffleHog
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/trufflesecurity/trufflehog" rel="noopener noreferrer"&gt;TruffleHog&lt;/a&gt; (AGPL-3.0) goes further by &lt;em&gt;verifying&lt;/em&gt; detected credentials against their upstream APIs: it tells you not just that a string looks like an OpenAI key, but whether that key is currently valid.&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;# Scan local git history, verified findings only&lt;/span&gt;
trufflehog git file://. &lt;span class="nt"&gt;--only-verified&lt;/span&gt;

&lt;span class="c"&gt;# Scan a public GitHub repo&lt;/span&gt;
trufflehog github &lt;span class="nt"&gt;--repo&lt;/span&gt; https://github.com/yourusername/yourrepo

&lt;span class="c"&gt;# Scan every repo in an org&lt;/span&gt;
trufflehog github &lt;span class="nt"&gt;--org&lt;/span&gt; yourorgname &lt;span class="nt"&gt;--only-verified&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--only-verified&lt;/code&gt; is great for triage: it filters out false positives and surfaces credentials that are exploitable right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scanning Docker images
&lt;/h2&gt;

&lt;p&gt;Images built with &lt;code&gt;ARG&lt;/code&gt; or &lt;code&gt;ENV&lt;/code&gt; instructions that reference real keys bake those keys into a layer permanently. A later instruction that overwrites the variable doesn't help: the earlier layer is still in the image.&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;# Scan a local or registry image&lt;/span&gt;
trufflehog docker &lt;span class="nt"&gt;--image&lt;/span&gt; yourimage:latest

&lt;span class="c"&gt;# Quick manual check of every layer&lt;/span&gt;
docker save yourimage:latest | &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xO&lt;/span&gt; | strings | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'sk-proj|AKIA|ghp_'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The classic mistake:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Bakes the real key into an image layer: don't&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; OPENAI_API_KEY&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; OPENAI_API_KEY=$OPENAI_API_KEY&lt;/span&gt;
&lt;span class="c"&gt;# Inject at runtime instead (docker run -e, or your orchestrator's secrets)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Auditing CI/CD pipelines
&lt;/h2&gt;

&lt;p&gt;Build logs are often public, especially in open source. Look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;env:&lt;/code&gt; blocks with literal values (&lt;code&gt;OPENAI_KEY: sk-proj-...&lt;/code&gt;). References like &lt;code&gt;${{ secrets.X }}&lt;/code&gt; are fine.&lt;/li&gt;
&lt;li&gt;Steps that print the environment: &lt;code&gt;run: echo $OPENAI_API_KEY&lt;/code&gt;, &lt;code&gt;env&lt;/code&gt;, &lt;code&gt;printenv&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Test scripts that hardcode keys "for easier local testing".&lt;/li&gt;
&lt;li&gt;Cached artifacts that include environment dumps from crash reporters.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  GitHub's built-in scanning
&lt;/h2&gt;

&lt;p&gt;GitHub secret scanning runs on public repositories and can be enabled for private ones on paid plans. For partner patterns (OpenAI, Stripe, AWS and many more) GitHub also notifies the provider, which may revoke the key.&lt;/p&gt;

&lt;p&gt;Turn on &lt;strong&gt;push protection&lt;/strong&gt; under &lt;code&gt;Settings → Code security → Secret scanning&lt;/code&gt; to block commits containing known secret patterns before they land.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you find something
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rotate the credential immediately.&lt;/strong&gt; Don't wait for the history rewrite; assume it's compromised.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check for unauthorised usage&lt;/strong&gt; in the provider's logs (OpenAI usage page, AWS CloudTrail, Stripe logs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove it from history&lt;/strong&gt; with &lt;code&gt;git filter-repo&lt;/code&gt; (not &lt;code&gt;filter-branch&lt;/code&gt;), force-push everywhere, and have contributors re-clone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask GitHub support to clear cached views&lt;/strong&gt; of the old commits; rewriting history doesn't purge them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assume forks still have it.&lt;/strong&gt; You can't rewrite other people's forks, which is why step 1 comes first.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Making the next leak cheaper
&lt;/h2&gt;

&lt;p&gt;Scanning finds the leaks you can see. The other half is making a leaked string less valuable: scoped keys, spending limits, and short-lived or origin-bound tokens.&lt;/p&gt;

&lt;p&gt;That second half is what I'm building with &lt;a href="https://keyvaultedge.com" rel="noopener noreferrer"&gt;KeyVault Edge&lt;/a&gt;: your code ships a token that the edge proxy only exchanges for the real key on requests from origins you allow. It's one layer, not a replacement for rotation and scanning.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://keyvaultedge.com/resources/audit-codebase-for-secrets" rel="noopener noreferrer"&gt;keyvaultedge.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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