Shiv Gupta

Practicing secret hygiene

Ever since I accidentally pushed a set of credentials to a branch on GitHub, and frantically tried to santizize the repo late on a Friday, I’ve adopted a couple of practices to prevent this from happening again. Here’s what has been working really well for me.

Prefer short-term access tokens over long-term credentials

When possible, I now avoid storing long-term credentials locally. For example, AWS credentials commonly stored in ~/.aws/credentials can be replaced by short-term credentials (see gimme-aws-creds and aws sso login for good examples of ways to accomplish this).

Use a global .gitignore to ignore files you commonly use to store credentials

If I do store plaintext credentials, a good fallback to make sure I don’t accidentally commit and push them to a repo is to ignore files containing credentials in a global .gitignore file.

# ~/global.gitignore

**/.env

This ignores .env files globally so they never show up in your git working tree. This step is fairly simple to implement given it does not require an external program other than git.

Use a password manager to dynamically inject your secrets as environment variables

1Password works really well here. Here’s an example. Let’s say my application needs a set of environment variables and I’d like to store them in a .env file like so:

POSTGRES_HOST=127.0.0.1
POSTGRES_USER=admin
POSTGRES_PASSWORD=somepassword

I instead replace the plaintext secrets with 1Password Secret References and have 1Password inject these as environment variables. Here’s how I would edit the .env:

POSTGRES_HOST=127.0.0.1
POSTGRES_USER=admin
POSTGRES_PASSWORD="op://Private/Postgres_Password"

And invoke my application with this:

op run --env-file="./.env" -- node app.js

This calls the 1Password CLI which injects all three secrets in my .env as environment variables that my application can read from.

Using a handy alias, I made this command a little less unwieldy:

# ~/.zshrc

opr() {
  # opr .env <command> [flags]
  op run --env-file "$1" -- "${@:2}"
}

To invoke my application with secrets injected, I’d run this:

opr .env node app.js

By replacing my plaintext passwords with 1Password secret references, I’ve reduced a risk factor and a lot of potential headache.

Though 1Password has worked really well for me, some alternatives to consider might be BitWarden (personal password manager), Hashicorp Vault, and AWS Secrets Manager (enterprise-grade secret managers).

Closing thoughts

Practicing secret hygiene individually is a good thing, no doubt. But it helps to invest in tooling that detects and removing plaintext secrets at scale. These tools typically integrate within source control, CI/CD, and IaC tooling to help identify exposed secrets. I’ve also found the OWASP Cheat Sheet Series to be a great resource for all things application security.