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/direnvrc or ~/.config/direnv/lib/*.sh. This file is loaded before your .envrc and 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
  fi

I 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 MYVAR2

So 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"
  done

Did 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)"
fi

Which 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_PASSWORD

And 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_PASSWORD

What do you think? Pretty nice isn’t it