Secrets Management: Don't Commit That Password

Practical credential hygiene for real projects — why secrets don't belong in source control, how .gitignore and environment variables actually protect you (and where they don't), and what to do if a secret is committed by mistake.

Free preview · 1 of 3 free left
Topic: Operations Guide beginner 8 min read

Why this matters

A committed secret doesn't just risk being seen by the wrong person today — once it's in Git history, it's there permanently unless you deliberately rewrite history (a genuinely painful, error-prone process), even if you delete it in a later commit. Treating credential hygiene as a first-class habit from day one is far cheaper than cleaning up after a leak.

What you'll learn

Why .gitignore alone isn't a complete defense, how environment variables keep secrets out of source code, and the concrete steps to take if a secret does get committed by mistake.

.gitignore protects future files, not files you've already told Git to track

Adding .env to .gitignore prevents Git from picking it up if it isn't already tracked. But if .env was ever explicitly git add-ed before the ignore rule existed, it's already in history — adding it to .gitignore afterward stops future changes to it from being tracked, but does not remove it from past commits. This is why checking git status and git diff --cached before every commit (see the Git workflow guide) matters even with a solid .gitignore in place — the ignore file is a safety net, not a substitute for looking.

Environment variables: the standard pattern

Instead of writing a credential directly into source code, store it in an environment variable (commonly via a .env file, itself gitignored) and read it at runtime:

# .env (never committed)
DASHBOARD_ADMIN_USER=someuser
DASHBOARD_ADMIN_PASS=somesecretvalue
const user = process.env.DASHBOARD_ADMIN_USER;
const pass = process.env.DASHBOARD_ADMIN_PASS;

This project's own admin/dashboard authentication follows exactly this pattern — the code reads process.env.DASHBOARD_ADMIN_USER / DASHBOARD_ADMIN_PASS, and if they're not set, the route explicitly refuses to serve rather than defaulting to some open or hardcoded fallback. That "fail closed, not open" behavior is a deliberate, important detail — a misconfigured deployment should be unreachable, never unprotected.

Timing-safe comparison — a related, easy-to-miss detail

A subtler credential-hygiene issue: comparing a supplied password to the real one using a plain === string comparison leaks timing information (the comparison returns faster the earlier a mismatch occurs), which is a real, documented side-channel in security-sensitive code. The fix is a constant-time comparison function, and it's worth knowing this exists even if you're not implementing auth yourself day to day — it's the kind of detail that separates "looks secure" from "is secure."

If a secret does get committed by mistake

  1. Rotate the credential immediately — treat it as compromised the moment it's pushed, even to a private repository. Changing the password/key is non-negotiable and the single most important step; cleaning up Git history is secondary.
  2. Remove it from the current and future commits with a normal commit (delete/replace the value), and confirm the fix with git diff --cached before pushing.
  3. Only pursue history rewriting (tools like git filter-repo) if the exposure was severe and the repository's history genuinely needs cleaning — recognize this as a separate, more involved effort from simply "fixing it going forward," and treat rotating the credential as complete on its own regardless of whether you rewrite history.

What's next

Credential hygiene is one part of a broader deployment safety mindset — see Writing Idempotent Deployment Scripts for how this project's own installer scripts are designed to never require a hardcoded secret to run safely.

Part of: System Administrators, DevOps Beginners, Cloud Beginners

Continue learning

Guide

Git for a Real Website: The iamravi.com Workflow

A structured, practical course in Git and GitHub, taught through the actual workflow used to build and maintain a production Node.js site — not a shallow command dump.

← Back to Guides

Next → TLS/SSL Handshake, Step by Step What actually happens during a TLS handshake, why certificate expiry breaks everything at once, and how to read the handshake yourself with openssl and a browser.