secrets and keys
why
a secret is only secret while it exists in exactly one shape — a typed runtime binding. every extra copy in a log, error, or client bundle is a leak surface no cleanup can reliably recall. centralizing the value makes misconfiguration loud: a missing binding fails at boot with a typed diagnostic, not as an unauthenticated request in production.
rule
a secret exists exactly once: a typed environment binding declared to the runtime. everything else — source, contracts, generated clients, logs, error messages, durable records, documents — holds references or derived identifiers, never the secret.
boundary map
| surface | allowed | forbidden |
|---|---|---|
| runtime config | typed binding, validated once at startup. | process.env reads scattered through code. |
| source | key names, binding references. | literal tokens, private keys, passwords. |
| logs and diagnostics | key fingerprints or last-4 identifiers. | full secret values in any log line or error. |
| durable records | provider-returned key IDs and fingerprints. | the credential that would still work later. |
| client code | no secret reaches it; the server proxies secret-requiring calls. | secrets in frontend bundles or public API responses. |
startup validation fails fast: a missing or malformed binding is a typed boot diagnostic, per diagnostic errors, not an undefined value found deep in a workflow.
rotation
treat every secret as rotating. store the provider's key identifier beside usage, so audits answer "which key made this call" without the key. prefer provider mechanisms (scoped API keys, signed exchange, short-lived tokens) over long-lived shared secrets where offered.
do not
- read secrets via
process.envoutside the one startup binding module. - log a whole request, response, or error object that may carry a credential — log the typed fields you chose.
- commit a secret "temporarily", including fixtures, scripts, or MDX examples.
- build a client-side feature that requires embedding a provider key.
verification
- search the repo for credential-shaped literals (key prefixes,
-----BEGIN, high-entropy strings in authored files). - confirm exactly one module reads the secret binding.
- probe a failing provider call: the diagnostic carries a fingerprint, not the key.
- rotate one secret in a non-production environment: no stored record breaks — references, not copies, were persisted.
good and bad
bad
const apiKey = process.env.PROVIDER_KEY; // read at the call sitelog.error("provider call failed", { headers: res.headers });
the secret is read outside the startup binding, and the whole header object — possibly carrying the credential — lands in a log line. logs are copied to aggregators, backups, and support tooling; once published, no cleanup recalls every copy. rotation becomes an emergency.
good
// env.ts: one typed binding validated at bootconst providerKey = env.PROVIDER_KEY;log.error("provider call failed", { keyFingerprint: fingerprint(providerKey) });
the secret exists once as a typed binding; logs and durable records carry fingerprints that answer audits without exposing the key. a missing binding fails loudly at boot; rotation touches one module plus the provider.