# OpenBao greenfield init/unseal proof (NET-WP-0020 T2) Date: 2026-07-02 Scope: `sso-mfa/bootstrap/openbao-init-unseal.sh` proven live against a genuinely uninitialized OpenBao 2.5.5 instance. ## Method A fresh local `bao server` (file storage, throwaway scratchpad dir) was started uninitialized and sealed. A `kubectl` shim forwarded the script's `kubectl -n openbao exec openbao-0 -- bao …` calls to the local instance, so the script ran byte-for-byte unmodified logic against real OpenBao. Console metadata lived in a scratch file with `sops-held-automation` selected. This substitutes for the "rebuild slate" run: it exercises the exact init/unseal/verify code paths on a greenfield instance. The first 3-node rebuild will re-run the same script through `creds-bootstrap-agent.sh` Phase 7b. ## Results 1. **Custody gate refusal** — with no model selected, the script refused (`unseal custody model is 'unselected'`). Matches earlier negative tests. 2. **Bug found and fixed** — `bao operator unseal -` does **not** read the share from stdin (OpenBao treats `-` as the literal key: *"'key' must be a valid hex or base64 string"*; without an argument it demands a TTY). The live cluster never hit this because it was already unsealed. Fixed to `bao write sys/unseal key=-`, which reads the value from stdin — shares still never touch argv or logs. 3. **Greenfield init path** — uninitialized → `operator init` (3 shares, threshold 2) → init.json written 0600 into the age-custody secrets dir → share replay stopped at threshold (share 2 of 3) → post-unseal verified. Evidence: `openbao_did_init_this_run=true`, `openbao_initialized=true`, `openbao_post_unseal_verified=true`. 4. **Restart/reseal path** — server restarted (initialized + sealed) → script skipped init, replayed SOPS-held shares to threshold → verified. Evidence: `openbao_did_init_this_run=false`, both flags true. Test server, storage, and init material were destroyed after the proof (init.json shredded).