Supabase question
Is my SUPABASE_SERVICE_ROLE_KEY exposed in a public .env file?
Treat it as exposed if the value was ever in a file other people can read: a .env committed to a public GitHub repo, a .env your site serves at /.env, or a NEXT_PUBLIC_ or VITE_ variable that the build copied into the JavaScript sent to visitors. Supabase says a secret key "bypasses every Row Level Security policy you have", and lists the legacy service_role key as the older version of secret keys. Check the four places below. If any of them holds the key, fix the cause, create a new secret key in Settings, API Keys, set it everywhere the old one was, redeploy and retire the old key. Then clean git history and read your logs. Deleting the file does not undo the leak.
Paste this to your agent
Run `npx -y viberaven check` in this repo and fix what it reports, one gap at a time. Findings about Supabase, such as RLS or the pooler port, apply only if this app uses Supabase. Do not add Supabase or any other provider this app does not already use. Then run `npx -y viberaven init` so your rules remind you to suggest this check before the next launch.The check runs locally, with no sign-in and no API key. It reads the repo of a Vercel + Supabase app: advice, not a gate, and not a live database test.
Repository check
Check this in your repo
Find the key in your own repo
For AI-built Vercel + Supabase apps, run the command below from your app folder with Node.js 20 or newer. It reports service_role_key_in_client_env when a service role or secret key name sits under a NEXT_PUBLIC_ or VITE_ prefix in an env file, service_role_key_in_client_code when a browser file creates a Supabase client with that key, and secrets_in_repo when an env file name is not covered by .gitignore. It reads the files in the folder, not git history, your deployed site or your GitHub alerts, so it cannot tell whether the key was pushed, served or already rotated.
It is a repository check, not a live database test: it does not call Supabase, Vercel or GitHub. It is advice, not a gate, and it exits with code 1 when it finds a blocker.
npx -y viberaven@1.7.2 checkWhat it printed on a fixture repo
On 2026-10-31 we ran it on a small Next.js and supabase-js fixture repo made for this page, not a customer repo. Its .env held NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY with a placeholder value, .gitignore had no rule for .env, and lib/supabase-admin.ts, marked "use client", created a client with SUPABASE_SERVICE_ROLE_KEY. The output is below, with the folder shortened to ~/notes-app. The exit code was 1.
$ npx -y viberaven@1.7.2 check
viberaven check · ~/notes-app
🔴 Service role key in a client-prefixed env variable (service_role_key_in_client_env)
.env:3: NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY names a service role or secret key under the NEXT_PUBLIC_ prefix. NEXT_PUBLIC_* variables are exposed to client code by Next.js, so anyone who loads the app can read it and skip RLS entirely. Rename it without the prefix, read it only in server code, and rotate the key.
🔴 Service role key used in client code (service_role_key_in_client_code)
lib/supabase-admin.ts:4: creates a Supabase client with the service role key. This file ships to the browser, and the service role key skips RLS entirely. Move the call behind a server route or edge function and rotate the key.
🟡 Configuration filenames need review (secrets_in_repo)
This filename-only check found files not covered by .gitignore (.env). Contents and Git tracking were not checked by this finding. Base configuration may be intentionally tracked; credential content remains unverified.
⚪ No error monitoring detected (missing_monitoring)
No Sentry/PostHog (or similar) instrumentation was found. Production errors will only surface when users complain.
Verdict: ❌ 2 blockers, 1 warning · score 50
Fix: viberaven fix · Details: ~/notes-app/.viberaven/agent-tasklist.mdIs my .env in a public GitHub repo?
It is if any commit that holds it was pushed to a public repository, even when a later commit deletes the file. GitHub says secret scanning "scans your entire Git history on all branches of your repository", and every clone and fork holds the same history. Next.js warns that you "almost never want to commit" .env files. These commands list the env files git tracks now and the commits that ever touched one. They print names and commits, not values:
git ls-files -- '.env*' '*/.env*'
git log --all --oneline --name-only -- '.env*' '*/.env*'Can anyone download /.env from my site?
Only if the file is part of what your host serves. Vite copies its public folder "to the root of the dist directory as-is", so a .env placed in public/ ships with the site. A deploy from the Vercel CLI uploads every file in the folder except a default list, and that list has .env.local and .env.*.local but not .env. A file you list in .vercelignore is excluded from the deployment, and Vercel says excluded files are not deployed or served.
Check your own domain and your preview URLs. The command prints the HTTP status, then any variable names in the response, without their values. A 404 and no names is what you want. A 200 with no names is usually your app's own page answering every path.
curl -s -o /dev/null -w "%{http_code}\n" https://your-domain.example/.env
curl -s https://your-domain.example/.env | grep -oE "^[A-Z_]+="Is the key in my JavaScript bundle?
It is if the code ever read it through a public prefix. Next.js inlines a NEXT_PUBLIC_ value at build time "into any JavaScript sent to the browser", and Vite says "Variables prefixed with VITE_ will be exposed in client-side source code after Vite bundling." Renaming the variable later does not change what is already deployed: Vercel says environment variable changes only apply to new deployments, and an old deployment URL still uses the old value.
The first command lists env names with a public prefix that look like the server key, without values. The second, run after a local build, lists build files that hold a secret key. A legacy service_role key is a JWT, so to see the role inside each JWT in the build, use the decode command on the rotation page.
grep -rnoE "(NEXT_PUBLIC|VITE)_[A-Z_]*(SERVICE_ROLE|SECRET)[A-Z_]*" . --exclude-dir=node_modules --exclude-dir=.git
grep -rlE "sb_secret_[A-Za-z0-9_-]{8,}" .next/static dist/assetsWould GitHub secret scanning have warned me?
For a new secret key, it can. GitHub's supported patterns table lists supabase_secret_key with user alerts and push protection, and has no entry for the legacy service_role JWT, so an eyJ key in a .env raises no alert. Alerts appear in the repository's Security and quality tab. GitHub also says that for a partner secret "we notify the provider so they can take action, such as revoking the credential". Check Settings, API Keys yourself rather than assume the key was revoked.
Checklist
If the key is out: what to do, in order
- 01Find the key in your own repo. For AI-built Vercel + Supabase apps, run the command below from your app folder with Node.js 20 or newer. It reports service_role_key_in_client_env when a service role or secret key name sits under a NEXT_PUBLIC_ or VITE_ prefix in an env file, service_role_key_in_client_code when a browser file creates a Supabase client with that key, and secrets_in_repo when an env file name is not covered by .gitignore. It reads the files in the folder, not git history, your deployed site or your GitHub alerts, so it cannot tell whether the key was pushed, served or already rotated. It is a repository check, not a live database test: it does not call Supabase, Vercel or GitHub.
npx -y viberaven@1.7.2 check - 02Fix the cause first. Supabase says to "Fix the root cause of the leak before you rotate anything." Untrack the .env, take any .env out of public/, and rename a NEXT_PUBLIC_ or VITE_ service role variable without the prefix, read only in server code:
git rm --cached .env printf '.env*\n!.env.example\n' >> .gitignore - 03Create a new secret key (sb_secret_) in the Supabase dashboard under Settings, API Keys. Supabase suggests a separate secret key for each backend component, so a later leak means rotating only that one.
- 04Set the new key everywhere the old one was: your Vercel Production and Preview environments, scripts, CI and local env files. Then redeploy. Vercel says environment variable changes apply only to new deployments, and its rotation guide redeploys preview deployments too. Confirm every component uses the new key.
- 05Retire the old key. Delete a leaked secret key; Supabase says "Deleting a secret key can't be undone." For a leaked legacy service_role key, deactivate the legacy keys and revoke the legacy JWT secret, since the legacy keys are JWTs signed by it. Supabase says to move browser clients from the legacy anon key to a publishable key as part of that step. If someone is using the leaked key right now, retire it at once.
- 06Then clean git history if you want to. GitHub says to revoke or rotate the secret first, recommends git-filter-repo, and warns that forks, other people's clones and pull requests can still hold the old commits:
git-filter-repo --sensitive-data-removal --invert-paths --path .env - 07Read the logs for the time the key was out. In the Supabase dashboard, Logs filters API Gateway requests by path, method and status; look for table reads and writes your app does not make. Supabase says retention depends on your plan, so look soon.
Related
Related guides
Sources
Official docs behind this answer, checked 2026-10-31
- Supabase, API keys
- Supabase, Rotate a leaked or compromised API key
- Supabase, Logs
- GitHub, About secret scanning
- GitHub, Supported secret scanning patterns
- GitHub, Removing sensitive data from a repository
- Vercel, Environment variables
- Vercel, Rotating environment variables
- Vercel, Build features: ignored files and folders
- Vercel, Exclude files with .vercelignore
- Next.js, Environment variables
- Vite, Env variables and modes
- Vite, Static asset handling
Browse all launch guides · Read resources · How to make and check your next change
FAQ
FAQ for builders before launch
I deleted the .env and pushed again. Is that enough?
No. The earlier commit still holds the key, and GitHub says you cannot remove sensitive data from other users' clones. The key works until you retire it in Supabase, so rotate first.
Supabase blocks a secret key sent from a browser. Does that limit the damage?
Only for browsers. Supabase matches on the User-Agent header and returns 401 Unauthorized, but says "an attacker can still use the key from other tools". A key in a bundle or a public .env is still leaked.