Skip to main content

Setting secrets in env vars

An AH-1G Aircraft Maintenance Test Flight Handbook and handwritten checklist for rotary wing helicopters from 1971-1972. Both handbooks are bound with metal rings.
Table of Contents

Sometimes you need to set environment variables with secrets, API keys or tokens, but they can be susceptible to exfiltration by malware, as seen during the recent Shai-Hulud attack.

For publishing to PyPI, it’s strongly recommended to use Trusted Publishing rather than managing long-lived tokens on your machine.

For other credentials, here’s how you can set them as env vars with 1Password CLI and direnv on macOS.

1Password #

The 1Password password manager has a command-line interface called op. Thanks to Simon Willison for his useful TIL post about it.

First install the CLI:

brew install 1password-cli

Then open 1Password > Settings > Developer > Integrate with 1Password CLI.

Then you can use the op command with your password item’s name in 1Password:

$ op item get "My secret item" --fields password
[use 'op item get xkm4wrtpvnq8hcjd3yfzs2belg --reveal' to reveal]

This obscures the actual password, and gives the item’s ID. You can use this ID:

$ op item get xkm4wrtpvnq8hcjd3yfzs2belg --fields password
[use 'op item get xkm4wrtpvnq8hcjd3yfzs2belg --reveal' to reveal]

Use --reveal to see the actual password:

$ op item get "My secret item" --fields password --reveal
my-secret-password

$ op item get xkm4wrtpvnq8hcjd3yfzs2belg --fields password --reveal
my-secret-password

Alternatively, use a secret reference. Open the item in 1Password, next to the password click ⌄ and select Copy Secret Reference then op read it:

$ op read "op://MyVault/xkm4wrtpvnq8hcjd3yfzs2belg/password"
my-secret-password

The secret reference is made up of the vault name and item ID, and op read doesn’t need --reveal.

Take your pick which one you prefer.

direnv #

direnv is a handy shell tool that can load and unload environment variables depending on your current directory.

Next, let’s set up direnv so that when you cd into a certain directory, it fetches the password from 1Password and sets it as an env var. When cding out, the env var is unloaded.

Install direnv:

brew install direnv

Then hook direnv into your shell. If you use Oh My Zsh, edit ~/.zshrc and add direnv to the plugins list:

plugins=(git gitfast gh you-should-use direnv)

Restart/reload your shell:

source ~/.zshrc

(I first got “Warning: direnv not found. Please install direnv and ensure it’s in your PATH before using this plugin” so needed to move eval "$(/opt/homebrew/bin/brew shellenv)" before source $ZSH/oh-my-zsh.sh.)

Now, in your project directory, create a file called .envrc containing an env var export that calls op:

# .envrc
export DEBUG=1
export MY_SECRET=$(op read "op://MyVault/xkm4wrtpvnq8hcjd3yfzs2belg/password")

On first run, you need to explicitly allow direnv to run for this directory. You also need to do this when editing .envrc later:

direnv allow .

If 1Password is set up with a passkey, it will now prompt to authorise:

$ cd my-project
direnv: loading ~/my-project/.envrc
direnv: export +DEBUG +MY_SECRET

Sometimes it shows a warning because of the 1Password UI prompt, but it’s okay:

$ cd my-project
direnv: loading ~/my-project/.envrc
direnv: ([/opt/homebrew/bin/direnv export zsh]) is taking a while to execute. Use CTRL-C to give up.
direnv: export +DEBUG +MY_SECRET

Then you can access the env var in your scripts:

$ echo ${DEBUG-nope}
1
$ echo ${MY_SECRET-nope}
my-secret-password

But when leaving the directory, you can no longer access it:

$ cd ..
direnv: unloading
$ echo ${DEBUG-nope}
nope
$ echo ${MY_SECRET-nope}
nope

Bonus #

1Password offers Team accounts to open-source projects as a way of giving back to the open source community.


Header photo: Slaymaker Barry Co. Sprocket Locks in The Bearings (vol. XII no. 1, 1st August 1895) in the Smithsonian Libraries and Archives, with no known copyright restrictions.