From 6c18b7f9cff0f01007b8a16219f890cfd2940efb Mon Sep 17 00:00:00 2001 From: Faks <1909645+Faks@users.noreply.github.com> Date: Mon, 19 Oct 2026 20:28:01 +0300 Subject: [PATCH] fix(setup): validate admin password against bcrypt's 72-byte input limit x/crypto/bcrypt refuses inputs longer than 72 bytes instead of truncating them, so a longer password at POST /api/setup/complete returns a 500 and the setup page shows the generic "An error occurred during setup. Please try again." Reproduced on a live deployment with a 128-character password. The handler logged "bcrypt: password length exceeds 72 bytes" on /api/setup/complete, and no user was created, so the retry is safe. Validate the byte length before hashing so the client gets a 400 with an actionable message, and show the server's message in the setup page when one is present. The api client already copies the JSON error field onto error.message, so the fallback to the generic string is unchanged for every other failure. Login and change-password hit the same limit and are not touched here. Not verified locally: no Go toolchain on this machine, and the frontend has no installed node_modules. Needs gofmt, staticcheck, and the frontend type check in CI. --- frontend/src/lib/pages/WelcomeAssistant.svelte | 4 +++- internal/handlers/setup.go | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/pages/WelcomeAssistant.svelte b/frontend/src/lib/pages/WelcomeAssistant.svelte index d183c8752..1fcd9f8e7 100644 --- a/frontend/src/lib/pages/WelcomeAssistant.svelte +++ b/frontend/src/lib/pages/WelcomeAssistant.svelte @@ -144,7 +144,9 @@ window.location.reload(); } catch (err) { console.error('Setup error:', err); - error = t('setup.setupError'); + // Show the server's message when it sends one, for example a password over + // bcrypt's 72 byte input limit. The generic string stays as the fallback. + error = err?.message || t('setup.setupError'); submitting = false; } } diff --git a/internal/handlers/setup.go b/internal/handlers/setup.go index 14e112bde..8c5e5ee7c 100644 --- a/internal/handlers/setup.go +++ b/internal/handlers/setup.go @@ -17,6 +17,10 @@ import ( "golang.org/x/crypto/bcrypt" ) +// bcrypt's input limit. x/crypto/bcrypt refuses inputs longer than this instead +// of truncating them, so a password over 72 bytes cannot be hashed at all. +const maxPasswordBytes = 72 + // AuthMiddleware interface to avoid circular imports type AuthMiddleware interface { MarkSetupCompleted() @@ -502,6 +506,13 @@ func (h *SetupHandler) validateSetupRequest(req models.SetupRequest) error { if req.AdminUser.Password == "" { return fmt.Errorf("admin password is required") } + // bcrypt's input limit is 72 bytes, and x/crypto/bcrypt errors instead of + // truncating. An over-long password therefore surfaces as a 500 here and the + // UI shows a generic setup failure. Validate before hashing so the client gets + // an actionable message. len(string) counts bytes, which is what matters. + if len(req.AdminUser.Password) > maxPasswordBytes { + return fmt.Errorf("admin password must be %d bytes or fewer (bcrypt input limit)", maxPasswordBytes) + } // Basic email validation if !strings.Contains(req.AdminUser.Email, "@") {