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
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 secrets — console.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.
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'