GnuPG, pass and environment variables
nix linux wslVault is a popular secret store in the production thanks to its helm integration: the secrets are injected to the pods via helm side car. No secrets are exposed in the plaintext in the deployment pipeline. π
I would like to adopt the similar approach to manage secrets in the local development such as the database credentials, API keys, etc. They are currently all over the place:
- Some credentials are exposed as the environment variables, open to access for any app. π±
- Some app-specific credentials are set in the current shell before running the command. A malicious app may peak the command history to steal the credentials1.
- Some app-specific credentials are set in the
.envrc
, and populated bydirenv
. The secret management is more transparent to the user, but the secrets are still stored in the plaintext.
Pass and GnuPG
First, we need a secure secret store, such as pass. It encrypts the secrets with GnuPG public key, and decrypt the secrets with private key. Letβs create a new GnuPG key pair:
gpg --gen-key
... ...
pub ed25519 2025-06-15 [SC] [expires: 2028-06-14]
6C84280B702324AC558BF08E29D906D3B8CA5CA2
... ...
Donβt forget to secure the .gnupg
with correct file permissions:
chmod 700 ~/.gnupg
The key is identified as 6C84280B702324AC558BF08E29D906D3B8CA5CA2
, which will
be used to intialize the pass store, β alternatively you can use the email.
More details can be found in this guide.
pass init 6C84280B702324AC558BF08E29D906D3B8CA5CA2
pass git init
We can organze the secrets with arbitary nested folders, one benefit is that you can delete them recursively π.
β― pass insert epicgames/CODEFRESH_API_KEY
mkdir: created directory '/Users/kunxi/.password-store/epicgames'
Enter password for epicgames/CODEFRESH_API_KEY:
We can list the secrets as:
β― pass
Password Store
βββ epicgames
βββ CODEFRESH_API_KEY
Under the hood, the secrets are stored as gpg encrypted data:
β― tree ~/.password-store
/Users/kunxi/.password-store
βββ epicgames
βββ CODEFRESH_API_KEY.gpg
Then I can replace the secret in plaintext with the following snippet
in the .envrc
:
export CODEFRESH_API_KEY=$(pass epicgames/CODEFRESH_API_KEY)
You will be challenged by the pinentry
for the pass phase of the private key.
In macOS, it is pinentry-mac
as the GnuPG key pairs are stored in the system
keychain. In Linux, it is curses-based pinentry
app coupled with gpg-agent
.
Caveats under Windows
After I published this post, I suddenly realized the above solution does not solve my problem, at all β how could I populate the environment variables for IntelliJ in build and run time without exposing the secrets in plaintext?
There are several options, but none of them just works β’.
JetBrains Gateway
The JetBrains Gateway is still in beta, but it is probably the most
promising approach. It worked like VsCode remote development: the server runs
in the remote machine, WSL more concretely, then the UI client connects to the
server for rendering.
Unfortunately, the server is a puppet of the client. It does not support
direnv
, and I have no idea how to inject the environment variables from the
shell.
I really have high hope on this project, as it seems to lead to the right direction to do remote development right. /sigh.
Remote Debugging
We can run maven
or gradle
in WSL to build, test the project in the shell
configured by the direnv
, and just use IntelliJ for editing and debugging.
This might be a reasonable compromise, and it should work. But I just miss the
one-click convenience to run gradle tasks.
All-in Windows
We can try to replicate the above setup in Windows:
- There exists a abandoned Pass4Win project, which uses Gpg4Win for encryption.
- Use Git Bash for Windows as shell.
- Always
cd
to the project, baptized with environment variables populated by direnv.
This might work, but I loathe to setup another shell environment with inconsistent behaviors while I already have one. I might just stick to cygwin if I could settle for a suboptimal experience, β personal opinion, no offense.
All-in Linux
This is the exactly opposite of the above direction, we can run IntelliJ as a WSL GUI application, recommended in this guide.
I still consider the WSL is an augment for Windows developer to access Unix environment. If I decide to go this far, maybe I should just pull the trigger to ditch Windows and install NixOS on my workstation.
Closing Thoughts
Every time I am frustrated with the tug of war of Windows application and shell, I just eyed my MacBook Pro: there is a reason developers pay premium for a coherent development environment.
Footnotes
-
If you use zsh, you can set
HIST_IGNORE_SPACE
option to avoid persisting sensitive data in the history. β©