feat(bin): add Bitwarden migration ceremony tooling - #13
Open
rega10 wants to merge 18 commits into
Open
Conversation
added 18 commits
August 29, 2026 02:41
…remony-record validator Add docs/bitwarden-rollout.md (operator-current): the staged, gate-approved path from temporary local-only custody to organization-owned Bitwarden custody for production and team credentials. It preserves the hardened local-vault boundary, keeps Bitwarden Secrets Manager an explicit undecided later choice, requires two independent owner recovery paths and hardware-backed MFA before any credential moves, inventories credential classes without values, defines the per-batch migration ceremony with captain approval and verify-before-retire ordering, and designs encrypted export and recovery drills with split ownership and no plaintext staging. Add bin/fm-bitwarden-ceremony.sh: a no-secret structure/status validator for per-batch ceremony records. It never touches any credential store, refuses secret-shaped input without echoing it, enforces the preflight -> approval -> moved -> verified -> retired order so retirement can never be recorded before verification and approval, is idempotent for interrupted-ceremony replay, and reports corrupt record lines by number with content withheld. Behavior coverage in tests/fm-bitwarden-ceremony.test.sh: malicious batch ids, secret-shape redaction and non-persistence, step-order and retirement gates, idempotent retries, partial-ceremony recovery, and corrupt-record withholding. Pointers: inventory entry in docs/documentation-audiences.json, a toolbelt row in docs/scripts.md, and a README documentation link. Claude-Session: https://claude.ai/code/session_01ABrW9CXMaE5jmeYYwEA6wo
tests/fm-public-followup.test.sh pinned the repro fixture's thread window to an absolute 2026-08-28T01:12:00Z. Once the wall clock passed that instant the rechain tests started refusing with "the thread can no longer be reached", failing the portable serial shard on calendar date alone. Derive received_at/followup_expires_at/reservation_expires_at and both obligation --expires-at stamps from the run's own clock, and let the expiry escalation test read the seeded deadline instead of repeating the constant, so the window is always live and the expiry cases stay pinned by FMX_NOW_OVERRIDE.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Prepare the captain-selected staged adoption of Bitwarden as the canonical managed store for production and team credentials. Produce the smallest practical operator-usable rollout package and implementation that moves from temporary local-only custody toward organization-owned Bitwarden custody while preserving current access until each migration batch is explicitly approved and verified. The captain has now ruled in his words: "i dont want to retire Bitwarden, and for now we are using a combination of Bitwarden and Automic Vault". This supersedes every prior destructive-retirement requirement: Bitwarden and Automic Vault coexist as the intended end state for now, no existing custody or credential is deleted, invalidated, rotated, or retired, batch approval never authorizes retirement, no separate retirement approval is expected, and the rollout deliverable must remove the retirement step or make it an explicit never-executed no-op rather than a deferred TODO. Do not create Bitwarden accounts or organizations, choose or purchase a subscription, invite users, alter billing, move, copy, read, print, or export any secret value, change production access, rotate credentials, modify Automic Vault custody, or run a real migration ceremony. Treat Bitwarden Password Manager for human and team credentials separately from Bitwarden Secrets Manager for machine and unattended access. Do not select or enable Secrets Manager; state the later architecture decision, service-account and access-token consequences, and interim boundary. Preserve at least two independent owner or admin recovery paths, hardware-backed MFA for privileged accounts, least-privilege collections and groups, joiner, mover, and leaver procedures, and no single-person recovery dependency. Design encrypted export and recovery drills without making an export, including offline custody, encryption, split ownership, restore verification, exposure rotation, cadence, and deletion or retention evidence without plaintext staging. Inventory credential classes and owners without secret values, covering human interactive, shared team, production service, CI/CD, break-glass, local development, and machine or unattended classes, with initial exclusions and reasons. Preserve Automic Vault as the hardened local-secret store and never duplicate secret material into repo or task files. Define a phased ceremony with preflight, per-item ownership and collection, captain approval, dual control where appropriate, post-move verification, rollback, coexistence with old custody after verification, and no-secret completion evidence. Surface unresolved subscription, legal or compliance retention, owner identity, and machine architecture decisions, but do not retain any unresolved destructive-retirement decision because the captain resolved it as no retirement. Deliver an authoritative operator runbook in the correct tracked audience, a justified structure/status-only helper that never accepts, echoes, logs, persists, or transforms secret values, executable behavior tests for malicious paths and filenames, redaction, no-secret logging, idempotent and concurrent retries, partial recovery, symlink and path-swap races, crash recovery, bounded locks, stock macOS Bash 3.2 compatibility, invalid bytes and dates, and refusal of any destructive retirement path, plus a private report with inspected boundaries, decisions, exact tests, and skipped real-world evidence. Keep contracts in one authoritative owner and use pointer-only updates elsewhere. Preserve every in-scope Bitwarden pipeline fix, keep the final diff free of tests/fm-public-followup.test.sh changes, and fix the orphan-writer token race by making final append conditional on the original lock token still owning the lock under the advisory guard. Run documentation audience and link checks, relevant tests and lint, including bin/fm-lint.sh, and open a real green PR for head fm/production-bitwarden-team-rollout without merging. Explicitly report that no real Bitwarden account, subscription, organization, invite, export, secret, production credential, rotation, Automic Vault custody, or access path was exercised.
What Changed
Risk Assessment
✅ Low: Captain, the change is well-bounded, satisfies the source-verifiable rollout constraints, and no material correctness or safety defect remains evident in the reviewed paths.
Testing
No baseline test output was supplied. The initial direct launch exposed and prompted correction of a missing executable bit; the focused behavioral suite then passed on a host with Bash 3.2, and an isolated operator transcript demonstrated complete no-secret coexistence evidence plus refusal of destructive retirement with state unchanged. No real Bitwarden account, subscription, organization, invite, export, secret, production credential, rotation, Automic Vault custody, or access path was exercised.
Evidence: Bitwarden ceremony operator transcript
Source: Bitwarden ceremony operator transcript
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 4 issues found → auto-fixed (2) ✅
docs/bitwarden-rollout.md:92- The intent requires that Bitwarden and Automic Vault coexist and that retirement be removed or an explicit never-executed no-op. This runbook instead directs operators to “delete or invalidate the old copy,” while the helper exposesmark ... retired. Remove this destructive step and its executable transition, or make it an explicit no-op as required.bin/fm-bitwarden-record-io.py:443- The transaction owner holds the lock while a child performs the append. If the owner is killed after the child stages its update, a retry can reclaim the stale lock and commit, then the orphan reaches this unconditional rename and overwrites the retry's successful update. Before renaming, verify under the advisory guard that the original lock token still owns the lock; add a synchronized regression that kills the owner after staging.bin/fm-bitwarden-ceremony.sh:142- The intent requires a helper that never accepts, echoes, or persists secret values, but validation only recognizes selected prefixes and 32-character hexadecimal runs. A password or secret-access key shaped as an ordinary 40-character alphanumeric label passesrequire_label, is appended at line 459, and echoed at line 460. Meeting the literal contract requires authorizing a stricter identifier-only interface or narrowing the guarantee to syntactically recognized secret shapes.🔧 Fix: Remove retirement path and guard orphaned writes
2 issues (1 error, 1 warning) still open:
docs/bitwarden-rollout.md:124- The intent simultaneously requires “exposure rotation” and says no existing credential is rotated. The runbook implements rotation for leavers and exposure at lines 63 and 106, but this blanket statement says the runbook never authorizes rotation. Operators cannot determine whether those required rotations are permitted. Clarify whether the prohibition applies only to migration-batch completion and old-custody retirement, or also to later incident and leaver procedures.bin/fm-bitwarden-ceremony.sh:253-statusandcheckusetodaywithout validating its result. If the date provider succeeds withzzzz, a record dated2099-12-31passes the lexical future-date check and is reported as valid progress. Validatetoday_datewithis_datebefore parsing, matching the validated init and mark boundaries.🔧 Fix: Enforce identifier grammar and validate read dates
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
tests/fm-bitwarden-ceremony.test.shbefore correction, reproducing the executable-bit failurechmod +x tests/fm-bitwarden-ceremony.test.shtests/fm-bitwarden-ceremony.test.shafter correction, covering identifier validation, invalid dates and bytes, redaction, idempotency, concurrency, path and symlink races, orphan-writer protection, crash recovery, bounded locking, Bash 3.2 compatibility, and retirement refusal/bin/bash --version | head -1, confirming GNU Bash 3.2.57 was available for the suite's stock-macOS compatibility caseIsolated operator workflow usinginit,add-item,check, all four orderedmarksteps,status, persisted-record inspection, rejectedmark ... retired, and final unchangedstatusChecked the evidence data for leftover transaction lock or stage files and inspectedgit status --shortfor test-created worktree artifacts✅ **Document** - passed
✅ No issues found.
🔧 **Lint** - 1 issue found → auto-fixed ✅
🔧 Fix: Fix ShellCheck warnings in Bitwarden ceremony tests
✅ Re-checked - no issues remain.
✅ **Push** - passed
✅ No issues found.