Skip to content

feat(beyondleetcode): C33c cap names at 100 characters - #188

Merged
GauranshMathur merged 4 commits into
mainfrom
C33c-name-cap
Oct 16, 2026
Merged

GauranshMathur merged 4 commits into
mainfrom
C33c-name-cap

Conversation

@GauranshMathur

@GauranshMathur GauranshMathur commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Card C33c (access control: security review before merge). Worker: builder-signin.

A name longer than 100 characters is refused on Setup and on Sign up with a field error, and by a direct POST /api/auth/sign-up/email, and nothing is stored. The cap is 100 characters, counted in Unicode code points on the trimmed name (maintainer, 2026-10-16).

Checklist

  • auth.ts: MAX_NAME_LENGTH, isNameTooLong, and the user.create.before hook refuses a longer name with a fixed APIError (NAME_TOO_LONG, 400, "The name is too long."), never echoing the name. It runs after the admission check (a closed sign-up answers 403 whatever the name) and before any Learner is made (a refused name leaves no Learner, not even the named Local one)
  • sign-up.ts: fixed field error "Use at most 100 characters." before calling Better Auth (after the blank-name check, before email and password), and Better Auth's own NAME_TOO_LONG mapped to the same text
  • Setup +page.server.ts: the same field error, before the Instance is claimed (after the already-set-up check), and the same mapping
  • Vitest 937 pass: the hook against real Better Auth over a file database (HTTP, server-side, a half-megabyte name, emoji, padding, closed sign-up, no Learner, the first admin with the Instance put back to not set up), Sign up, Setup
  • signup.multi.e2e.ts: browser tests of Sign up (101 characters refused on the field, exactly 100 emoji accepted, a direct POST refused and nothing stored); Playwright 95 pass locally under the shared lock
  • Stale comments corrected in signup.multi.e2e.ts and seed-open-instance.ts: the Users toggle (C34b) can open registration now; the seed is a test fixture nothing in the app imports
  • Self-review done against the card and the decisions
  • Codex second opinion: skipped, codex is not installed on this machine
  • CI green; ready for security review (not merged by the worker)

From the security review

  1. A long name no longer reveals whether an email has an Account. Better Auth looks the email up before the database hook runs, so a name refused only there answered 400 for an unknown email and 422 for a known one, with nothing stored either way. The name check now also runs in hooks.before on /sign-up/email, right after the admission check (so a closed sign-up still answers 403 first), throwing the same fixed NAME_TOO_LONG error. The database-hook check stays as the backstop. Tests: a known and an unknown email with a long name get the same 400 and the same body; a closed Instance answers 403 for a long name, a blank one, a known email or not. A browser test on the open server checks the same over a real POST.
  2. A blank name is no longer stored over HTTP. A name that is empty after trimming (an empty string, spaces, Unicode whitespace) is refused in the same two places with a fixed NAME_REQUIRED error, 400, "Enter a name.". Sign up and Setup map it to the forms' existing blank-name field error. Tests: HTTP for three blank spellings (nothing stored, no Learner), the server-side call, a known email the same as an unknown one, and the two form mappings.

Zero-width, control and bidi characters are left alone, as told: which characters a name may hold is a product rule.

Notes for the security review

  • The hook stores the trimmed name. The cap counts the trimmed name, so without this a direct POST with "a" plus 400 KB of spaces would pass the cap and store 400 KB. The forms already send the name trimmed; a direct POST now gets the same. This is the one behaviour I added beyond the card's wording (a name with spaces around it is stored without them on every path); tell me if you would rather count the untrimmed name instead.
  • Counting is cheap on any input. isNameTooLong trims, then decides from the UTF-16 length alone when it can (up to 100 units is within the cap, over 200 is not) and only spreads into code points between those. Tests cover the boundaries with ASCII, emoji (two units each), lone surrogates and Unicode whitespace.
  • Setup's browser path is covered by the action's unit tests and by the first-admin test against real Better Auth, not by a new browser test: setup.multi.e2e.ts is outside this card's files.
  • Not changed, as told: the form inputs have no maxlength (the browser counts UTF-16 units, so it would refuse a 100-emoji name the server accepts).
  • Flaky, not from this change: one local full run failed users.multi.e2e.ts's beforeAll (ensureLearner: Prisma "Operation has timed out" on the shared database); the next two runs passed in full. The same setup timed out twice during C32 whenever two test processes wrote to the shared SQLite file at once. Worth a card: a retry in multi-fixture.ts, or a busy timeout on its connection.

🤖 Generated with Claude Code

GauranshMathur and others added 2 commits October 16, 2026 11:27
…ign up and Setup

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ale registration comments corrected

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@GauranshMathur
GauranshMathur marked this pull request as ready for review October 16, 2026 11:34
@GauranshMathur

Copy link
Copy Markdown
Owner Author

Security review (orchestrator-dispatched, 2026-10-16): verdict fix first. All three decisions hold: the cap applies on Setup, on Sign up and over HTTP; a refusal stores nothing; errors are fixed strings. Counting is cheap, and no path today changes a name without the create hook.

To fix in this PR:

  1. A long name now reveals whether an email has an Account. Better Auth looks the email up before the create hook runs. On an open Instance, a long name gets 422 for a known email and 400 for an unknown one, and nothing is created. Check the name in hooks.before as well, before the lookup.
  2. A blank name is stored over HTTP. Refuse a name that is empty after trimming in the same places.

Open for the maintainer: zero-width, control and bidi characters in names, which allow display spoofing on Users. Routed to C38: OAuth's updateUser name writes skip the create hook. Codex second opinion did not run (not installed).

🤖 Generated with Claude Code

GauranshMathur and others added 2 commits October 16, 2026 11:50
…il lookup, so it is no probe

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@GauranshMathur

Copy link
Copy Markdown
Owner Author

Fixes verified (orchestrator, 2026-10-16) at abee3d5, updated with main, CI green. hooks.before on /sign-up/email refuses a blank or too-long name right after the admission check, before Better Auth looks the email up. A known and an unknown email now get the same 400, and a closed Instance still answers 403 first. The create hook keeps the same check as a backstop. Merging.

🤖 Generated with Claude Code

@GauranshMathur
GauranshMathur merged commit c8c6730 into main Oct 16, 2026
10 checks passed
@GauranshMathur
GauranshMathur deleted the C33c-name-cap branch October 16, 2026 12:01
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