What Is This CLI Password Manager You Speak Of? #
Using pass, the Unix password manager, has completely changed the way I handle API keys and other sensitive environment variables.
As developers and engineers, we usually handle sensitive secrets by throwing them in a .env or config file and calling it a day.
However, that is extremely insecure and also prone to data loss.
How many times have you DM’d a colleague to ask them for their .env file because you accidentally deleted yours during your weekly shitty GitHub project cleanup?
And how many times have you forgotten to set up a .gitignore file and had to use git-filter-repo to delete the history?
pass solves all that.
How? Git.
I know I’m beating a dead horse each time I talk about how Git solves all our problems, but what do you want me to do, lie? It does. The answer is always Git.
pass uses Git and GPG under the hood. This lets you save your keys in a “private” repository and not worry about them being stolen, because they’ll be encrypted.
So How Do I Do That? #
First, install pass.
I’ll show you how it’s done on Fedora, since that is the correct distro. FIGHT ME!
sudo dnf install passThe Boring Stuff (GPG Setup) #
We just generate a GPG key.
gpg --gen-keyThis launches an interactive wizard. GPG will ask for your name and email. These are just metadata tied to the key, so use whatever you like; it doesn’t matter. I’m not your mom. After that, it will prompt you to set a passphrase. Do not skip this step: the passphrase is what protects your private key. If someone gets their hands on your private key and it isn’t passphrase-protected, they own everything you’ve encrypted.
Real name: Your Name
Email address: [email protected]
You selected this USER-ID:
"Your Name <[email protected]>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? OOnce your key is generated, you’ll want to set it to never expire so you don’t have to deal with renewal:
I know, I know, key rotation is important, but counterpoint: I don’t want to fucking deal with it.
gpg --edit-key <KeyID>This drops you into an interactive GPG prompt (gpg>). Here’s what each command does:
expire: enters the expiration submenu0: selects “key does not expire”y: confirms the change when GPG asks “Is this correct?”save: writes the changes to disk and exits the prompt
gpg> expire
Changing expiration time for the primary key.
Please specify how long the key should be valid.
0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Your selection? 0
Key does not expire at all
Is this correct? (y/N) y
gpg> saveLet’s Start the Fun Part (pass Setup)
#
pass init <KeyID>Then we init the Git repository.
pass git initThen add your keys.
pass insert llm/deepseekImportant note: Do not use your email as the
passentry name! That is a credential leak, albeit a minor one. If you want to add an email to your password entry, just usepass edit <key name>.
It will then prompt you for your “password”. Just paste your key there.
Alright, How Do I Use Them Now? #
Environment Variables & the Terminal #
This is the meat and potatoes of the whole thing: actually using your secrets. The best way I’ve found is using Bash command substitution, the $() syntax, which executes a command and substitutes its output. In our case, it runs pass, decrypts the secret, and inserts the value into our command at runtime.
Here’s an example using export:
export DEEPSEEK_API_KEY=$(pass show llm/deepseek)And that’s it. Your API key is now available as an environment variable without you having to manually copy and paste it like some kind of caveman.
A quick security note: The secret is still exposed to processes that inherit the environment variable. GPG protects your secrets while they’re stored, not while your applications are using them (it’s the same for .env too don’t worry). Also, if you added multiple lines of metadata to your pass entry, the command above will export those too(just pass into head, awk, sed or whatever if you only need the first line).
Clipboard #
if you need to send your secrets to that one Guy who refuses to use git and sensible secret management software here is how to copy them into your clipboard without leaking them in your Terminal
pass show llm/deepseek --clipBy default, pass copies the first line of the entry and restores your clipboard after 45 seconds.
Just remember: Your clipboard isn’t a magical secure vault. Some clipboard managers can retain copied values (Sensible DEs like KDE don’t), and sending someone an API key over an insecure channel defeats the whole point of protecting it in the first place.
Use a secure method when possible. And maybe have a conversation with the guy about his engineering choices.
Backups #
Now we’ll create a remote repository on GitHub using the GitHub CLI (or you can use the website like a chump).
Don’t forget to make it a private repo. Yes, they’re encrypted, but extra security never hurts.
gh repo create <RepositoryName> --privateThen copy the URL of the empty remote repository and set it as the origin of your pass repository.
pass git remote add origin [email protected]:<GitHubUsername>/<RepositoryName>You aren’t using SSH? First of all: ew. Second of all, here is the HTTPS version, I guess.
pass git remote add origin https://github.com/<GitHubUsername>/<RepositoryName>I’ll write a guide at a later date on how to use SSH for your Git operations, though it’s not that hard.
After that, just push the keys and you’re done.
pass git push origin mainHow Can I Access My Keys From Another Machine? #
Well, my astute reader, what you need to do is export your keys and place them in a secure place, for example, a password manager like Bitwarden. How can you do that? Here you go:
NAS Backup #
# Let's make a directory to save the files to
mkdir exported_keys
cd exported_keys
gpg --output public.gpg --armor --export <email>
gpg --output private.gpg --armor --export-secret-key <email>And then you can move them to your NAS through SCP.
Make sure that your NAS is also encrypted. If it isn’t, you just moved the vulnerability from your personal machine to your NAS. You didn’t plug any holes, you just added more availability. (The keys you exported are not encrypted by
pass; they’re the keys that do the encrypting, so they’re only as safe as your GPG passphrase.)
cd ..
scp -r exported_keys nas_username@nas_hostname:destinationDon’t forget to delete the exported files afterwards.
rm -r exported_keysPassword Manager Backup #
If you’re using a password manager like Bitwarden, you don’t actually need to export your keys to files first.
Note:
wl-copyis Wayland-specific. If you’re on X11, usexclip -selection clipboardinstead. But if you know what X11 is, why the hell are you reading this guide anyway? You don’t need to, but thanks for the engagement, my fellow basement dweller.
gpg --armor --export <KeyID> | wl-copyPaste that in your public key field inside your password manager.
Now do the same for your private key:
gpg --armor --export-secret-keys <KeyID> | wl-copyAnd there you go. No temporary files, no unnecessary cleanup, and your keys are backed up.
What Do I Do Now? #
Remember those GPG keys I told you to back up earlier?
Yeah, we’re going to need those now.
Retrieve them from your password manager or encrypted backup and transfer them securely to your new machine.
Assuming you’ve saved them in a secure directory, import them with:
gpg --import /path/to/public.gpg
gpg --import /path/to/private.gpgIf you backed up your keys to password manager instead, just copy the contents of your private-key to your clipboard and pipe them directly into GPG:
wl-paste | gpg --importYou can do the same for your public key, though the private-key export should already contain the corresponding public-key material.
Now verify that your private key was imported successfully:
gpg --list-secret-keys --keyid-format LONGThere you go. No temporary files needed. Apparently pipes really do solve everything. Sorry, Git. You’ve got competition.
Important: Make sure this is the same GPG key you used to initialize your password store. Generating a new key won’t magically decrypt files encrypted with the old one.
I assume my readers don’t need this warning but i’ll say it nonetheless, don’t leave copies of your private key lying around in your Downloads directory. Once you’ve confirmed the import works, clean up any temporary files you’ve created.
Clone the Repo #
Now for my favorite part.
Git.
Again.
Clone your private GitHub repository directly into the directory where pass expects to find its secrets:
git clone [email protected]:<GitHubUsername>/<RepositoryName>.git ~/.password-storeThis assumes ~/.password-store doesn’t already exist. If it does, check what’s in there before replacing anything.
And since we’re dealing with secrets, let’s make sure other local users can’t browse our password store:
chmod -R go-rwx ~/.password-storeThat’s It, You’re Done #
That’s literally it. Just verify that you can decrypt before celebrating.
pass show llm/deepseek > /dev/null && echo "Decryption works!"If the command succeeds, then congrats, you’re done.
Before Being Done Done #
One last thing.
When you add or modify secrets, pass automatically commits those changes to your local Git repository, assuming you’ve already initialized Git for the password store.
But Git isn’t going to push those commits by telepathy.
After making changes, run:
pass git pushAnd when you switch to another machine, pull the latest changes before modifying your secrets:
pass git pull --rebaseJust remember that changes made independently on different machines can still cause Git conflicts.
Conclusion #
And that’s pretty much it. Your secrets are now encrypted with GPG, version-controlled with Git, and backed up somewhere that isn’t the .env file you accidentally deleted at 3 AM.
No more begging your colleagues for API keys, no more manually rebuilding your environment, and one less reason to question your career choices.
Just remember to back up your GPG private key and keep your passphrase safe. Because if you lose access to either, all those beautifully encrypted secrets are going to stay beautifully encrypted. Forever.
And yes, I managed to solve yet another problem with Git.
You’re welcome.