Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
Bitwarden CLI + macOS Keychain — Secret Infrastructure on macOS #
How machines in the fleet store and use Bitwarden credentials. Original setup:
ole Blu (MacBook Air, ole-blu.local). Replicated on Mac Studio 2022 (mac.lan).
Architecture #
| What | Where | Notes |
|---|---|---|
| Bitwarden API key (client id/secret) | ~/.config/bw/env, mode 0600 |
Two lines, both export-ed. Non-master secret; file-safe. |
| Bitwarden master password | macOS Keychain | Service bw-master, account <user>. NEVER in a file. |
| Service credentials (API keys, app passwords) | Bitwarden vault items | Login type (type:1); fetched per-use at runtime. |
Session keys (BW_SESSION) are transient — never written to disk.
Why this split #
- The API key only identifies/authenticates the CLI to Bitwarden's API; exposure doesn't unlock the vault. It's low-sensitivity, so a 0600 file is acceptable and enables non-interactive login.
- The master password unlocks everything. Keychain storage gives ACL-protected, prompt-on-first-access, encrypted-at-rest handling with zero file copies.
- Scripts fetch vault secrets per-use and unset them — no
export SECRET=in shell profiles (readable viaps eww, lands in every child env).
Setup on a new macOS machine #
# 1. Install CLI
brew install bitwarden-cli
# 2. Copy env file from an existing machine (secret never enters chat/session logs)
mkdir -p ~/.config/bw
scp existing-machine:.config/bw/env ~/.config/bw/env
chmod 600 ~/.config/bw/env
# 3. Log the CLI in (prints "already logged in" if done — that's fine)
source ~/.config/bw/env
bw login --apikey
# 4. Store the master password in Keychain (interactive; user types it once)
security add-generic-password -s bw-master -a "$USER" -w
# ^ prompts: password, then confirm. Never echoed.
# 5. Verify (do not print the password itself)
security find-generic-password -s bw-master -w | wc -c # non-zero
To get an API key if starting from scratch: https://vault.bitwarden.com → Settings → Security → Keys → View API key.
Unlocking in scripts #
Paste this block at the top of any script that needs vault access:
[ -f "$HOME/.config/bw/env" ] && source "$HOME/.config/bw/env"
if [ -z "${BW_PASSWORD:-}" ]; then
BW_PASSWORD=$(security find-generic-password -s bw-master -w 2>/dev/null) || exit 1
fi
export BW_PASSWORD
bw login --apikey 2>/dev/null || true
BW_SESSION=$(bw unlock --passwordenv BW_PASSWORD --raw 2>/dev/null)
# ... do work, fetching secrets per-use (below) ...
unset BW_SESSION BW_PASSWORD # clear when done
Fetching secrets #
Fetch the item ONCE and extract both fields (separate bw get calls can return
mismatched values if the item changed between calls):
ITEM=$(bw get item "Porkbun API" --session "$BW_SESSION")
API_KEY=$(echo "$ITEM" | jq -r '.login.username')
API_SECRET=$(echo "$ITEM" | jq -r '.login.password')
# Validate — empty/null means wrong name or missing field
[ -z "$API_KEY" ] || [ "$API_KEY" = "null" ] && { echo "missing fields" >&2; exit 1; }
Password-only: bw get password "Item Name" --session "$BW_SESSION".
Item naming is a contract — scripts match exact names. Current conventions:
| Bitwarden item | Username field | Password field |
|---|---|---|
| Porkbun API | pk1_... API key | sk1_... secret |
| Bluesky App Password | handle | app password |
Pitfalls #
sourcesets shell variables, NOT environment variables. AlwaysexportBW_* vars, orbw unlock --passwordenv BW_PASSWORDsilently fails (empty--raw).bw login --apikey --rawreturns nothing when already logged in. Use non-rawbw login --apikeyand gate onbw unlockinstead.- First Keychain read from a new process pops a macOS approval dialog ("security wants to use your confidential information") — one-time per item per session.
bw getmatches the FIRST duplicate item name. After creating: checkbw list items --search "Name" --session "$BW_SESSION" | jq 'length'→ 1.- CLI caches vault data locally. After editing the vault elsewhere,
bw sync. - Never paste live secret values into AI agent sessions or shell history — they persist. Reference the item name instead. Rotate + purge anything that leaked.
- First Keychain read from a new process may pop a macOS dialog — expected.
Fleet status #
| Machine | Status |
|---|---|
| ole Blu (MacBook Air, ole-blu.local) | Original — full setup |
| Mac Studio 2022 (mac.lan) | Complete 2026-09-11: env via scp, login verified, Keychain item added, unlock + sync verified (664 items) |