TL;DR: Replace plaintext
.envsecrets with Bitwarden CLI.PASSWORD=$(bw get password "Item" --raw). Encrypted, auditable, multi-device sync, no plaintext on disk.
Optional feature: Use Bitwarden CLI to manage credentials instead of plaintext .env files.
Traditional approach (plaintext secrets):
# .env.secrets
DB_PASSWORD=my_secret_password
OFFSITE_SSH_PASSWORD=another_secretProblems:
- ❌ Plaintext secrets on disk
- ❌ No audit trail (who accessed what when?)
- ❌ Manual rotation (edit file, restart services)
- ❌ Git-unfriendly (must be .gitignored)
- ❌ No multi-device sync
Vaultwarden approach (encrypted vault):
# Retrieve credential on-demand
PASSWORD=$(bw get password "Database Production" --raw)Benefits:
- ✅ Encrypted at rest (Vaultwarden vault encrypted)
- ✅ Audit trail (Vaultwarden logs access)
- ✅ Easy rotation (update in vault, no file edits)
- ✅ Multi-device sync (same secrets everywhere)
- ✅ No plaintext on disk (credentials only in memory)
- Vaultwarden server running and accessible
- Bitwarden CLI (
bw) installed on the system - Credentials stored in Vaultwarden vault
- Master password available (can be in
.env.secretsor environment)
# Download latest release
curl -fsSL https://vault.bitwarden.com/download/?app=cli&platform=linux -o bw.zip
unzip bw.zip
sudo mv bw /usr/local/bin/
sudo chmod +x /usr/local/bin/bw
# Verify installation
bw --versionAlternative (via npm):
sudo npm install -g @bitwarden/cli# Set custom server URL (if using self-hosted Vaultwarden)
bw config server https://vaultwarden.example.com
# Login (one-time)
bw login your-email@example.comCreate items in Vaultwarden web UI or via CLI:
# Example: Add SSH password
bw create item \
--name "Production SSH Password" \
--login \
--username root \
--password "your-secure-password"Initialize Vaultwarden session once per script execution:
#!/bin/bash
# Initialize Vaultwarden session
init_vaultwarden_session() {
local master_password
# Option A: Master password from environment
if [[ -n "${BW_MASTER_PASSWORD:-}" ]]; then
master_password="$BW_MASTER_PASSWORD"
# Option B: Master password from .env.secrets (less secure)
elif [[ -f "$HOME/.env.secrets" ]]; then
master_password=$(grep "^VAULTWARDEN_MASTER_PASSWORD=" "$HOME/.env.secrets" | cut -d= -f2 | tr -d ' \n\r')
else
echo "ERROR: Master password not found" >&2
return 1
fi
# Check login status
if ! bw status 2>/dev/null | grep -q '"status":"unlocked"'; then
bw login "your-email@example.com" --passwordenv BW_MASTER_PASSWORD --raw >/dev/null 2>&1 || true
fi
# Get session token
export BW_MASTER_PASSWORD="$master_password"
BW_SESSION=$(bw unlock --passwordenv BW_MASTER_PASSWORD --raw 2>/dev/null)
export BW_SESSION
if [[ -z "$BW_SESSION" ]]; then
echo "ERROR: Failed to unlock Vaultwarden vault" >&2
return 1
fi
echo "✅ Vaultwarden session initialized"
}
# Usage
init_vaultwarden_session || exit 1# Get password by item name
PASSWORD=$(bw get password "Item Name" --raw)
# Get password by item ID (faster)
PASSWORD=$(bw get password "a1b2c3d4-..." --raw)
# Get username
USERNAME=$(bw get username "Item Name" --raw)
# Get custom field
API_KEY=$(bw get item "Item Name" | jq -r '.fields[] | select(.name=="api_key") | .value')Support both Vaultwarden and .env files:
#!/bin/bash
get_credential() {
local item_name="$1"
local env_var="$2"
local value
# Try Vaultwarden first (if available)
if command -v bw >/dev/null 2>&1 && [[ -n "${BW_SESSION:-}" ]]; then
value=$(bw get password "$item_name" --raw 2>/dev/null)
if [[ -n "$value" ]]; then
echo "$value"
return 0
fi
fi
# Fallback: .env.secrets
if [[ -f "$HOME/.env.secrets" ]]; then
value=$(grep "^${env_var}=" "$HOME/.env.secrets" | cut -d= -f2 | awk '{print $1}')
if [[ -n "$value" ]]; then
echo "$value"
return 0
fi
fi
echo "ERROR: Credential not found: $item_name / $env_var" >&2
return 1
}
# Usage
DB_PASSWORD=$(get_credential "Database Production" "DB_PASSWORD")#!/bin/bash
# AIDE Database Update with Vaultwarden Integration
# Initialize Vaultwarden (optional)
if command -v bw >/dev/null 2>&1; then
init_vaultwarden_session
fi
# Get offsite backup credentials
if [[ -n "${BW_SESSION:-}" ]]; then
# Vaultwarden method
OFFSITE_SSH_PASSWORD=$(bw get password "Offsite Backup SSH" --raw 2>/dev/null)
else
# Fallback: .env.secrets
OFFSITE_SSH_PASSWORD=$(grep "^OFFSITE_SSH_PASSWORD=" .env.secrets | cut -d= -f2 | awk '{print $1}')
fi
# Use credential for offsite backup
sshpass -p "$OFFSITE_SSH_PASSWORD" rsync -avz /var/lib/aide/aide.db \
backup@backup-server:/backups/aide/BW_SESSION token is sensitive - treat it like a password:
# ✅ Good: Export in same shell
export BW_SESSION="..."
# ❌ Bad: Store in file
echo "$BW_SESSION" > /tmp/session # Never do this!Session timeout: Vaultwarden sessions expire (default: 1 hour). Re-initialize if needed.
Options (from most to least secure):
-
Environment variable (set externally, not in script)
export BW_MASTER_PASSWORD="..." ./script.sh
-
Prompt user (interactive mode)
read -s -p "Master password: " BW_MASTER_PASSWORD
-
.env.secretsfile (encrypted filesystem recommended)# Still better than storing individual secrets in plaintext VAULTWARDEN_MASTER_PASSWORD=your_master_password -
TPM/Hardware security (advanced)
- Store master password in TPM
- Use systemd credentials
If using self-hosted Vaultwarden:
- ✅ Use HTTPS (valid certificate)
- ✅ Use VPN for remote access
- ✅ Enable 2FA on Vaultwarden account
- ✅ Regular security updates
Enable Vaultwarden event logging:
# Check recent access
bw list events --organizationid <org-id>
# Export audit log
bw export --format json --output audit.json# List all secrets in .env.secrets
grep "^[A-Z_]*=" .env.secrets | cut -d= -f1For each secret:
bw create item \
--name "Secret Name" \
--login \
--username "" \
--password "value_from_env"Replace:
# Old
PASSWORD=$(grep "^DB_PASSWORD=" .env.secrets | cut -d= -f2)With:
# New
PASSWORD=$(bw get password "Database Production" --raw)Run scripts in dry-run mode to verify Vaultwarden integration works.
After successful migration:
# Backup first (just in case)
cp .env.secrets .env.secrets.backup
# Securely delete
shred -vfz -n 3 .env.secretsCause: Session token expired or invalid.
Solution:
# Re-initialize session
unset BW_SESSION
init_vaultwarden_sessionCause: Bitwarden CLI not installed.
Solution:
# Install via npm
sudo npm install -g @bitwarden/cli
# Or download binary
curl -fsSL https://vault.bitwarden.com/download/?app=cli&platform=linux -o bw.zip
unzip bw.zip && sudo mv bw /usr/local/bin/Cause: Wrong master password.
Solution:
- Verify
BW_MASTER_PASSWORDis correct - Check for trailing spaces/newlines
- Try manual unlock:
bw unlock
Cause: Self-signed certificate or custom CA.
Solution:
# Add custom CA certificate
export NODE_EXTRA_CA_CERTS="/path/to/ca-certificate.crt"-
Use session initialization once per script
- Don't unlock vault for every credential
- Cache
BW_SESSIONfor the script duration
-
Implement graceful fallback
- Support both Vaultwarden and
.env.secrets - Makes migration easier
- Support both Vaultwarden and
-
Use item IDs for performance
bw get password <id>is faster than searching by name- Get ID once:
bw list items | jq -r '.[] | select(.name=="Item") | .id'
-
Enable 2FA on Vaultwarden account
- Protects against master password compromise
- Use TOTP or hardware key
-
Rotate master password regularly
- Recommended: Every 6-12 months
- Update in all scripts after rotation
-
Monitor Vaultwarden access logs
- Set up alerts for suspicious activity
- Review access patterns regularly
- Bitwarden CLI Documentation
- Vaultwarden Wiki
- SETUP.md - AIDE installation guide
- BEST_PRACTICES.md - Production recommendations
Version: 1.0.0 Created: 2026-01-04 Author: Marc Allgeier (fidpa)