How to Fix Bolt.new API Key Exposure (2026)

A Bolt.new app with an exposed OpenAI key can drain your credits in hours. In our scan of 500 Bolt.new projects, 67% had at least one critical security issue, and exposed API keys were the most common finding. The usual culprit: a variable named VITE_OPENAI_API_KEY that Vite silently baked into the client JavaScript bundle at build time.

This guide walks you through finding the leak, rotating the key, and fixing the root cause so it does not happen again.

TL;DR

Bolt.new API key exposure has two main causes: (1) secrets with a VITE_ prefix baked into the client bundle by Vite, and (2) Bolt generating code that uses your Supabase service_role key in frontend JavaScript. Rotate the exposed key immediately, then either remove the VITE_ prefix and move the call to a Netlify Function, or switch from service_role to the anon key with RLS policies. Do not delay rotation waiting to confirm abuse.

Step 1: Find the Exposed Key

Before rotating, confirm exactly what is exposed and how.

1

Check your JS bundle in the browser. Open your deployed Bolt app, press F12, go to Sources, and search for your key value or its known prefix: sk_proj_ (OpenAI), sk_live_ (Stripe), AKIA (AWS), or eyJhbGci (Supabase JWTs). If it appears inside a .js file served from your domain, it is in your client bundle and visible to any visitor.

2

Search your exported code for VITE_-prefixed variables.

# In your exported Bolt.new project
grep -r "VITE_" . --include="*.env*" --include="*.ts" --include="*.tsx" --include="*.js"

Any variable named VITE_OPENAI_KEY, VITE_ANTHROPIC_API_KEY, or VITE_STRIPE_SECRET is almost certainly in your bundle. The VITE_ prefix is Bolt's way of making values available to client components, but it also exposes them to every visitor.

3

Check for hardcoded Supabase service_role usage. Bolt sometimes generates a Supabase client using the service_role key directly in frontend code:

# Dangerous pattern to look for in your Bolt-generated code
grep -r "service_role" . --include="*.ts" --include="*.tsx" --include="*.js"
grep -r "SUPABASE_SERVICE_ROLE" . --include="*.ts" --include="*.tsx" --include="*.env*"

The anon key (VITE_SUPABASE_ANON_KEY) is designed to be public. The service_role key is not.

4

Check git history for committed .env files.

git log --all --full-history -- .env
git log --all --full-history -- .env.local
git log --all --full-history -- .env.production

If any of these return commits, the secrets were stored in your repo's history. Anyone who cloned or forked the repo has a copy.

CheckYourVibe scans your deployed Bolt app and flags API keys found in the JavaScript bundle, including OpenAI, Anthropic, Stripe, Supabase service-role, and AWS credentials. It reads what an attacker reads from your public bundle.

Step 2: Rotate the Exposed Key Immediately

Do not try to fix the code first. The key is already out. Rotate it now.

1

Generate a new key in the affected service's dashboard (OpenAI, Stripe, Supabase, etc.). Most services let you have multiple active keys so production stays live during the transition.

2

Update Netlify environment variables. In the Netlify dashboard, go to Site Settings > Environment Variables. Find the variable and update its value. Netlify will trigger a new deploy with the updated secret.

3

Redeploy. Netlify rebuilds automatically after an environment variable change. Wait for the new deploy to go live before revoking the old key.

4

Revoke the old key in the service dashboard. Delete or disable the compromised key. An old key left active is still usable by anyone who copied it.

5

Check usage logs. Review the affected service's usage dashboard for spikes during the exposure window. OpenAI shows per-key usage in the API dashboard. If you see unexpected requests, review which endpoints were called.

Do not delay revocation waiting to confirm abuse. API abuse can cost thousands of dollars within hours of exposure. Rotate first, investigate second.

Step 3: Remove from Git History (if committed)

If your audit found secrets in git history, delete them from that history. The file is visible in any clone even if you deleted it in a later commit.

# Install git-filter-repo
pip install git-filter-repo

# Remove .env from all history
git filter-repo --path .env --invert-paths
git filter-repo --path .env.local --invert-paths

After scrubbing, force-push to all remotes and notify any collaborators to re-clone. If the repo was public at any point, treat the key as compromised regardless of whether you can confirm it was accessed.

Add .env to your .gitignore if it is not already there:

echo ".env" >> .gitignore
echo ".env.local" >> .gitignore
echo ".env.*.local" >> .gitignore
git add .gitignore && git commit -m "chore: gitignore .env files"

Step 4: Fix the Root Cause (VITE_ Prefix)

Rotating fixes the immediate risk. Fixing the root cause stops it from happening again on the next deploy.

Why VITE_ Variables Are Dangerous

Vite embeds every variable prefixed with VITE_ into the client bundle at build time. The prefix exists specifically to share values with browser code. Using it with a secret is the wrong tool for the job.

