#!/usr/bin/env bash # Secret storage for claude-mode (POSIX port), sourced by claude-mode and by the # key helper. # # Windows uses DPAPI, which binds ciphertext to one Windows user on one machine. # There is no single equivalent here, so this picks the best available backend: # # macOS security login Keychain, unlocked with the session # Linux secret-tool libsecret / GNOME Keyring, same idea # Linux pass gpg-backed, agent-cached # any file 0600 in ~/.claude-mode/vault - PLAINTEXT # # The file backend is the honest fallback: it is no worse than the API keys # people already keep in .bashrc, but it is not encrypted and claude-mode says so # out loud rather than implying protection it does not provide. CM_VAULT_DIR="${CM_ROOT:-$HOME/.claude-mode}/vault" CM_VAULT_SERVICE="claude-mode" cm_vault_backend() { if [ -n "${CLAUDE_MODE_VAULT:-}" ]; then printf '%s\n' "$CLAUDE_MODE_VAULT"; return; fi if [ "$(uname -s)" = "Darwin" ] && command -v security >/dev/null 2>&1; then printf 'security\n'; return fi if command -v secret-tool >/dev/null 2>&1; then printf 'secret-tool\n'; return; fi if command -v pass >/dev/null 2>&1; then printf 'pass\n'; return; fi printf 'file\n' } cm_vault_backend_label() { case "$(cm_vault_backend)" in security) printf 'macOS Keychain\n' ;; secret-tool) printf 'libsecret (GNOME Keyring)\n' ;; pass) printf 'pass (gpg)\n' ;; file) printf 'plain file, 0600 (NOT encrypted)\n' ;; esac } # cm_vault_set - reads the secret from stdin cm_vault_set() { local ref="$1" secret IFS= read -r secret || true [ -n "$secret" ] || { echo "empty key, aborted" >&2; return 1; } case "$(cm_vault_backend)" in security) security add-generic-password -U -a "$ref" -s "$CM_VAULT_SERVICE" -w "$secret" >/dev/null ;; secret-tool) printf '%s' "$secret" | secret-tool store --label="claude-mode $ref" \ service "$CM_VAULT_SERVICE" ref "$ref" >/dev/null ;; pass) printf '%s\n' "$secret" | pass insert -m -f "$CM_VAULT_SERVICE/$ref" >/dev/null ;; file) mkdir -p "$CM_VAULT_DIR" chmod 700 "$CM_VAULT_DIR" 2>/dev/null || true local f="$CM_VAULT_DIR/$ref.key" ( umask 077; printf '%s' "$secret" > "$f" ) chmod 600 "$f" 2>/dev/null || true ;; esac } # cm_vault_get - prints the secret, or nothing (exit 1) if absent cm_vault_get() { local ref="$1" out="" case "$(cm_vault_backend)" in security) out="$(security find-generic-password -a "$ref" -s "$CM_VAULT_SERVICE" -w 2>/dev/null)" || return 1 ;; secret-tool) out="$(secret-tool lookup service "$CM_VAULT_SERVICE" ref "$ref" 2>/dev/null)" || return 1 ;; pass) out="$(pass show "$CM_VAULT_SERVICE/$ref" 2>/dev/null | head -n1)" || return 1 ;; file) [ -f "$CM_VAULT_DIR/$ref.key" ] || return 1 out="$(cat "$CM_VAULT_DIR/$ref.key")" ;; esac [ -n "$out" ] || return 1 printf '%s' "$out" } cm_vault_has() { cm_vault_get "$1" >/dev/null 2>&1; } cm_vault_mask() { local k="$1" if [ -z "$k" ]; then printf '(none)\n'; return; fi if [ "${#k}" -le 12 ]; then printf '****\n'; return; fi printf '%s...%s\n' "${k:0:8}" "${k: -4}" }