Why
Several values in the browser sign-in flow are stored before they are bounded:
LoginBegintakes the account name from the request, builds the per-account rate-limit key as"login:"+name, and stores the name in the ceremony. The name is not checked againstidentity.ValidateName(2–32 characters) before either use. The rate limiter keeps keys until its 65,536-key cap and does not sweep expired entries before that, so the length of the key is chosen by the client. An unauthenticated request can grow the tables by the size of the name it sends.AddPasskeyBeginstores the request’s label in the ceremony and then inuser_credentials.validateCredentialbounds the credential id, public key and AAGUID, but not the label, transports, or attachment. An authenticated account can add records whose size it chooses.clientAddressreturns the configured forwarding header as-is, so an oversized header can become a rate-limit key when a proxy header is configured.
The ceremony map has a 4,096-entry cap and a five-minute TTL; the rate limiter has a 65,536-key cap. Both cap the number of entries, not the size of one.
Scope
- Validate and bound the account name before it reaches
beginAccountor a ceremony; use a fixed decoy name for invalid input so the response does not become a format oracle. - Bound the passkey label and the remaining credential metadata in
validateCredentialand at the begin handler, so the value is refused before the ceremony or the database stores it. - Bound or normalise
clientAddress— accept an IP or a short host token, and cap the key length inrateLimiter.allowas a backstop. - Sweep expired rate-limit entries as the table grows, not only at the cap, or state why the current behaviour is safe with bounded keys.
- Add tests for an oversized name, an oversized label, and an oversized forwarding header: each is refused or truncated, and the limiter map does not grow with input length.
Acceptance
- A request cannot make the rate-limit or ceremony tables grow in proportion to a client-supplied string.
- An invalid name still gets the same decoy response as an unknown account.
- Passkey labels and credential metadata are bounded in one place, refused before storage, and covered by tests.
- Any operator-visible limit is described in
docs/operations.md.
From the internal review for #106.