keychain-manager

Manages secrets and API keys in the macOS Keychain using the security CLI. Triggered when a user asks to store, retrieve, list, rotate, or delete tokens and credentials. Uses a consistent naming convention with the bbmisa account and uppercase service names. Never exposes secrets in plaintext output or commits them to git.

Keychain Manager

Store, retrieve, list, rotate, and delete secrets in the macOS Keychain via the security CLI.

When to Trigger

  • User asks to store a secret, API key, or token
  • User asks to retrieve a credential from the keychain
  • User wants to manage, rotate, or delete stored keys
  • User asks "add to keychain" or "get from keychain"
  • A workflow requires a secret that should not be hardcoded

Naming Convention

All entries use a consistent format:

  • Account: bbmisa (always -a bbmisa)
  • Service: UPPERCASE_WITH_UNDERSCORES (e.g., GITHUB_PERSONAL_ACCESS_TOKEN)

This ensures secrets are easily discoverable and follow a predictable pattern.

Operations

Store a Secret

security add-generic-password -a bbmisa -s SERVICE_NAME -w "token_value"

To update an existing entry (overwrite):

security add-generic-password -a bbmisa -s SERVICE_NAME -w "new_token_value" -U

The -U flag updates the password if the entry already exists.

Retrieve a Secret

security find-generic-password -a bbmisa -s SERVICE_NAME -w

Returns only the password value to stdout. Use in scripts:

TOKEN=$(security find-generic-password -a bbmisa -s SERVICE_NAME -w 2>/dev/null)

List Stored Secrets

Search by pattern:

security dump-keychain | grep "svce" | grep -i "pattern"

List all Berna-managed secrets:

security dump-keychain | grep -A4 '"acct"<blob>="bbmisa"' | grep "svce"

Delete a Secret

security delete-generic-password -a bbmisa -s SERVICE_NAME

Rotate a Secret

Follow this sequence — never delete before verifying the new value:

  1. Retrieve the old value (for rollback if needed)
  2. Store the new value with -U
  3. Verify retrieval returns the new value
  4. Confirm with the user
# Step 1: Note old value exists
security find-generic-password -a bbmisa -s SERVICE_NAME -w > /dev/null 2>&1 && echo "Old value exists"

# Step 2: Update with new value
security add-generic-password -a bbmisa -s SERVICE_NAME -w "new_token_value" -U

# Step 3: Verify
security find-generic-password -a bbmisa -s SERVICE_NAME -w

Shell Integration

Add to ~/.zshrc for automatic environment variable export:

export GITHUB_TOKEN="$(security find-generic-password -a bbmisa -s GITHUB_PERSONAL_ACCESS_TOKEN -w 2>/dev/null)"
export HUGGINGFACE_TOKEN="$(security find-generic-password -a bbmisa -s HUGGINGFACE_TOKEN -w 2>/dev/null)"
export FIRECRAWL_API_KEY="$(security find-generic-password -a bbmisa -s FIRECRAWL_API_KEY -w 2>/dev/null)"

The 2>/dev/null suppresses errors if the key does not exist, preventing shell startup noise.

Known Tokens in Keychain

These tokens are already stored and available:

Service NameUsed For
HUGGINGFACE_TOKENHuggingFace Inference API
FIRECRAWL_API_KEYFirecrawl web scraping
MEM0_API_KEYMem0 persistent AI memory
DAYTONA_API_KEYDaytona dev environments
GITHUB_PERSONAL_ACCESS_TOKENGitHub API and CLI auth

Anti-Patterns

Never do these:

  • Echo tokens to stdout unmasked — if displaying for verification, show only first 4 and last 4 characters
  • Store in plaintext files — no .env files with real values committed to git; use .env.example with placeholders
  • Commit keychain retrieval output — never let security find-generic-password output end up in git history
  • Use short or ambiguous service names — always use descriptive uppercase names (e.g., STRIPE_LIVE_SECRET_KEY not stripe)
  • Skip the -a bbmisa flag — without it, entries are harder to find and may collide with system entries

Masked Display Helper

When the user asks to verify a stored secret, display it masked:

TOKEN=$(security find-generic-password -a bbmisa -s SERVICE_NAME -w 2>/dev/null)
echo "${TOKEN:0:4}****${TOKEN: -4}"

This shows ghp_****Ab1x — enough to confirm identity without exposing the full value.