Preparing your learning space...
25% through Security for FDEs tutorials
Every integration needs credentials — API keys, passwords, tokens. Put them in the wrong place and they end up in a git repo a thousand people can see, or in a log line that gets forwarded to three SaaS tools. This tutorial covers where credentials should not live, how environment variables became the standard home for configuration, and how a proper secret manager upgrades that from "out of source control" to "rotatable, scoped, and audited."
Secrets are what an attacker looks for first. A stolen API key or password is how most real breaches start — and the surprising part is how often that key was sitting in a file that was already public. Keep your secrets in the one place designed to hold them, and never inside your source code.
An API key typed as a string literal, or a .env file committed to version control, both end up in your history — and history eventually escapes (a public fork, a screenshot, a doc). The rule is absolute: a secret that ships inside source code is a secret that will leak.
Why it matters: version history never forgets. Even "I'll remove it in the next commit" leaves the secret in the repo's past. That's still a leak.
An environment variable is a value your process provides to your app at runtime — injected when the app starts, never written into the code:
APP_ENV=production DB_PASSWORD=s3cr3t STRIPE_KEY=sk_live_4f9c...
Your code asks "what's DB_PASSWORD?" and the answer comes from whoever launched the process — your terminal, your deployment, your container. Reading secrets from the environment keeps them separate from the code, which is the first step toward safety.
In Python, os.getenv returns the value or a safe default. So no literal value sits in your source:
import os
DB_HOST = os.getenv("DB_HOST", "localhost") # non-secret: a default is fine
DB_PASSWORD = os.getenv("DB_PASSWORD") # a secret: no default
if not DB_PASSWORD:
raise RuntimeError("DB_PASSWORD is not set")
Explanation: the code only reacts to configuration — it never contains it. The password comes in through the environment at startup and stays out of the repo. os.getenv is the simplest bridge between your operating environment and your code.
Note: give safe non-secret defaults (like localhost) so local development stays smooth, but give no default for real passwords — a missing value should fail fast instead of running insecure.
For local development it's tedious to set a dozen variables by hand in every terminal. A small library (python-dotenv) reads a .env file at startup — a plain list of KEY=value pairs — and loads them into the process environment:
# .env (gitignored, never committed)
DB_PASSWORD=dev_only_secret
STRIPE_KEY=sk_test_...
from dotenv import load_dotenv
load_dotenv() # reads .env into the environment
import os
DB_PASSWORD = os.getenv("DB_PASSWORD")
Best Practice: add .env to .gitignore the day you create it. Commit a .env.example with empty or fake values instead, so new teammates know which variables to set.
A .env is fine for your laptop; it is not secrets infrastructure. It holds secrets in plaintext on disk, keeps no audit trail, gives every process on the machine access to all the secrets, and is easy to leak by accident (a mistaken git add ., a backup, a screenshot). For a throwaway script, plaintext in a gitignored file is acceptable; for anything touching real customer data, use a secret manager.
A secret manager (AWS Secrets Manager, Google Secret Manager, Azure Key Vault, HashiCorp Vault) is a service that stores secrets and hands them out to authorized callers at runtime:
App → asks secret manager (with its own small credential) → gets the key over TLS
The concrete gains:
The good news is your code barely changes. The secret still lands in an environment variable; only the source of that value changes:
before: .env file → env var → code after: secret manager (encrypted) → env var → code
Your code keeps calling os.getenv("DB_PASSWORD"). The security lives in where the value came from, not in how the code reads it.
How to apply: during CI or at deploy time, fetch the secrets from the manager and export them as environment variables, then start the app. The pipeline's own credential is the only secret in the process — and it too should be scoped to the minimum.
Rotation is replacing a still-valid secret with a new one, either on a schedule or on an event (someone leaves, an incident, a suspected leak). With a secret manager this is a single command, and your app picks up the new value at the next deploy.
Why rotate: the longer a secret lives, the longer the window in which a leaked copy can be abused. A schedule bounds that window.
On a confirmed leak: don't just "mute" the old key — rotate it and audit who could have seen it. And because each integration should have its own key, rotating one doesn't break the others.
If every integration shares one API key, then revoking one service's access revokes them all, and you can't tell which service leaked. Instead:
Now rotating one service changes nothing for the others, and you can cut off exactly the offender.
Keep dev, staging, and production secrets wholly separate — separate manager paths or namespaces — so a bug in dev can never hand out the production key. A developer's laptop uses a dev token; staging uses staging credentials; only production holds production secrets.
Best Practice: add a guard so production refuses a value that looks like a dev sample. Better still, hold prod credentials only behind a prod-only scope, so they can never be fetched from a dev environment.
As secrets move and as they're stored:
https://) and put secrets in headers or bodies, never in a URL query string — query params survive into access logs.Note: more than just passwords count as secrets — connection strings, internal hostnames, tenant IDs, private routes, and signing keys are all worth the same protection.
.env only locally, only gitignored; commit a .env.example.Save your progress and earn XP for completing tutorials.
4 questions · Pass with 70%+
1Where should a production API key live?
2Why shouldn't a secret go in a URL query string?
3Rotating one service's key breaks the others. Best fix?
4.env is acceptable for…
Technology
Forward Deployed Engineer
Lesson group
Security for FDEs
Progress
25% complete