What you'll learn
Quick Answer
Keep secrets out of code entirely — use environment variables locally and a secrets manager in production. If a key has ever been committed, rotate it. Removing it from a later commit does not remove it from history.
Why this specifically matters
Public repositories are scanned continuously by automated tools looking for credential patterns. A committed cloud key is typically found and used within minutes, most often to run up compute charges. Students have received five and six figure bills this way.
Private repositories are safer but not safe. Everyone with access has the key, it appears in every clone, it is in every backup, and it survives long after the person who added it has left.
The critical property is that git history is permanent. Committing a key and deleting it in the next commit leaves it fully readable in the earlier commit. The repository has not forgotten; the working tree has simply moved on.
Which leads to the only correct response to an exposed key: rotate it. Revoke it and issue a new one. Cleaning history is optional tidying; rotation is the actual fix.
Environment variables and .env files
The standard approach is to read secrets from the environment, so they never appear in code:
import os
API_KEY = os.environ["API_KEY"] # fails loudly if missing
DEBUG = os.environ.get("DEBUG", "0") # optional with default
Using os.environ[...] for required values is deliberate — the application refuses to start rather than running with a missing credential and failing confusingly later.
For local development, a .env file holds them:
API_KEY=sk_test_abc123
DATABASE_URL=postgres://localhost/dev
Two things must accompany it. Add .env to .gitignore before creating the file, since adding it afterwards does not untrack an already-committed one. And commit a .env.example listing the required variable names with dummy values, so a new contributor knows what is needed.
In production, set variables through the platform's own configuration rather than shipping a .env file.
What to do when it has already happened
In order, and the order matters:
- Rotate the credential immediately. Revoke the old one at the provider. Do this first — before cleaning history, before telling anyone. Every minute the key remains valid is exposure.
- Check for unauthorised use. Cloud billing, API logs, database access records. Assume it was used until you have evidence otherwise.
- Remove it from the code and switch to environment variables.
- Optionally rewrite history with a tool such as git-filter-repo. This is disruptive — it changes every commit hash and requires everyone to re-clone — and it does not help if the repository was ever public or forked.
Step 4 is genuinely optional. Once a key has been exposed, it is compromised regardless of whether you scrub it. Rotation is what removes the risk; history cleaning removes embarrassment.
Prevent recurrence with a pre-commit hook that scans staged changes for credential patterns. GitHub also scans public repositories and notifies some providers directly, which is occasionally how people find out.
Secrets managers
Environment variables are fine for a small project and have real limits at scale: no audit trail of who read what, no rotation, and no easy way to grant one service access to one secret.
A secrets manager — AWS Secrets Manager, HashiCorp Vault, or a platform's built-in store — addresses those. The application authenticates to the manager and fetches secrets at startup or on demand.
What it buys: access control per secret, an audit log, automatic rotation for supported services such as databases, and encryption at rest with a managed key.
The authentication question — how the application proves it may read a secret — is solved by the platform's identity system. An instance with an attached role gets temporary rotating credentials automatically, so there is no bootstrap secret to leak. That is the elegant part: it removes the last hardcoded credential.
For a student project this is overkill. Understanding why it exists is worth having for interviews.
Practical rules
- Never log secrets. A logged token ends up in log aggregation with far wider access than your database. Redact them explicitly.
- Different secrets per environment. Development must never use production credentials — it removes the safety of a mistake being harmless.
- Client-side means public. Anything in JavaScript sent to the browser, or in a mobile binary, is readable. Vite requires a
VITE_prefix for exposed variables precisely to make this a deliberate act. - Least privilege. A key that only reads should not be able to write. It limits the damage when — not if — one leaks.
- Rotate periodically, not only after incidents, so rotation is a routine you have practised rather than something attempted for the first time under pressure.
See what recruiters look at on your GitHub — a committed .env is also a visible signal about how you work.
