PST-016: Username existence is confirmed by the sign-up form and by Settings, Account #745
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#745
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found in the security audit of 2026-10-10 (
SECURITY-AUDIT-2026-10-10.md), run againstmainatc30851bfrom 8 to 10 October 2026, as PST-016. Severity: Low.The text below is the audit's entry, unchanged apart from links to the other issues it names. The finding was produced by automated review and checked by a second automated pass; re-check it before acting on it.
src/postulo/accounts/forms.py(1600-1605)Who can do it, and what they need. Unauthenticated stranger while registration is open; any account holder otherwise. No other precondition.
How it works.
POST
/accounts/signup/withusername=<candidate>(registration open) or POST/settings/account/withusername=<candidate>while signed in; the response says "A user with that username already exists." exactly when the finding is somebody's sign-in name. Verified by scratch test for both forms (HITS: ['A user with that username already exists.']). Combined with the pre-claim issue this turns a username list into an email list.Impact. Enumeration of sign-in identifiers across the instance by any member (and by strangers when registration is open). Usernames are low-sensitivity on their own but are half of what the lockout oracle needs, and the threat model says every surface "never confirms whether a record exists".
Evidence.
Verification (verdict: confirmed; severity after verification: Low). Keep low (arguably informational for a default, invite-only instance). Preconditions: for the stranger path, an operator has opened registration (or the attacker holds an invitation link), and the signup rate limit is 20 probes per minute per IP. For the member path, any signed-in account, with no throttle on POST /settings/account/. Impact is limited to confirming that a lowercase username exists on the instance. Email existence is not leaked at signup because ACCOUNT_PREVENT_ENUMERATION is on. The sentence 'the threat model says every surface never confirms whether a record exists' should be dropped, since that line is about the API. The email-list claim should read: confirms a guessed email against a known username through the shared per-account lockout key, one account at a time, at the cost of locking the victim out for five minutes. Remediation to suggest: throttle the settings username check (as phone_numbers.collision_message does with throttle.consume), and accept or document the signup disclosure as inherent to username-based sign-up.
Suggested remediation. Accept this as inherent to usernames and document it, or reduce the surface: rate-limit username changes per account (allauth has no limit on Settings → Account), and consider removing the username from sign-in/signup (
ACCOUNT_LOGIN_METHODS = {"email"}) so a username is a display handle rather than a credential that is worth enumerating.Also covered by this issue.
src/postulo/accounts/forms.py(1600-1605); Informational). Reveals which usernames exist, contradicting the signup help text 'Nobody else sees it'. Usernames are not secrets and are shown to administrators, so impact is minimal; this is the standard behaviour of any unique-username system.Fixed in
d4c87ab54on main.