Secrets — what never to expose

Leaked secrets are one of the most common and costly developer mistakes. Attackers scan GitHub every minute looking for exposed API keys, passwords, and tokens. This page is about knowing exactly what a secret is, where it belongs, and why it can never go in client-side code.

What counts as a secret?

A secret is any value that grants access to a resource — an API, a database, a payment processor, an email service. If someone else gets it, they can impersonate your server, spend your money, access your users' data, or destroy your infrastructure.

Stripe Secret Key — sk_live_...

Lets anyone create charges, issue refunds, access customer payment data, and modify your Stripe account. This is different from the Stripe publishable key — that one is safe to expose.

Database URL / Password

Typically contains the username, password, host, and database name: postgresql://admin:password@db.host:5432/mydb. Direct access to every user record, every piece of data in your app.

JWT Secret / Session Secret

Used to sign authentication tokens. If exposed, anyone can forge a valid login token for any user — including admin accounts.

OpenAI / Groq / Anthropic API Key

Grants access to AI API calls billed to your account. An exposed key means unlimited AI calls at your expense. Keys can rack up thousands of dollars in hours.

Supabase Service Role Key

The service_role key bypasses all Row Level Security policies. It has unrestricted read/write/delete access to every table. The anon key is safe to expose — the service role key is not.

Cloudflare API Token

Can modify DNS records, deploy workers, read secrets, and manage your entire Cloudflare account. If someone gets this, they can redirect your domain to anywhere.

Resend / SendGrid / SMTP Credentials

Lets anyone send email from your domain. Used for spam, phishing, and reputation destruction.

GitHub Personal Access Token

Depending on scopes, can push code, modify repos, read private repositories, or delete branches. Treat like a password.

The server vs client boundary

This is the most important mental model in web security. Your application code runs in two places, and they are fundamentally different:

Browser (Client)

  • Anyone can open DevTools and read all JS
  • Anyone can see network requests
  • Anyone can inspect localStorage and cookies
  • Zero trust — treat everything here as public
  • Safe: public API keys, public URLs, UI state

Server (Backend)

  • Code runs on your machine, not the user's
  • Users cannot read your server-side code
  • Environment variables are invisible to clients
  • This is the only safe place for secrets
  • Safe: all secrets listed above
The golden rule

If it's in browser JavaScript, the entire internet can read it. Secrets belong on the server. Always.

What IS safe to expose

Not everything needs to be a secret. Some keys and values are designed to be public:

Stripe Publishable Key — pk_live_...

Used in the browser to render the Stripe payment form. Stripe designed it to be public — it can only be used to tokenize card data, nothing else.

Supabase Anon Key

The anon key is meant to be public. Access is controlled by your Row Level Security policies. Without RLS configured, the anon key is dangerous — but with proper RLS, it's safe in browser code.

Public API URLs

The URL of your own API (https://api.yoursite.com) is fine in the browser. It's just an address — not a credential.

Analytics IDs, Turnstile Site Keys

These are designed to be embedded in public-facing pages. They identify your account but don't grant privileged access.

How secrets get leaked — the common mistakes

Most secret leaks happen from one of these mistakes:

  • Committing .env to Git — The most common. Add .env to .gitignore before you ever touch the file.
  • Hardcoding in source code — Writing const apiKey = "sk-live-abc123" directly in your JS. It's now in every Git commit forever.
  • Putting secrets in frontend JS — A Vite or Create React App build bundles all your JS into a file users can download and read.
  • Public GitHub repository — A private repo made public, or a contributor accidentally pushing a secret to an open-source project.
  • Logging secretsconsole.log(process.env) during debugging, then shipping that log statement to production.
  • Secrets in URLs — Passing a secret as a query parameter: /api/data?key=sk-live-abc. URLs are logged in access logs and browser history.
If you accidentally commit a secret to Git, treat it as fully compromised — even if you delete it in the next commit. Rotate the key immediately in the service that issued it, then clean the Git history if needed.

Rotation — what to do when a secret leaks

Every secret has a rotation process — a way to invalidate the old value and generate a new one. Learn this before you need it:

  • Stripe — Dashboard → API keys → Roll secret key
  • OpenAI — Platform → API keys → Revoke, create new
  • Supabase — Project settings → API → Rotate keys
  • Cloudflare — Profile → API tokens → Revoke token, create new
  • GitHub PAT — Settings → Developer settings → Personal access tokens → Delete, regenerate
  • Database password — Connect directly (or via your host's console) and run ALTER USER username WITH PASSWORD 'newpassword'
Next: read the .env files guide to see how to store secrets safely during development, and API Keys to understand the difference between public and secret keys.