Wrong (key ends up in every visitor's browser):

// Bolt-generated client component - leaks key to the browser
const client = new OpenAI({
  apiKey: import.meta.env.VITE_OPENAI_API_KEY,
  dangerouslyAllowBrowser: true, // another red flag
});

Right (key stays server-side via a Netlify Function):

// netlify/functions/chat.ts - server-side only
import { Handler } from "@netlify/functions";
import OpenAI from "openai";

const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

export const handler: Handler = async (event) => {
  const { message } = JSON.parse(event.body || "{}");
  const response = await client.chat.completions.create({
    model: "gpt-4o",
    messages: [{ role: "user", content: message }],
  });
  return {
    statusCode: 200,
    body: JSON.stringify({ reply: response.choices[0].message.content }),
  };
};

Your React component calls /.netlify/functions/chat on your own domain. Your Netlify Function calls OpenAI. The key never reaches the browser.

Variable Naming in Netlify

In Netlify's environment variables panel, name your secret without any public prefix:

WrongRight
VITE_OPENAI_API_KEYOPENAI_API_KEY
VITE_ANTHROPIC_API_KEYANTHROPIC_API_KEY
VITE_STRIPE_SECRET_KEYSTRIPE_SECRET_KEY

Then access it in your Netlify Function as process.env.OPENAI_API_KEY. Netlify injects it only at runtime, and only into server-side function code.

Step 5: Fix Supabase service_role Misuse

The Supabase service_role key bypasses every Row Level Security policy in your database. It is designed for backend admin operations only.

Bolt.new occasionally generates a Supabase client in frontend code that uses the service_role key. The result: any visitor who opens DevTools has full read and write access to your entire database.

Wrong (service_role in frontend):

// In a React component or browser-side hook
const supabase = createClient(
  import.meta.env.VITE_SUPABASE_URL,
  import.meta.env.VITE_SUPABASE_SERVICE_ROLE_KEY // never do this
);

Right (anon key client-side, service_role only in server functions):

// React component - uses anon key, controlled by RLS policies
const supabase = createClient(
  import.meta.env.VITE_SUPABASE_URL,
  import.meta.env.VITE_SUPABASE_ANON_KEY // public by design
);

// netlify/functions/admin-task.ts - service_role only here
const adminClient = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY! // server-only
);

The anon key is safe to expose because Supabase RLS policies control what it can access. Enable RLS on every table and add policies that match your auth model.

If you rotated your Supabase service_role key, go to the Supabase dashboard under Project Settings > API, generate a new service_role key, and update your Netlify environment variable. The old key will stop working for all legitimate admin functions too, so update your server-side code before revoking.

Step 6: Prevent Future Leaks

Pre-commit Hook with Gitleaks

Gitleaks scans for secrets before each commit:

# Install gitleaks (macOS)
brew install gitleaks

# Test your current repo
gitleaks detect --source . --verbose

# Add pre-commit hook
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/sh
gitleaks protect --staged --verbose
if [ $? -ne 0 ]; then
  echo "Gitleaks found potential secrets. Commit blocked."
  exit 1
fi
EOF
chmod +x .git/hooks/pre-commit

What to Put Where

Secret typeWhere it livesAccessed via
OpenAI / Anthropic keyNetlify environment variablesprocess.env.OPENAI_API_KEY in a Function
Stripe secret keyNetlify environment variablesprocess.env.STRIPE_SECRET_KEY in a Function
Supabase anon keyNetlify env vars as VITE_SUPABASE_ANON_KEYimport.meta.env.VITE_SUPABASE_ANON_KEY (public)
Supabase service_roleNetlify environment variables (no VITE_ prefix)process.env.SUPABASE_SERVICE_ROLE_KEY in a Function only
Database URLNetlify environment variables (no VITE_ prefix)process.env.DATABASE_URL in a Function only

How do I know if my Bolt.new API key is exposed?

Open your deployed Bolt app in the browser, press F12, go to Sources, and search for your key value or its prefix (sk_proj_, sk_live_, AKIA, or eyJhbGci for Supabase JWTs). If it appears in a .js file, it is in your client bundle. Also run grep -r 'VITE_' . in your exported Bolt.new code to catch prefix-exposed variables.

What is the Supabase service_role key and why is it dangerous?

The service_role key bypasses all Row Level Security policies, giving full read and write access to every table in your database. Bolt.new occasionally generates code that uses this key on the frontend. If a visitor opens DevTools and copies it, they have complete database access. Use the anon key client-side and add RLS policies instead.

Why does VITE_OPENAI_API_KEY expose my key even when set in Netlify?

Vite bakes any variable prefixed with VITE_ into your client-side JavaScript at build time. Even when stored safely in Netlify's environment variables panel, it ends up as plain text in your deployed .js files. Rename it OPENAI_API_KEY (no prefix) and access it only from a Netlify Function.

Do I need to rotate my key if I am not sure it was accessed?

Yes. Rotate it regardless. It takes under 5 minutes. The cost of not rotating a compromised key is far higher than updating a valid one. Check the service's usage dashboard for unexpected spikes in the period the key was live.

Does deleting the Netlify deploy fix the exposure?

No. If the key was in a public GitHub repo or cached by search engines or scanners, deleting the deploy does not remove those copies. Rotate the key first, then clean the root cause.

Check your Bolt.new deployment for API keys in your JavaScript bundle, Supabase service_role misuse, and security header gaps. Free scan, no signup required.

How-To Guides

How to Fix Bolt.new API Key Exposure (2026)