Every app needs two kinds of values it can't hardcode: configuration (which database to connect to, which feature flags are on) and secrets (API keys, passwords, signing keys). Getting this right early prevents some of the most common and most damaging security mistakes.
If Cartara flagged this in your diff, you likely added environment variable reads, a .env file, or changed how your app loads configuration.
Config vs. Secrets
Configuration is any value that varies by environment: the database URL, the log level, which API endpoint to use, feature toggles. Not necessarily sensitive, but shouldn't be hardcoded.
Secrets are the subset of config that would cause harm if exposed: API keys, database passwords, signing keys, OAuth client secrets. These need extra protection — encryption, access control, and rotation.
Both should live outside your code. The difference is that secrets need stronger safeguards.
The Core Rule: Store Config in Environment Variables
A widely-followed principle says: store config in environment variables, not in your code.
The reason is that the same built artifact should deploy to dev, staging, and production without modification — only the environment around it changes. If your database URL is hardcoded, you have to rebuild for each environment.
In practice, your app reads values like this:
```ts
const databaseUrl = process.env.DATABASE_URL;
const stripeKey = process.env.STRIPE_SECRET_KEY;
const logLevel = process.env.LOG_LEVEL ?? 'info';
```
The .env File
Locally, developers keep config in a .env file that the app loads on startup:
```
DATABASE_URL=postgres://localhost/myapp_dev
STRIPE_SECRET_KEY=sk_test_abc123
LOG_LEVEL=debug
```
The non-negotiable rule: .env must be in .gitignore and never committed to Git.
Committing a .env with real secrets is one of the most common ways credentials leak — and Git remembers everything, so even a deleted-and-recommitted secret is still in the history and must be rotated immediately.
Commit a .env.example with the keys but placeholder values so teammates know what's needed:
```
.env.example
DATABASE_URL=your-database-url
STRIPE_SECRET_KEY=your-stripe-key
```
Where Secrets Should Live
| Environment | Where secrets go |
|---|---|
| Local dev | A .env file, gitignored |
| Hosting platforms | The platform's encrypted environment variables UI (Vercel, Railway, Render) |
| CI/CD pipeline | The platform's encrypted secret store — never in the workflow YAML |
| Larger setups | A dedicated secrets manager (HashiCorp Vault, AWS Secrets Manager, Doppler) |
A dedicated secrets manager is worth it when you have many secrets, multiple services, or compliance requirements. It centralises storage, encrypts at rest, controls access, and logs who read what.
What You'll See in Your Code
Secrets and config management typically shows up in diffs as:
Reading from environment variables:
```ts
const config = {
databaseUrl: process.env.DATABASE_URL!,
jwtSecret: process.env.JWT_SECRET!,
stripeKey: process.env.STRIPE_SECRET_KEY!,
};
```
A .env.example file being added or updated with new required variables.
Validation at startup — catching missing required variables before the app starts:
```ts
const required = ['DATABASE_URL', 'JWT_SECRET', 'STRIPE_SECRET_KEY'];
for (const key of required) {
if (!process.env[key]) throw new Error(Missing required env var: ${key});
}
```
Rotation
Secrets should be rotatable — changeable without a painful redeploy — and should be rotated periodically, and immediately if one might have been exposed.
- A leaked-but-rotated secret is harmless. A leaked long-lived secret is a standing risk.
- Rotation is much easier if you designed for it (secrets read from the environment, not hardcoded).
Where possible, prefer short-lived credentials (cloud IAM roles, OIDC tokens) over long-lived static keys. A credential that expires in an hour is far less dangerous to leak.
The Principle of Least Privilege
Every secret should grant the minimum access needed. A key used only to read from one storage bucket shouldn't also be able to delete your database. Scope credentials tightly so a single leaked secret can't compromise everything.