Context
Surfaced during a security survey of the passwordless flows.
POST /login lets an unauthenticated caller distinguish a registered, verified user from an unknown one:
- A valid user gets an ephemeral token plus a
loginMethods array.
- An unknown identifier gets
401 { "error": "Not Allowed" }.
- An unverified user gets a distinct
401 { "error": "Login failed. Need to verify." }.
See src/controllers/authentication.ts (the login handler, roughly lines 40-167).
Note: the 401 vs 403 difference an automated scan may flag is not the real signal (those branches are DB-error paths where findOne throws, not the not-found path). The real enumeration comes from the response content differing between known and unknown identifiers.
Why it is subtle
Some enumeration is arguably inherent to passwordless login: the client legitimately needs to know which continuation methods are available. So this is a design tradeoff to decide deliberately, not an obvious bug.
Suggested work
- Decide the intended posture (uniform response vs. method disclosure) and document it.
- If tightening: return a uniform shape for unknown vs. unverified vs. valid, and gate method disclosure behind a later step.
- Add a test asserting the chosen behavior so it does not regress.
Acceptance
- A short note in the docs stating the intended enumeration posture, and code/tests matching it.
Context
Surfaced during a security survey of the passwordless flows.
POST /loginlets an unauthenticated caller distinguish a registered, verified user from an unknown one:loginMethodsarray.401 { "error": "Not Allowed" }.401 { "error": "Login failed. Need to verify." }.See
src/controllers/authentication.ts(theloginhandler, roughly lines 40-167).Note: the
401vs403difference an automated scan may flag is not the real signal (those branches are DB-error paths wherefindOnethrows, not the not-found path). The real enumeration comes from the response content differing between known and unknown identifiers.Why it is subtle
Some enumeration is arguably inherent to passwordless login: the client legitimately needs to know which continuation methods are available. So this is a design tradeoff to decide deliberately, not an obvious bug.
Suggested work
Acceptance