add bw-vault: Vaultwarden CLI wrapper with credential blindness for Claude Code
This commit is contained in:
parent
fb3beffe18
commit
70208da97e
4 changed files with 518 additions and 0 deletions
49
dot_claude/skills/vaultwarden.md
Normal file
49
dot_claude/skills/vaultwarden.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
name: vaultwarden
|
||||
description: Manage passwords in Vaultwarden. Use when needing credentials
|
||||
for services, databases, APIs, or when storing new secrets.
|
||||
---
|
||||
|
||||
## CRITICAL: Credential Blindness
|
||||
NEVER use commands that would print passwords to stdout.
|
||||
Passwords must NEVER appear in tool output sent to Anthropic's API.
|
||||
|
||||
## Available commands (each triggers Touch ID)
|
||||
|
||||
### Search (safe — returns metadata only)
|
||||
```bash
|
||||
bw-vault search <query>
|
||||
```
|
||||
Returns: item name, username, URL, id — NO passwords
|
||||
|
||||
### Inject password into a command (safe — password never in output)
|
||||
```bash
|
||||
bw-vault inject <item-name-or-id> --as <ENV_VAR> -- <command...>
|
||||
```
|
||||
Example: `bw-vault inject "prod-db" --as PGPASSWORD -- psql -h db.local -U admin`
|
||||
|
||||
### Copy to clipboard (safe — only "Copied" message returned)
|
||||
```bash
|
||||
bw-vault copy <item-name-or-id> [field]
|
||||
```
|
||||
field defaults to "password", can be "username", "totp", "uri"
|
||||
|
||||
### Write to temp file (safe — only file path returned)
|
||||
```bash
|
||||
bw-vault file <item-name-or-id> /tmp/secret-XXXX
|
||||
```
|
||||
|
||||
### Create new item (password auto-generated)
|
||||
```bash
|
||||
bw-vault create
|
||||
```
|
||||
|
||||
### Edit existing item
|
||||
```bash
|
||||
bw-vault edit <item-name-or-id>
|
||||
```
|
||||
|
||||
## NEVER DO
|
||||
- `bw get password <id>` — would leak to API
|
||||
- `cat /tmp/secret-XXXX` — would leak file contents to API
|
||||
- `echo $PGPASSWORD` — would leak env var to API
|
||||
- Any command that prints a secret value to stdout
|
||||
274
dot_local/bin/bw-vault-unlock
Normal file
274
dot_local/bin/bw-vault-unlock
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
#!/bin/bash
|
||||
# bw-vault-unlock — Privileged Bitwarden CLI wrapper
|
||||
# Installed to /usr/local/bin/bw-vault-unlock (root:wheel 0755)
|
||||
# Called via sudo from ~/.local/bin/bw-vault
|
||||
#
|
||||
# Security: passwords never printed to stdout. Only metadata, exit codes,
|
||||
# and "Copied to clipboard" messages are returned.
|
||||
set -euo pipefail
|
||||
export PATH="/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin"
|
||||
|
||||
BW_BIN="/opt/homebrew/bin/bw"
|
||||
BW_HASH_FILE="/var/root/.bw-hash"
|
||||
|
||||
# Verify bw binary integrity
|
||||
if [[ -f "$BW_HASH_FILE" ]]; then
|
||||
EXPECTED_HASH=$(/usr/bin/head -1 "$BW_HASH_FILE")
|
||||
ACTUAL_HASH=$(/usr/bin/shasum -a 256 "$BW_BIN" | /usr/bin/awk '{print $1}')
|
||||
if [[ "$EXPECTED_HASH" != "$ACTUAL_HASH" ]]; then
|
||||
echo "ERROR: bw binary integrity check failed" >&2
|
||||
echo "Expected: $EXPECTED_HASH" >&2
|
||||
echo "Actual: $ACTUAL_HASH" >&2
|
||||
echo "Run: sudo shasum -a 256 /opt/homebrew/bin/bw | awk '{print \$1}' > /var/root/.bw-hash" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
export BITWARDENCLI_APPDATA_DIR="/var/root/.bw-data"
|
||||
|
||||
# Subcommand allowlist
|
||||
SUBCMD="${1:-}"
|
||||
case "$SUBCMD" in
|
||||
search|inject|copy|file|create|edit) ;;
|
||||
*)
|
||||
echo "Usage: bw-vault {search|inject|copy|file|create|edit} [args...]" >&2
|
||||
echo "" >&2
|
||||
echo "Commands:" >&2
|
||||
echo " search <query> Search vault (metadata only)" >&2
|
||||
echo " inject <id> --as <VAR> -- <cmd...> Run command with secret as env var" >&2
|
||||
echo " copy <id> [field] Copy secret to clipboard" >&2
|
||||
echo " file <id> <path> Write secret to file (0600)" >&2
|
||||
echo " create Create new vault item" >&2
|
||||
echo " edit <id> Edit existing vault item" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Read credentials using awk (not source — prevents env leakage)
|
||||
CRED_FILE="/var/root/.bw-credentials"
|
||||
if [[ ! -f "$CRED_FILE" ]]; then
|
||||
echo "ERROR: credentials file not found at $CRED_FILE" >&2
|
||||
echo "Run the setup script first: sudo bw-vault-setup" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BW_CLIENTID=$(/usr/bin/awk -F= '/^BW_CLIENTID=/{print $2}' "$CRED_FILE")
|
||||
BW_CLIENTSECRET=$(/usr/bin/awk -F= '/^BW_CLIENTSECRET=/{print $2}' "$CRED_FILE")
|
||||
BW_PASSWORD=$(/usr/bin/awk -F= '/^BW_PASSWORD=/{print $2}' "$CRED_FILE")
|
||||
|
||||
cleanup() {
|
||||
# Logout on exit (best-effort)
|
||||
env -i PATH="$PATH" BITWARDENCLI_APPDATA_DIR="$BITWARDENCLI_APPDATA_DIR" \
|
||||
"$BW_BIN" logout --nointeraction 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Login with API key (clean env, idempotent)
|
||||
env -i PATH="$PATH" BITWARDENCLI_APPDATA_DIR="$BITWARDENCLI_APPDATA_DIR" \
|
||||
BW_CLIENTID="$BW_CLIENTID" BW_CLIENTSECRET="$BW_CLIENTSECRET" \
|
||||
"$BW_BIN" login --apikey --nointeraction 2>/dev/null || true
|
||||
|
||||
# Unlock vault — password via stdin
|
||||
SESSION=$(echo "$BW_PASSWORD" | env -i PATH="$PATH" \
|
||||
BITWARDENCLI_APPDATA_DIR="$BITWARDENCLI_APPDATA_DIR" \
|
||||
"$BW_BIN" unlock --passwordfile /dev/stdin --raw 2>/dev/null)
|
||||
|
||||
# Clear credentials from memory ASAP
|
||||
unset BW_CLIENTID BW_CLIENTSECRET BW_PASSWORD
|
||||
|
||||
if [[ -z "$SESSION" ]]; then
|
||||
echo "ERROR: failed to unlock vault" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Helper to run bw commands with session
|
||||
bw_cmd() {
|
||||
env -i PATH="$PATH" BITWARDENCLI_APPDATA_DIR="$BITWARDENCLI_APPDATA_DIR" \
|
||||
"$BW_BIN" "$@" --session "$SESSION" 2>/dev/null
|
||||
}
|
||||
|
||||
case "$SUBCMD" in
|
||||
search)
|
||||
shift
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "ERROR: search requires a query" >&2
|
||||
exit 1
|
||||
fi
|
||||
QUERY="$*"
|
||||
# Return metadata only — strip password, notes, totp, fields
|
||||
bw_cmd list items --search "$QUERY" | \
|
||||
/usr/bin/python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
items = json.load(sys.stdin)
|
||||
except json.JSONDecodeError:
|
||||
print('[]')
|
||||
sys.exit(0)
|
||||
for i in items:
|
||||
i.pop('notes', None)
|
||||
i.pop('fields', None)
|
||||
i.pop('passwordHistory', None)
|
||||
if 'login' in i and i['login']:
|
||||
i['login'].pop('password', None)
|
||||
i['login'].pop('totp', None)
|
||||
if 'card' in i and i['card']:
|
||||
i['card'].pop('number', None)
|
||||
i['card'].pop('code', None)
|
||||
if 'identity' in i and i['identity']:
|
||||
i['identity'].pop('ssn', None)
|
||||
json.dump(items, sys.stdout, indent=2)
|
||||
"
|
||||
;;
|
||||
inject)
|
||||
shift
|
||||
if [[ $# -lt 4 ]]; then
|
||||
echo "ERROR: usage: inject <item-id> --as <VAR> -- <command...>" >&2
|
||||
exit 1
|
||||
fi
|
||||
ITEM_ID="$1"; shift
|
||||
if [[ "$1" != "--as" ]]; then
|
||||
echo "ERROR: expected --as, got '$1'" >&2
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
VAR_NAME="$1"; shift
|
||||
# Validate var name (alphanumeric + underscore only)
|
||||
if [[ ! "$VAR_NAME" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
|
||||
echo "ERROR: invalid env var name '$VAR_NAME'" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$1" != "--" ]]; then
|
||||
echo "ERROR: expected --, got '$1'" >&2
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "ERROR: no command specified after --" >&2
|
||||
exit 1
|
||||
fi
|
||||
SECRET=$(bw_cmd get password "$ITEM_ID")
|
||||
if [[ -z "$SECRET" ]]; then
|
||||
echo "ERROR: failed to retrieve password for '$ITEM_ID'" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Run the target command with the secret as an env var
|
||||
export "$VAR_NAME=$SECRET"
|
||||
unset SECRET
|
||||
"$@"
|
||||
;;
|
||||
copy)
|
||||
shift
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "ERROR: copy requires an item id/name" >&2
|
||||
exit 1
|
||||
fi
|
||||
ITEM_ID="$1"
|
||||
FIELD="${2:-password}"
|
||||
bw_cmd get "$FIELD" "$ITEM_ID" | /usr/bin/pbcopy
|
||||
echo "Copied $FIELD to clipboard"
|
||||
;;
|
||||
file)
|
||||
shift
|
||||
if [[ $# -lt 2 ]]; then
|
||||
echo "ERROR: usage: file <item-id> <output-path>" >&2
|
||||
exit 1
|
||||
fi
|
||||
ITEM_ID="$1"
|
||||
OUTPATH="$2"
|
||||
umask 077
|
||||
bw_cmd get password "$ITEM_ID" > "$OUTPATH"
|
||||
chmod 600 "$OUTPATH"
|
||||
echo "Written to $OUTPATH (mode 0600)"
|
||||
;;
|
||||
create)
|
||||
shift
|
||||
echo "Interactive item creation:"
|
||||
read -rp "Item name: " ITEM_NAME
|
||||
read -rp "Username: " ITEM_USER
|
||||
read -rp "URL (optional): " ITEM_URL
|
||||
read -rp "Generate password? [Y/n]: " GEN_PASS
|
||||
if [[ "${GEN_PASS,,}" != "n" ]]; then
|
||||
ITEM_PASS=$(bw_cmd generate -ulns --length 32)
|
||||
else
|
||||
read -rsp "Password: " ITEM_PASS
|
||||
echo
|
||||
fi
|
||||
# Build the item JSON
|
||||
ITEM_JSON=$(/usr/bin/python3 -c "
|
||||
import json, sys
|
||||
item = {
|
||||
'organizationId': None,
|
||||
'folderId': None,
|
||||
'type': 1,
|
||||
'name': sys.argv[1],
|
||||
'notes': None,
|
||||
'favorite': False,
|
||||
'login': {
|
||||
'uris': [{'match': None, 'uri': sys.argv[3]}] if sys.argv[3] else [],
|
||||
'username': sys.argv[2],
|
||||
'password': sys.argv[4],
|
||||
'totp': None
|
||||
},
|
||||
'fields': [],
|
||||
'reprompt': 0
|
||||
}
|
||||
print(json.dumps(item))
|
||||
" "$ITEM_NAME" "$ITEM_USER" "$ITEM_URL" "$ITEM_PASS")
|
||||
unset ITEM_PASS
|
||||
RESULT=$(echo "$ITEM_JSON" | bw_cmd encode | bw_cmd create item)
|
||||
CREATED_ID=$(echo "$RESULT" | /usr/bin/python3 -c "import sys,json; print(json.load(sys.stdin).get('id','unknown'))" 2>/dev/null || echo "unknown")
|
||||
echo "Created item: $CREATED_ID (use 'bw-vault copy $CREATED_ID' to retrieve password)"
|
||||
;;
|
||||
edit)
|
||||
shift
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "ERROR: edit requires an item id/name" >&2
|
||||
exit 1
|
||||
fi
|
||||
ITEM_ID="$1"
|
||||
# Get current item
|
||||
CURRENT=$(bw_cmd get item "$ITEM_ID")
|
||||
if [[ -z "$CURRENT" ]]; then
|
||||
echo "ERROR: item '$ITEM_ID' not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
CURRENT_NAME=$(echo "$CURRENT" | /usr/bin/python3 -c "import sys,json; print(json.load(sys.stdin).get('name',''))")
|
||||
CURRENT_USER=$(echo "$CURRENT" | /usr/bin/python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('login',{}).get('username','') if d.get('login') else '')")
|
||||
echo "Editing: $CURRENT_NAME (user: $CURRENT_USER)"
|
||||
read -rp "New username (enter to keep): " NEW_USER
|
||||
read -rp "New password? [y/N]: " CHANGE_PASS
|
||||
if [[ "${CHANGE_PASS,,}" == "y" ]]; then
|
||||
read -rp "Generate password? [Y/n]: " GEN_PASS
|
||||
if [[ "${GEN_PASS,,}" != "n" ]]; then
|
||||
NEW_PASS=$(bw_cmd generate -ulns --length 32)
|
||||
else
|
||||
read -rsp "New password: " NEW_PASS
|
||||
echo
|
||||
fi
|
||||
UPDATED=$(echo "$CURRENT" | /usr/bin/python3 -c "
|
||||
import sys, json
|
||||
item = json.load(sys.stdin)
|
||||
new_user = sys.argv[1] if sys.argv[1] else None
|
||||
new_pass = sys.argv[2] if sys.argv[2] else None
|
||||
if new_user and item.get('login'):
|
||||
item['login']['username'] = new_user
|
||||
if new_pass and item.get('login'):
|
||||
item['login']['password'] = new_pass
|
||||
print(json.dumps(item))
|
||||
" "$NEW_USER" "$NEW_PASS")
|
||||
unset NEW_PASS
|
||||
else
|
||||
UPDATED=$(echo "$CURRENT" | /usr/bin/python3 -c "
|
||||
import sys, json
|
||||
item = json.load(sys.stdin)
|
||||
new_user = sys.argv[1] if sys.argv[1] else None
|
||||
if new_user and item.get('login'):
|
||||
item['login']['username'] = new_user
|
||||
print(json.dumps(item))
|
||||
" "$NEW_USER")
|
||||
fi
|
||||
ITEM_REAL_ID=$(echo "$CURRENT" | /usr/bin/python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
||||
echo "$UPDATED" | bw_cmd encode | bw_cmd edit item "$ITEM_REAL_ID" >/dev/null
|
||||
echo "Updated item: $ITEM_REAL_ID"
|
||||
;;
|
||||
esac
|
||||
3
dot_local/bin/executable_bw-vault
Normal file
3
dot_local/bin/executable_bw-vault
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
# User-facing wrapper — triggers Touch ID via sudo → runs privileged bw operation
|
||||
sudo /usr/local/bin/bw-vault-unlock "$@"
|
||||
192
dot_local/bin/executable_bw-vault-setup
Normal file
192
dot_local/bin/executable_bw-vault-setup
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
#!/bin/bash
|
||||
# bw-vault-setup — One-time setup for bw-vault (Vaultwarden CLI integration)
|
||||
# Run this script manually: bash ~/.local/bin/bw-vault-setup
|
||||
set -euo pipefail
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${GREEN}[+]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
||||
error() { echo -e "${RED}[✗]${NC} $*" >&2; }
|
||||
|
||||
# Check we're running as the user (not root)
|
||||
if [[ $EUID -eq 0 ]]; then
|
||||
error "Run this script as your user (not sudo). It will prompt for sudo when needed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "============================================"
|
||||
echo " bw-vault Setup — Vaultwarden CLI for Claude Code"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
|
||||
# Step 1: Fix Homebrew permissions if needed
|
||||
info "Checking Homebrew..."
|
||||
if ! brew --version >/dev/null 2>&1; then
|
||||
error "Homebrew not found. Install it first: https://brew.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if brew dirs are writable
|
||||
if [[ ! -w /opt/homebrew/Cellar ]]; then
|
||||
warn "Homebrew directories not writable. Fixing ownership..."
|
||||
sudo chown -R "$(whoami)" /opt/homebrew
|
||||
fi
|
||||
|
||||
# Step 2: Install dependencies
|
||||
info "Installing bitwarden-cli and pam-reattach..."
|
||||
brew install bitwarden-cli pam-reattach 2>/dev/null || {
|
||||
# May already be installed
|
||||
brew upgrade bitwarden-cli pam-reattach 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Verify installations
|
||||
if ! command -v bw >/dev/null; then
|
||||
error "bitwarden-cli installation failed"
|
||||
exit 1
|
||||
fi
|
||||
info "bw CLI version: $(bw --version)"
|
||||
|
||||
if [[ ! -f /opt/homebrew/lib/pam/pam_reattach.so ]]; then
|
||||
error "pam-reattach installation failed"
|
||||
exit 1
|
||||
fi
|
||||
info "pam-reattach installed"
|
||||
|
||||
# Step 3: Configure Touch ID for sudo (with tmux support)
|
||||
info "Configuring Touch ID for sudo..."
|
||||
SUDO_LOCAL="/etc/pam.d/sudo_local"
|
||||
if [[ -f "$SUDO_LOCAL" ]] && grep -q pam_tid "$SUDO_LOCAL"; then
|
||||
info "Touch ID for sudo already configured"
|
||||
else
|
||||
sudo bash -c "cat > $SUDO_LOCAL << 'PAMEOF'
|
||||
# pam-reattach: required for Touch ID to work in tmux/screen
|
||||
auth optional /opt/homebrew/lib/pam/pam_reattach.so
|
||||
auth sufficient pam_tid.so
|
||||
PAMEOF"
|
||||
info "Created $SUDO_LOCAL"
|
||||
fi
|
||||
|
||||
# Step 4: Configure scoped sudo for bw-vault
|
||||
info "Configuring scoped sudo for bw-vault..."
|
||||
SUDOERS_FILE="/etc/sudoers.d/bw-vault"
|
||||
if [[ -f "$SUDOERS_FILE" ]]; then
|
||||
info "Sudoers config already exists"
|
||||
else
|
||||
CURRENT_USER=$(whoami)
|
||||
sudo bash -c "cat > $SUDOERS_FILE << SUDOEOF
|
||||
Cmnd_Alias BW_VAULT = /usr/local/bin/bw-vault-unlock
|
||||
Defaults!BW_VAULT timestamp_timeout=0
|
||||
$CURRENT_USER ALL=(root) BW_VAULT
|
||||
SUDOEOF
|
||||
chmod 0440 $SUDOERS_FILE"
|
||||
# Validate sudoers syntax
|
||||
if sudo visudo -cf "$SUDOERS_FILE" >/dev/null 2>&1; then
|
||||
info "Sudoers config validated and installed"
|
||||
else
|
||||
error "Sudoers config has syntax errors — removing"
|
||||
sudo rm -f "$SUDOERS_FILE"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Step 5: Create root bw data directory
|
||||
info "Creating root bw data directory..."
|
||||
sudo bash -c 'mkdir -p /var/root/.bw-data && chmod 700 /var/root/.bw-data'
|
||||
|
||||
# Step 6: Configure bw CLI server URL (as root)
|
||||
info "Configuring Bitwarden CLI server URL..."
|
||||
sudo BITWARDENCLI_APPDATA_DIR=/var/root/.bw-data /opt/homebrew/bin/bw config server https://vaultwarden.viktorbarzin.me
|
||||
|
||||
# Step 7: Store API credentials
|
||||
info "Setting up Vaultwarden API credentials..."
|
||||
echo ""
|
||||
echo "You need your Vaultwarden Personal API Key."
|
||||
echo "Get it from: https://vaultwarden.viktorbarzin.me/#/settings/security/security-keys"
|
||||
echo " → API Key section → View API Key"
|
||||
echo ""
|
||||
|
||||
if sudo test -f /var/root/.bw-credentials; then
|
||||
read -rp "Credentials file already exists. Overwrite? [y/N]: " OVERWRITE
|
||||
if [[ "${OVERWRITE,,}" != "y" ]]; then
|
||||
info "Keeping existing credentials"
|
||||
else
|
||||
_store_creds=true
|
||||
fi
|
||||
else
|
||||
_store_creds=true
|
||||
fi
|
||||
|
||||
if [[ "${_store_creds:-}" == "true" ]]; then
|
||||
read -rp "BW_CLIENTID (e.g. user.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx): " _clientid
|
||||
read -rp "BW_CLIENTSECRET: " _clientsecret
|
||||
read -rsp "Master password: " _password
|
||||
echo ""
|
||||
|
||||
sudo bash -c "umask 077; cat > /var/root/.bw-credentials << CREDEOF
|
||||
BW_CLIENTID=$_clientid
|
||||
BW_CLIENTSECRET=$_clientsecret
|
||||
BW_PASSWORD=$_password
|
||||
CREDEOF"
|
||||
unset _clientid _clientsecret _password
|
||||
info "Credentials stored in /var/root/.bw-credentials (root:wheel 0600)"
|
||||
fi
|
||||
|
||||
# Step 8: Install the privileged unlock script
|
||||
info "Installing bw-vault-unlock to /usr/local/bin/..."
|
||||
sudo cp ~/.local/bin/bw-vault-unlock /usr/local/bin/bw-vault-unlock
|
||||
sudo chown root:wheel /usr/local/bin/bw-vault-unlock
|
||||
sudo chmod 0755 /usr/local/bin/bw-vault-unlock
|
||||
|
||||
# Step 9: Pin bw binary hash
|
||||
info "Pinning bw binary hash..."
|
||||
BW_HASH=$(shasum -a 256 /opt/homebrew/bin/bw | awk '{print $1}')
|
||||
sudo bash -c "echo '$BW_HASH' > /var/root/.bw-hash && chmod 600 /var/root/.bw-hash"
|
||||
info "Hash: $BW_HASH"
|
||||
|
||||
# Step 10: Verify
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo " Verification"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
|
||||
# Test sudo Touch ID
|
||||
info "Testing sudo (Touch ID should appear)..."
|
||||
if sudo echo "sudo works"; then
|
||||
info "sudo + Touch ID: OK"
|
||||
else
|
||||
warn "sudo test failed"
|
||||
fi
|
||||
|
||||
# Test credential file is protected
|
||||
if cat /var/root/.bw-credentials 2>/dev/null; then
|
||||
error "SECURITY: credentials file is readable by user!"
|
||||
else
|
||||
info "Credential file protected: OK"
|
||||
fi
|
||||
|
||||
# Test bw-vault search
|
||||
info "Testing bw-vault search (Touch ID will appear)..."
|
||||
if bw-vault search "test" >/dev/null 2>&1; then
|
||||
info "bw-vault search: OK"
|
||||
else
|
||||
warn "bw-vault search test failed (may be normal if vault is empty)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
info "Setup complete!"
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " bw-vault search <query> # Search (metadata only)"
|
||||
echo " bw-vault inject <id> --as <VAR> -- <cmd...> # Inject secret into command"
|
||||
echo " bw-vault copy <id> [field] # Copy to clipboard"
|
||||
echo " bw-vault file <id> <path> # Write to file (0600)"
|
||||
echo " bw-vault create # Create new item"
|
||||
echo " bw-vault edit <id> # Edit item"
|
||||
echo ""
|
||||
echo "After brew upgrade bitwarden-cli, update the hash:"
|
||||
echo " sudo bash -c \"\$(shasum -a 256 /opt/homebrew/bin/bw | awk '{print \\\$1}') > /var/root/.bw-hash\""
|
||||
Loading…
Add table
Add a link
Reference in a new issue