Skip to content

feat(bin): add Bitwarden migration ceremony tooling - #13

Open
rega10 wants to merge 18 commits into
mainfrom
fm/production-bitwarden-team-rollout
Open

feat(bin): add Bitwarden migration ceremony tooling#13
rega10 wants to merge 18 commits into
mainfrom
fm/production-bitwarden-team-rollout

Conversation

@rega10

@rega10 rega10 commented Aug 29, 2026

Copy link
Copy Markdown
Owner

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

  • Add an operator runbook for staged, approval-gated Bitwarden and Automic Vault coexistence, including custody boundaries, credential inventory, recovery practices, and deferred machine-secret decisions.
  • Add a no-secret ceremony CLI with validated append-only records, ordered batch gates, idempotent retries, bounded locking, atomic writes, crash recovery, and refusal of destructive retirement paths.
  • Add executable behavior coverage for redaction, malicious inputs, invalid records and dates, concurrent retries, symlink and path-swap races, lock recovery, and macOS Bash 3.2 compatibility.

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

Scope: isolated structure/status demonstration only. No real Bitwarden or Automic Vault system, account, secret, credential, custody, or access path was exercised.

$ bin/fm-bitwarden-ceremony.sh init TeamBatch1
fm-bitwarden-ceremony: batch 'TeamBatch1' initialized (2026-09-02) at ~/.no-mistakes/evidence/01M1GJ5JTEM2HNE2JJZ76RHYXF/bitwarden-ceremony-demo-data/bitwarden/TeamBatch1.ceremony

$ bin/fm-bitwarden-ceremony.sh add-item TeamBatch1 HumanAdmin --owner IdentityOps --collection ProdAdmins
fm-bitwarden-ceremony: batch 'TeamBatch1': item 'HumanAdmin' registered (owner=IdentityOps collection=ProdAdmins)

$ bin/fm-bitwarden-ceremony.sh check TeamBatch1
next: preflight

$ bin/fm-bitwarden-ceremony.sh mark TeamBatch1 preflight
fm-bitwarden-ceremony: batch 'TeamBatch1': step 'preflight' recorded (2026-09-02)

$ bin/fm-bitwarden-ceremony.sh mark TeamBatch1 approval --approved-by Captain
fm-bitwarden-ceremony: batch 'TeamBatch1': step 'approval' recorded (2026-09-02)

$ bin/fm-bitwarden-ceremony.sh mark TeamBatch1 moved
fm-bitwarden-ceremony: batch 'TeamBatch1': step 'moved' recorded (2026-09-02)

$ bin/fm-bitwarden-ceremony.sh mark TeamBatch1 verified
fm-bitwarden-ceremony: batch 'TeamBatch1': step 'verified' recorded (2026-09-02)

$ bin/fm-bitwarden-ceremony.sh status TeamBatch1
batch: TeamBatch1
record: ~/.no-mistakes/evidence/01M1GJ5JTEM2HNE2JJZ76RHYXF/bitwarden-ceremony-demo-data/bitwarden/TeamBatch1.ceremony
items: 1
  [x] preflight
  [x] approval
  [x] moved
  [x] verified
next: complete

$ persisted no-secret record
fm-bitwarden-ceremony v1
batch: TeamBatch1
created: 2026-09-02
item: HumanAdmin owner=IdentityOps collection=ProdAdmins
step: preflight date=2026-09-02
step: approval date=2026-09-02 approved-by=Captain
step: moved date=2026-09-02
step: verified date=2026-09-02

$ bin/fm-bitwarden-ceremony.sh mark TeamBatch1 retired
fm-bitwarden-ceremony: unknown step; steps in order are: preflight approval moved verified
[exit 1, expected refusal]

$ bin/fm-bitwarden-ceremony.sh status TeamBatch1
batch: TeamBatch1
record: ~/.no-mistakes/evidence/01M1GJ5JTEM2HNE2JJZ76RHYXF/bitwarden-ceremony-demo-data/bitwarden/TeamBatch1.ceremony
items: 1
  [x] preflight
  [x] approval
  [x] moved
  [x] verified
next: complete

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 exposes mark ... 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 passes require_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.
  • 🚨 The required private report is absent from both the worktree and commit range. No delivered artifact records inspected boundaries, decisions, exact tests, skipped real-world evidence, or the required explicit statement that no real Bitwarden account, subscription, organization, invite, export, secret, production credential, rotation, Automic Vault custody, or access path was exercised.

🔧 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 - status and check use today without validating its result. If the date provider succeeds with zzzz, a record dated 2099-12-31 passes the lexical future-date check and is reported as valid progress. Validate today_date with is_date before 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.sh before correction, reproducing the executable-bit failure
  • chmod +x tests/fm-bitwarden-ceremony.test.sh
  • tests/fm-bitwarden-ceremony.test.sh after 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 case
  • Isolated operator workflow using init, add-item, check, all four ordered mark steps, status, persisted-record inspection, rejected mark ... retired, and final unchanged status
  • Checked the evidence data for leftover transaction lock or stage files and inspected git status --short for test-created worktree artifacts
✅ **Document** - passed

✅ No issues found.

🔧 **Lint** - 1 issue found → auto-fixed ✅
  • ⚠️ linter found issues (exit code 1)

🔧 Fix: Fix ShellCheck warnings in Bitwarden ceremony tests
✅ Re-checked - no issues remain.

✅ **Push** - passed

✅ No issues found.

Rene Garza Jr. 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.
@rega10 rega10 changed the title feat(bin): add Bitwarden rollout runbook and no-secret ceremony validator feat(bin): add Bitwarden migration ceremony tooling Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant