Skip to content

Secrets Bitwarden Env

Bitwarden bw edit item cannot transfer cipher ownership — recreate-and-soft-delete instead

From legacy section: SEO NEO / Workbook

Setting organizationId: null and collectionIds: [] via bw edit item <id> returns exit code 0 and bumps revisionDate, but the Bitwarden API silently ignores ownership-field changes on that endpoint. Cipher stays in the org. The only working path to move an item from an org collection back to a user's personal vault via CLI is: bw get item <id> → strip system fields with jq → bw create item (creates fresh in personal vault under user encryption key, new ID) → bw delete item <old-id> (soft-delete to org trash, recoverable 30 days). Never trust bw edit exit codes for ownership transfers — verify state by re-fetching the cipher and checking organizationId / collectionIds, OR by re-running an audit against the org collection. Why: Burned mid-task on 2026-05-08 cleanup. 03-execute-move.sh reported [OK] on 23 items but the audit count was unchanged. Diagnosed by re-fetching one item and seeing organizationId and collectionIds[Evolve] still set despite the edit. Cost ~30 min to pivot to the recreate-and-delete approach (04-execute-move-v2.sh). The Bitwarden Public API documents collection management but provides NO endpoint for org→user cipher ownership transfer; the web vault's "Restore to user" action uses an internal admin endpoint that isn't exposed publicly. How to apply: Any time you're modifying Bitwarden ciphers via CLI/API, verify the change took effect by re-fetching, not by trusting exit codes. For ownership transfers specifically, default to recreate-and-soft-delete from the start — never try bw edit with modified org fields. Reusable executor lives at scripts/bitwarden/04-execute-move-v2.sh. Triggers: any task involving moving Bitwarden items between personal vault and an org collection, or between orgs. Sub-lesson — verify with --limit 1 before bulk: When testing a destructive bulk operation, run with --limit 1 AND manually verify in the web vault (item appears in expected location, original is in expected state, login still works). Don't rely on the script's own success log. The trial run on 2026-05-08 reported [OK] and we proceeded — only when the audit re-ran did we discover nothing actually moved. Date: 2026-05-09


Bitwarden API key types — user.* vs organization.* are NOT interchangeable

From legacy section: SEO NEO / Workbook

The two OAuth client_id formats authenticate different APIs with non-overlapping capabilities. user.{uuid} keys (Settings → Security → Keys → API key) authenticate the Vault Management API at api.bitwarden.com — they let you act as the user (read/write your own ciphers, sync, etc.) but cannot hit the Public API endpoints. organization.{uuid} keys (Org → Settings → Organization info → API key) authenticate the Public API at api.bitwarden.com/public — for managing members, groups, collections, policies, event logs across the org. Need both for full automation: user key for cipher manipulation via CLI, org key for admin operations like setting hidePasswords on collection assignments. Scope strings also differ: scope=api for user key, scope=api.organization for org key. Why: First proposed Bitwarden integration (2026-05-08) assumed the user key could manage team members. It can't. Pivoted from "build an MCP" to "use bw CLI + Public API as needed." Later (2026-05-09) needed Public API for org member listing — bw CLI does NOT expose bw list org-members despite supporting bw list org-collections; org member visibility is Public-API-only. How to apply: When a Bitwarden task requires team management (members/groups/permissions/policies/events), confirm an organization.* key exists before proposing automation. When the task is cipher manipulation only, the user.* key + bw CLI is sufficient. Both keys live in .env for Evolve as BITWARDEN_ORG_CLIENT_ID/SECRET and (if needed in future) a separate BITWARDEN_USER_* pair. Triggers: any new Bitwarden integration request; any "manage team access" or "audit org" task. Date: 2026-05-09


Don't blanket-source .env with set -a; . file — values containing spaces break shell parsing

From legacy section: SEO NEO / Workbook

Bash's set -a; . .env; set +a pattern works only if every value is a simple unquoted token. WordPress application passwords, GHL secrets, and other real-world values frequently contain spaces or shell metacharacters; sourcing them as bash code causes command not found errors on whatever-the-second-word-of-the-value-is. Use a selective loader instead: read only the keys you need with grep, then export "$kv" per line. Why: 05-org-admin.sh initially used set -a; . "$ENV_FILE"; set +a and exited with xmM3: command not found (line 18 = WP_APP_PASSWORD=... with spaces). Fixed by replacing with: while IFS= read -r kv; do export "$kv"; done < <(grep -E '^BITWARDEN_[A-Z_]+=' "$ENV_FILE"). How to apply: When loading config from a project .env in a script that only needs a known subset of vars, always select-then-export instead of bulk-source. Pattern: while IFS= read -r kv; do [[ -z "$kv" || "$kv" == \#* ]] && continue; export "$kv"; done < <(grep -E '^PREFIX_[A-Z_]+=' "$ENV_FILE"). Bonus: also strip surrounding quotes from values you control — bash export literally includes quotes from KEY="value" if read this way; either drop the quotes from .env or post-process with sed 's/^\([A-Z_]*\)="\(.*\)"$/\1=\2/'. Triggers: any new bash script that reads from a multi-purpose .env. Date: 2026-05-09