Most local dev setups end up with some secret — an API key, a cloud credential, a database password — sitting in a plaintext file on disk indefinitely. Here's a simple pattern that avoids that, using AWS credentials as the example.
The problem with the usual approaches
-
A credentials file (e.g.
~/.aws/credentials) — plaintext, no expiry, no prompt. Anyone with filesystem access (malware, a compromised dependency, a careless backup, a stolen unencrypted disk) reads it instantly. -
A
.envfile "just for now" — inevitably ends up committed once someone forgets it's there, or pasted into a chat while debugging. -
Secrets exported in your shell profile — same problem, just moved into
.bashrcor.zshrc, still plaintext, still readable by anything running as your user.
The common thread: none of these require proving you're you at the moment of use. The secret just sits there, decrypted, waiting.
The fix: password manager + directory-scoped env loading
Store the secret in a system built for secrets — a password manager — and only materialize it into the environment for the duration of a shell session, gated behind your master password.
A password manager like Bitwarden gives you:
- Encrypted-at-rest storage, synced across devices.
- A CLI (
bw get item,bw unlock --raw) that scripts easily. - A clean separation between login (proving identity to Bitwarden's servers) and unlock (decrypting the vault locally with the master password) — unlock is what gates access, and it's required every time.
direnv gives you:
- Automatic loading/unloading of environment variables scoped to a directory.
cdin, get the vars;cdout, they're gone. - A trust boundary:
.envrcfiles must be explicitly approved (direnv allow) before they execute, so a malicious.envrcin a cloned repo can't silently run code. - No setup script to remember — it's automatic on directory entry.
Together: the secret never touches disk in plaintext. It lives encrypted in Bitwarden, and only exists as an environment variable for the lifetime of a shell session in one directory, after an explicit unlock.
Example: AWS credentials
Store the key as a normal Login item (or a secure note) in Bitwarden (e.g. named aws-iam-keys) with custom fields aws_access_key_id and aws_secret_access_key.
Then a project's .envrc:
BW_ITEM="aws-iam-keys"
export BW_SESSION="$(bw unlock --raw)"
item_json="$(bw get item "$BW_ITEM" --session "$BW_SESSION" 2>/dev/null)"
if [ -z "$item_json" ]; then
log_error "Could not fetch Bitwarden item '$BW_ITEM'"
return 1
fi
bw_field() {
echo "$item_json" | jq -r --arg name "$1" '.fields[]? | select(.name == $name) | .value // empty'
}
export AWS_ACCESS_KEY_ID="$(bw_field aws_access_key_id)"
export AWS_SECRET_ACCESS_KEY="$(bw_field aws_secret_access_key)"
cd-ing into the project prompts for the Bitwarden master password once and exports the two env vars that any AWS SDK or CLI already knows how to read. The same pattern works for any secret: swap the item name and field names, export whatever env vars the tool in question expects (a database URL, an API token, whatever).
One thing worth noting: direnv only re-executes .envrc when the directory changes or the file itself is edited — it doesn't re-run on every command within the same shell. So "prompt once per shell/directory load" isn't something you have to build; it's just how direnv already works. It's tempting to cache the unlocked BW_SESSION to a file so multiple terminal tabs share one unlock, but that just trades one problem for another — now there's a live decryption key sitting on disk. Re-entering the master password once per new terminal is a fine trade for keeping no secret material on disk at all.
Takeaway
For any local secret, ask whether it's ever decrypted-and-idle on disk. A password manager + directory-scoped env loading means the secret only exists in memory, for as long as you're in the project, gated behind an explicit unlock — a small amount of setup for a meaningfully smaller attack surface than a credentials file that just sits there forever.
What's your setup for local secrets — do you rely on a cloud secrets manager instead, or something else entirely? Curious to hear in the comments.
Top comments (0)