Keep secrets out of source control and out of anything shipped to a browser. Load them at runtime from the environment or a secrets manager, validate them once at startup, never log them, give each service its own credentials, and rotate them on a schedule so a leak has an expiry date.
Where do secrets leak in TypeScript projects?
TypeScript adds types, not secrecy. The leaks are the same as in plain JavaScript, with a few that the build step makes easier.
- Committed files. A
.envfile or a config with a real key lands in git history, where deleting it later does not remove it. - Client bundles. Code that runs in the browser is public. If a build tool inlines an environment variable into the bundle, the value is readable by every visitor. A constructed example: a developer reads
process.env.PAYMENTS_KEYinside a shared module that the frontend also imports. The bundler replaces it at build time, and the key ships to every browser. - Logs and errors.
console.log(config)during debugging, an HTTP client that logs request headers, or an error object holding a connection string. - Shared credentials. OWASP's Secrets Management Cheat Sheet notes that services often share the same secrets, which makes finding the source of a leak hard.
How should the app load secrets?
For local development and simple deployments, Node can load variables from a file. The Node.js documentation describes --env-file=file, which loads environment variables from a file relative to the current directory and makes them available on process.env. If the same variable exists in the real environment and in the file, the environment wins. That precedence is useful: production values set by the platform override anything left in a development file.
For production, prefer a secrets manager or your platform's secret store that injects values at runtime and records access. OWASP describes the need to centralize storage, provisioning, auditing, rotation and management of secrets, so that access is controlled and leaks are limited.
How do you stop missing or malformed secrets from causing odd failures?
Read the environment in exactly one module, validate it, and export a typed object. Everything else imports that object and never touches process.env.
// config.ts
function need(name: string): string {
const v = process.env[name];
if (!v) throw new Error(`Missing required setting: ${name}`);
return v;
}
export const config = Object.freeze({
dbUrl: need("DATABASE_URL"),
apiKey: need("PAYMENTS_KEY"),
});
The app fails at startup with the name of the missing setting, not the value. It never prints a secret in the error. A frozen object also stops accidental changes later. Keep this module out of anything the browser bundle imports, and split server config and public config into two files so the compiler and reviewers can see the boundary.
How do you keep secrets out of logs?
Do not log the config object. Redact known fields in your logger, and keep request logging to a short allowlist of headers instead of a denylist. Exceptions from database and HTTP clients often include connection strings, so catch them near the boundary and rethrow a sanitized error.
What about build and CI?
Build-time secrets are still secrets. Give CI jobs only the credentials a job needs, scoped to the environment, and avoid passing them as command line arguments that end up in process lists. Add a secret scanner as a pre-commit hook and as a CI check, so a committed key is caught within minutes.
What to do when a secret leaks
Assume it is compromised the moment it is in a public place or a build artifact, even briefly. Revoke and replace it first, then clean history and investigate use. Cleaning history alone leaves the credential valid.
What to do
- Ignore
.envfiles in git, and commit an example file with fake values. - Read the environment in one module, validate at startup and export a typed object.
- Split server-only and browser-visible config, and check what the bundler inlines.
- Use a secrets manager in production, with per-service credentials.
- Redact secrets in logs and sanitize caught errors.
- Run secret scanning in pre-commit and CI.
- Rotate on a schedule and have a written revoke-and-replace procedure.
Safeguard includes secrets scanning for repositories. Try the secrets scanning demo below.
Open the secrets scanning demo
Quick check
Is a .env file safe if it is in .gitignore?
Safer, not safe. It keeps the file out of commits, but it can still be copied into images, bundles or backups. Production should use a secret store.
Does deleting a committed key from git fix the leak?
No. The key stays in history and clones. Revoke and replace it first.
Sources
Last reviewed: 18 October 2026.