fix(stack): gate init --force backend-switch destroy on live-services confirmation#773
Merged
Merged
Conversation
…ces confirm Root cause: Init() destroyed the old backend's cluster (destroyOldBackendIfSwitching) unconditionally on --force, then only afterward validated the new backend via NewBackend/Prerequisites — either of which can fail, leaving the old cluster destroyed with nothing running. Unlike Down()/Purge(), this path had no ConfirmRunningServicesLoss gate, so a live-traffic-serving stack could be silently wiped by 'stack init --force --backend <X>'. Fix: (1) reorder Init() so NewBackend + backend.Prerequisites run before destroyOldBackendIfSwitching, so an invalid/unavailable target backend aborts before anything is destroyed. (2) thread a skipConfirm (--yes) bool through Init and gate the destroy on ConfirmRunningServicesLoss, mirroring Down/Purge; non-interactive callers without --yes now fail closed instead of destroying. CLI wiring in cmd/obol/main.go adds --yes/-y to 'stack init', matching purge's flag. bootstrap.go's internal (force=false) call passes skipConfirm=false, a no-op since force=false never reaches the destroy path. Confirmed Canary402 full-surface audit finding. Claude-Session: https://claude.ai/code/session_014YjPMViNrZ7zBVgUQzwEKk
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.
Full-surface audit finding (CRITICAL):
obol stack init --forcedestroys a live cluster with no confirmationFound by the 2026-07-16 whole-product-surface audit (PR #770), adversarially confirmed.
internal/stack/stack.goInit()callsdestroyOldBackendIfSwitching→oldBackend.Destroy()before validating the new backend, and with noConfirmRunningServicesLossgate — unlikeDown()/Purge(), which added exactly that gate after the 2026-05-22 cross-stack outage (see the comment at stack.go:266). Soobol stack init --force --backend k3sagainst a host running a live k3d stack (staleOBOL_BACKEND, or a muscle-memory re-run) tears down the running cluster — containers, PVCs, agent wallets, registered offers — and if the new backend then failsPrerequisites(k3s is Linux+sudo-only), leaves nothing running with a bare error.Fix
NewBackend+Prerequisites) before the destroy, so an invalid/unavailable backend aborts with nothing destroyed.ConfirmRunningServicesLoss(new--yes/skipConfirm threaded from the CLI, mirroringpurge). Live services + non-TTY + no--yes→ refuse; TTY → prompt.Test: Init with live services, non-TTY, no
--yesreturns an error and never calls Destroy.go test ./internal/stack/... ./cmd/obol/...green.https://claude.ai/code/session_014YjPMViNrZ7zBVgUQzwEKk