Secrets Bitwarden Env¶
Bitwarden bw edit item cannot transfer cipher ownership — recreate-and-soft-delete instead¶
From legacy section: SEO NEO / Workbook
Setting
organizationId: nullandcollectionIds: []viabw edit item <id>returns exit code 0 and bumpsrevisionDate, 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 trustbw editexit codes for ownership transfers — verify state by re-fetching the cipher and checkingorganizationId/collectionIds, OR by re-running an audit against the org collection. Why: Burned mid-task on 2026-05-08 cleanup.03-execute-move.shreported[OK]on 23 items but the audit count was unchanged. Diagnosed by re-fetching one item and seeingorganizationIdandcollectionIds[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 trybw editwith modified org fields. Reusable executor lives atscripts/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 1AND 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 atapi.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 atapi.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 settinghidePasswordson collection assignments. Scope strings also differ:scope=apifor user key,scope=api.organizationfor 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 —bwCLI does NOT exposebw list org-membersdespite supportingbw 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 anorganization.*key exists before proposing automation. When the task is cipher manipulation only, theuser.*key +bwCLI is sufficient. Both keys live in.envfor Evolve asBITWARDEN_ORG_CLIENT_ID/SECRETand (if needed in future) a separateBITWARDEN_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 +apattern 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 causescommand not founderrors on whatever-the-second-word-of-the-value-is. Use a selective loader instead: read only the keys you need with grep, thenexport "$kv"per line. Why:05-org-admin.shinitially usedset -a; . "$ENV_FILE"; set +aand exited withxmM3: 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.envin 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 fromKEY="value"if read this way; either drop the quotes from.envor post-process withsed 's/^\([A-Z_]*\)="\(.*\)"$/\1=\2/'. Triggers: any new bash script that reads from a multi-purpose.env. Date: 2026-05-09