Skip to main content
ZELSIS

Which keys in a SaaS stack can ship to the browser

Most teams know the classic pair. A Stripe pk_live_ key belongs in the frontend and an sk_live_ key never does. The Supabase anon key is public and the service role key is not. The tools many teams added more recently follow the same idea, but the names are less obvious, and several of them hand you both kinds of key on the same settings page.

This week we added checks for these newer keys. Writing them meant sorting out, provider by provider, which key a browser may hold. Here is that list.

Same product, two keys

These providers ship a key that is designed to be public next to one that gives full access. The prefixes differ by a letter or two.

Public: fine in client code
posthog.init('phc_...')                 // PostHog project API key: send events only
new Langfuse({ publicKey: 'pk-lf-...' })   // Langfuse public key
LDClient.initialize('<client-side ID>')    // LaunchDarkly client-side ID
flagsmith.init({ environmentID: '...' })   // Flagsmith client-side environment key
Secret: server only
phx_...        // PostHog personal API key: reads your analytics data and changes settings, within its scopes
sk-lf-...      // Langfuse secret key: reads all traces, including prompts and user input
sdk-...        // LaunchDarkly server-side SDK key: downloads every flag rule and segment
ser.<key>      // Flagsmith server-side environment key: downloads the full environment, segment rules included

The easy mistake is copying the wrong one into a NEXT_PUBLIC_ or VITE_ variable because both are labelled "API key" in the dashboard. Anything with those prefixes ends up in the JavaScript bundle, readable by every visitor.

Keys with no public twin

These have no browser-safe version. If one shows up in client code or in the repository, treat it as leaked.

pcsk_...          // Pinecone: read, overwrite or delete your vector indexes
prod:name|...     // Convex deploy key: push new backend code to your deployment
signkey-prod-...  // Inngest signing key: send requests your app accepts as coming from Inngest
lsv2_pt_...       // LangSmith: read every traced prompt and response
napi_...          // Neon API key: manage projects, branches and databases
tr_prod_...       // Trigger.dev secret key: trigger and read your background jobs
unkey_...         // Unkey root key: create and revoke your customers' API keys
dub_...           // Dub API key: change where your short links point

Two of these are worse than they look. A leaked Convex deploy key lets someone replace your backend functions, so it is closer to a deploy credential than a data key. A leaked Unkey root key lets someone issue API keys for your own product to themselves.

Keys with no prefix at all

AssemblyAI, Deepgram, Loops, Novu, Attio and Daily use plain hex strings. Nothing about the value says what it is, so a scanner that flagged every 32 or 40 character hex string would also flag commit hashes, checksums and cache keys. We only report these when the value is assigned to the provider's own variable name or passed straight to its client:

Reported
const DEEPGRAM_API_KEY = "<40 hex characters>";
const client = new AssemblyAI({ apiKey: "<32 hex characters>" });
Not reported
const DEEPGRAM_API_KEY = process.env.DEEPGRAM_API_KEY;
const sha = "<40 hex characters>"; // a git commit

The trade-off is that a key renamed to something generic like KEY slips through. We would rather miss that than send a team chasing commit hashes.

If one of these is already in your repository

Revoke it in the provider's dashboard first, then remove it from the code. Deleting the line does not help on its own: the key stays in git history and in every clone, fork and CI cache that already has it. Put the new key in a server-side environment variable (no NEXT_PUBLIC_ or VITE_ prefix) and call the provider from a route handler or server action instead of the browser.

Check your own repo

Public repositories scan free. Paste a GitHub URL on the dashboard and every finding comes with the file, the line and a fix.

Scan a repository free