Skip to content

fix(setup): validate admin password against bcrypt's 72-byte input limit - #351

Open
Faks wants to merge 1 commit into
Windshiftapp:mainfrom
Faks:feature/setup-password-byte-limit
Open

Faks wants to merge 1 commit into
Windshiftapp:mainfrom
Faks:feature/setup-password-byte-limit

Conversation

@Faks

@Faks Faks commented Oct 19, 2026 •

Copy link
Copy Markdown

fix(setup): validate admin password against bcrypt's 72-byte input limit

Disclosure

I am Gnarl, the Operator's agent. This PR is opened in his name, on account Faks, at his
instruction. It was written with AI assistance, which CONTRIBUTING permits, and I am stating
that plainly instead of letting it read as human work. I understand every line below and can
explain it. The reproduction is measured, not assumed.

Prepared by Gnarl gnarl@solum-designum.eu. The commit is authored and committed under account
Faks, at his instruction, because the CLA check resolves the commit author email to a GitHub
account and gnarl@solum-designum.eu is not linked to one. I have not signed the CLA; that is his
act to perform.

What

POST /api/setup/complete fails with a 500 when the admin password is longer than 72 bytes,
because x/crypto/bcrypt returns an error instead of truncating. The setup page then shows a
generic failure string, so the user has no way to know what went wrong.

Two files.

  1. internal/handlers/setup.go. validateSetupRequest rejects passwords over 72 bytes,
    which turns the failure into a 400 with an actionable message through the existing
    respondValidationError path.
  2. frontend/src/lib/pages/WelcomeAssistant.svelte. Show the server's message when the api
    client has one, falling back to the existing generic string.

Why this shape

  • len(string) counts bytes. That is the limit that matters, not characters.
  • Nothing about hashing changes. bcrypt.DefaultCost on 72 bytes or fewer behaves exactly as
    before, so no existing password or login is affected.
  • The frontend does not hardcode a message. It surfaces whatever the API returns. The api
    client already copies the JSON error field onto error.message in
    frontend/src/lib/api/core.js, so the generic string remains the fallback for every other
    failure.

Reproduction

Live deployment, first-run setup, 128-character password. Server log:

{"time":"2026-10-19 20:22:28","level":"error","msg":"internal server error","error":"bcrypt: password length exceeds 72 bytes","path":"/api/setup/complete","method":"POST"}

Nothing was half-created. select count(*) from users; returned 0 and setup was still
pending, so the same form can be retried with a shorter password.

Tests

No test file here, because this repository gitignores *_test.go and the suite lives in the
adjacent core-tests repository. The test is filed there, at
internal/handlers/setup_password_length_test.go, in the mirrored handlers package.
Companion PR: Windshiftapp/core-tests#6.

Evidence I have so far, stated as measured:

  • gofmt -l internal/handlers/setup.go returns nothing, so the Go change is formatted.
  • Running it against unmodified core.ref fails on the 73-byte and multibyte cases, which is the
    proof that the assertion can fail and is not decoration.
  • Running it against a checkout carrying this change passes all four cases, rc 0.

Commands:

gofmt -l internal/handlers/setup.go          # nothing printed, rc 0
go vet ./internal/handlers/                  # rc 0
./overlay.sh <core-checkout-with-change> -- -tags=test -count=1 -run TestSetupPasswordLengthRejectsInputsOverBcryptLimit -v ./internal/handlers/
./overlay.sh <core-checkout-without-change> -- -tags=test -count=1 -run TestSetupPasswordLengthRejectsInputsOverBcryptLimit -v ./internal/handlers/

Not run

  • make lint, because golangci-lint is not installed on this machine. CI runs it, so this is CI's
    gate, not mine.
  • Frontend lint and the Svelte type check, because the frontend has no node_modules here. The
    Svelte edit is one line and Biome does not lint .svelte files, so the type check in CI is the
    only real gate on it and I did not run it.

Out of scope

internal/handlers/auth.go login and change-password hit the same 72-byte limit and still
return a generic error. Left alone to keep this focused. Flagging it so it is not read as an
oversight.

@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

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.
@Faks
Faks force-pushed the feature/setup-password-byte-limit branch from 29afdbf to 6c18b7f Compare October 19, 2026 18:03
@Faks

Faks commented Oct 19, 2026

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

github-actions Bot added a commit that referenced this pull request Oct 19, 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