Our Signup Form Would Tell You Who Our Customers Were

by Serguey Shinder

On a Sunday our signup endpoint took about sixty eight thousand requests, which is roughly what it normally takes in five months. Nobody was trying to break in. Somebody was asking it a question, over and over, and the question was whether a particular email address already had an account with us.

The form answered honestly every time. If the address was new, it moved on to the password step. If the address already existed, it said so, in a helpful sentence about signing in instead, and it said so noticeably faster, because a known address short circuits before we do any of the work of creating anything.

We had written that message deliberately and I still think the intention was right. Telling somebody they already have an account saves a support ticket and a good deal of confusion. What we had not considered is that the same sentence, asked a hundred thousand times by a script, is not a message to a person. It is a lookup service. By Monday morning somebody had a list of which addresses out of a much larger list belonged to our customers, and that fact is worth money to the sort of people who send convincing emails about your account.

The password reset page did the same thing in different wording. So did the newsletter unsubscribe form, which I would not have thought about for a year.

The fixes are dull and nearly all of them are about making the two cases indistinguishable. Both paths now return the same page with the same sentence, which says that if the address is registered we have sent an email to it, and then we send one of two different emails, one of which explains how to sign in. Both paths take the same time, because the code does the same work either way and throws half of it away. There are limits per address and per network, and a Sunday like that one now raises something with somebody.

We also went looking for every other place where a stranger can get a yes or a no out of us without signing in. There were six. A promotional code check that confirms whether a code is real. A file endpoint that answers not found for a file that does not exist and forbidden for one that does, which is my favourite, because the person who wrote it was being careful and precise.

What I watch for now is not the data we return. It is the difference between two answers. Different words, a different status code, a different number of milliseconds, a redirect that happens in one case and not in the other. Anything an unauthenticated stranger can ask repeatedly becomes a source of truth about your customers, and none of it looks like a leak when you read it one request at a time.

– Serguey Asael Shinder

Leave a Reply