Use Bitwarden and direnv to inject env variables
Posted in linux on March 2, 2026 by Adrian Wyssmann ‐ 5 min read
Last year I stumbled upon Loading credentials from Bitwarden with direnv which explains how direnv and bitwarden can be used together to keep infrastructure credentials safe - many thanks to the author of the post. Taking that into account, I slightly modified the whole setup to my needs.
Direnv Scripts
One essential thing of direnv is, to be able to extend the std-lib functions:
Itβs also possible to create your own extensions by creating a bash file at
~/.config/direnv/direnvrcor~/.config/direnv/lib/*.sh. This file is loaded before your.envrcand thus allows you to make your own extensions to direnv.
The blog post I referred above, in essence shows how the developer created a script, which does the following
- Guide the user to login to bitwarden
- Get an item based on name of item and the folder in which the item is stored
- Extracts the information from the result (bitwarden uses json formatted messages)
- exports the password into an environment variables
So far amazing. What I did not like is, that I have to unlock bitwarden every time. I understand this is probably more secure, but I find it annoying, so I adjusted the login as follows:
if [[ -z $BW_SESSION ]]; then
echo "Failed to log into bitwarden. Ensure you're logged in with bw login, and check your password" >&2
bw_auth
fiI introduced a new function bw_auth which looks as follows
# @description To keep your environment secure but convenient, weβll use a temporary session cache
bw_auth() {
local SESSION_FILE="/dev/shm/bw_session_$USER"
# 1. Check if session file exists and is still valid
if [[ -f "$SESSION_FILE" ]]; then
export BW_SESSION=$(cat "$SESSION_FILE")
# Verify the session actually works
if ! bw status | grep -q "unlocked"; then
echo "β bw status $(bw status)"
unset BW_SESSION
rm "$SESSION_FILE"
fi
fi
# 2. If no valid session, unlock
if [[ -z "$BW_SESSION" ]]; then
echo "π Bitwarden locked. Please provide Master Password:"
local NEW_SESSION=$(bw unlock --raw)
if [[ $? -eq 0 ]]; then
export BW_SESSION="$NEW_SESSION"
echo "$NEW_SESSION"
echo "$BW_SESSION" > "$SESSION_FILE"
chmod 600 "$SESSION_FILE"
echo "β
Session cached in RAM."
else
echo "β Unlock failed."
return 1
fi
fi
}The function checks if there is a SESSION_FILE in memory (/dev/shm) - if so, it will use the BW_SESSION in the file and export it. Otherwise I am asked to login to bitwarden. This means I only have to login one time to bitwarden, after I start my computer. Still shutting down or restarting the computer ensures that the file in the memory is gone. Still less secure than login every time I want to inject variables, but good enough for me.
Bitwarden Secret Manager
When you have a paid account of Bitwarden, you might also get Bitwarden Secret Manager, which IMHO is more suitable, as it only grants access to the secrets needed and not the whole password vault.
{{img src=“bws.png” caption=“Bitwarden Secrets Manager with some example projects and secrets”}}
As in bws there can be multiple secrets with the same name, the cli works with secret ids and not names. We have to take that into account when writing the function. Similar to the bw_env I call the function by providing a list of variable names:
bws_env MYVAR1 MYVAR2So I iterate trough the variables using the names
# Iterate through requested variables
for environment_variable_name in "$@"; do
local QUERY=".[] | select(.key == \"$environment_variable_name\")"
local SECRET=$(bws secret list | jq -r "$QUERY | .value")
if [[ -z $SECRET || $SECRET == "null" ]]; then
echo "β Failed to retrieve credential for $environment_variable_name" >&2
return 1
fi
export "$environment_variable_name=$SECRET"
echo "β
Exported $environment_variable_name"
doneDid you know?
It will return the first occurence of the name found in the list bws secret list returns, so if you have multiple variables with the same name but different secret value, organize them into projects.
In order to be able to use different projects I extend the function with an optional -p parameter:
local PROJECT_NAME=""
local PROJECT_ID=""
local OPTIND
while getopts "p:" opt; do
case "$opt" in
p) PROJECT_NAME=$OPTARG ;;
*)
echo "Usage: bws_env [-p project_name] VAR1 VAR2 ..." >&2
return 1
;;
esac
done
shift $((OPTIND - 1))
# If a project name was provided, find its ID
if [[ -n "$PROJECT_NAME" ]]; then
PROJECT_ID=$(bws project list | jq -r ".[] | select(.name == \"$PROJECT_NAME\") | .id")
if [[ -z "$PROJECT_ID" ]]; then
echo "β Project '$PROJECT_NAME' not found" >&2
return 1
fi
echo "π Filtering by Project: $PROJECT_NAME ($PROJECT_ID)"
fiWhich we then use in the query
# Append project filter to the JQ query if ID exists
if [[ -n "$PROJECT_ID" ]]; then
QUERY="$QUERY | select(.projectId == \"$PROJECT_ID\")"
fi
local SECRET=$(bws secret list | jq -r "$QUERY | .value")So that this works you have to create a machine specific token and export it as BWS_ACCESS_TOKEN.
Here is my .envrc …
bws_env -p personal B2_ACCOUNT_KEY B2_ACCOUNT_ID RESTIC_PASSWORDAnd this happens if I enter the directory or run direnv allow .
direnv: loading ~/Downloads/restore/.envrc
π Filtering by Project: personal (73ca6e0e-a90d-4094-99f6-b3f3008d49b4)
β
Exported B2_ACCOUNT_KEY
β
Exported B2_ACCOUNT_ID
β
Exported RESTIC_PASSWORD
direnv: export +B2_ACCOUNT_ID +B2_ACCOUNT_KEY +RESTIC_PASSWORDWhat do you think? Pretty nice isn’t it