Skip to content

fix: re-check setup completion before creating root admin#60

Merged
andregoepel merged 1 commit into
mainfrom
bugfix/setup-recheck-completion
Jul 4, 2026
Merged

fix: re-check setup completion before creating root admin#60
andregoepel merged 1 commit into
mainfrom
bugfix/setup-recheck-completion

Conversation

@andregoepel

Copy link
Copy Markdown
Owner

Closes #52 (F3 — /Setup bootstrap allows root-admin takeover race).

What

OnValidSubmit in Setup.razor now re-asserts SetupCompletion.IsCompleteAsync immediately before creating the root administrator, and fails closed (info notification + redirect to /) if setup already happened.

Why

The completion guard previously ran only in OnInitializedAsync (initial render). The external SetupRedirectMiddleware blocks fresh HTTP GETs of /Setup once an admin exists, but neither guard re-runs for a form submit on an already-open Blazor Server circuit. A circuit opened during the first-run deployment window could therefore be driven to mint a second root administrator after legitimate setup completed.

Re-checking inside the submit closes that window — the create now happens only if no administrator holds the role at submit time.

Why this approach

SetupRedirectMiddleware in AndreGoepel.Marten.Identity documents this exact expectation:

"The host's Setup page should also re-check SetupCompletion.IsCompleteAsync and refuse — this is defence in depth."

SetupCompletion.IsCompleteAsync returns true only once a non-deleted user actually holds the Administrator role, so the re-check reflects real completion state, not merely "some data exists."

Scope note

This eliminates the practical vectors (retained circuit, re-run after completion). A sub-millisecond race between two simultaneous first-run submits (before any admin exists) is not meaningfully an escalation — there is no admin to protect yet — and is further addressed by the separately-tracked hardening of gating /Setup behind a one-time bootstrap secret / loopback.

Verification

  • dotnet build -c Release — 0 errors.
  • dotnet test -c Release — 54/54 passing.
  • Single-file change: Setup.razor (+17).

Independent of #59 (password-policy fix), which touches a different region of the same file.

OnInitializedAsync guarded only the initial render, and the external
SetupRedirectMiddleware only blocks fresh GETs of /Setup once an admin
exists — neither re-runs for a form submit on an already-open Blazor
circuit. A circuit opened during the first-run window could therefore
be used to mint a second root administrator after setup completed.

Re-assert SetupCompletion.IsCompleteAsync at the top of OnValidSubmit,
immediately before creating the user, and fail closed (notify + redirect
to /) if setup already happened. This is the defence-in-depth re-check
the identity package's SetupRedirectMiddleware explicitly expects the
host Setup page to perform.

Closes #52
@andregoepel
andregoepel merged commit 6232194 into main Jul 4, 2026
3 checks passed
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.

[Security] /Setup bootstrap allows root-admin takeover race (F3)

1 participant