What should a failed-sign-in counter be keyed on, and how does keying it on the account alone become a weapon?
answer
- ask who the counter punishes
- the roster is the attacker's input
- account, source, or the pair
- escalate rather than latch
basics
~20 sKey the refusing counter on the account-and-source pair, and keep per-source and per-account counts for shape only. A counter keyed on the account alone lets anyone who knows a member's identifier keep that member out on demand.
solid answer
~50 sThree keys are available and they punish different people. Keyed on the account alone, the counter bounds one attacker guessing one member's password — and hands any stranger who knows that member's identifier a way to keep them out deliberately; a club roster is public enough to make that a targeted outage during a launch window. Keyed on the source alone, it refuses everyone sharing one connection, so a single fumbled password at the launch field can shut the whole club out, and an attacker with a pool of addresses steps around it anyway. Keyed on the pair, it bounds one source against one account, which is the honest unit for guessing, without giving strangers a lock to pull. Let the pair counter do the refusing, keep the other two as signals, and escalate — a delay, then an extra proof — rather than latching.
code
pseudocode · 17 lineson failed_signin(account_id, source):
pair = bump(counter_for(account_id, source))
acct = bump(counter_for(account_id))
src = bump(counter_for(source))
if pair >= PAIR_HARD:
return refuse_until(now() + cooldown(pair)) # clears itself
if pair >= PAIR_SOFT or src >= SRC_SOFT:
return require_extra_proof() # a challenge, not a latch
if acct >= ACCT_WATCH:
raise_alert(account_id) # visible, but not a refusal
return generic_failure()
on successful_signin(account_id, source):
reset(counter_for(account_id, source))
reset(counter_for(account_id))go deeper
Know that a failed-attempt counter has a key, and that the key decides who is refused. Counting per account means anyone who knows the member's name can cause the refusal.
Compare the three keys out loud and say what each one costs: the account key hands out a targeted outage, the source key punishes a shared connection, the pair key matches the behaviour you actually want to stop.
Price the refusal against the window it can close. Say which counter refuses and which only signals, and show a ladder that ends by itself instead of a latch someone else decides the duration of.
Decide what the club owes a member it refuses: a bounded wait with a stated end, a recovery route that avoids the refused credential, and a record good enough to tell sabotage from a member fumbling a password.
## Three keys, three different victims A failed-sign-in counter is not one design. It is a choice of **key**, and the key decides who gets refused when the count runs out. On the launch-authorisation site that matters concretely: a member who cannot sign in during the window cannot authorise a flight, so a refusal is not an inconvenience, it is the outcome someone might be aiming for. | keyed on | what it bounds | who it refuses | how it is stepped around | |---|---|---|---| | the account | guesses against one member | that member, whoever was guessing | spread the guesses across many accounts | | the source | volume from one origin | everyone behind that origin | rotate through a pool of origins | | the account-and-source pair | one origin working on one account | only that combination | many origins, one account; or many accounts, one origin | ## The account-only counter is a targeted outage The club publishes its roster, because that is what a club does. A counter keyed only on the account turns that roster into an input: type a member's identifier, spend the attempts, and that member is refused. No password is needed, no guess has to be close, and nothing about the account has to be known beyond the name on the list. The control bought to stop guessing has become a denial of service against a named person, and the person choosing the target is the attacker. The damage scales with what a refusal costs. If getting back in means waiting for an administrator, then during a three-hour window "locked out" and "not a member tonight" are the same sentence. This is the whole reason the key choice is a security decision rather than a tuning knob. ## The source-only counter refuses the innocent The opposite key fails the other way. At the launch field the club runs one uplink, and every member on it presents as the same origin. One member fumbling a password three times spends the budget for everyone standing around them. Meanwhile the attacker who prompted the control has no such constraint: distributing attempts across origins is cheap, and a per-source count is one of the easiest things to step around. So a per-source count is useful as a **signal** — this origin is behaving oddly — and poor as the thing that refuses. ## The pair, and what it does not bound The account-and-source pair is the closest match to the behaviour worth stopping: *this origin is working on this account*. It cannot be triggered against a member by a stranger who is not also spending their own attempts from their own origin, and it does not punish a whole field for one person's typo. Be honest about the gap. A pair counter says nothing about one origin trying one password against a thousand accounts, or a thousand origins trying one account. Those shapes are what the per-account and per-source counts are watching for, and the economics of spreading a guessing campaign that way is a subject of its own. Run more than one counter and be clear about which one refuses. ## A ladder, not a latch The second half of the design is what happens when a count is reached. A latch — refused until cleared by a human — is what makes the weapon worth using. A ladder costs the attacker the same and costs the member far less: 1. **A delay** that grows with the count, so a run slows to uselessness while a member who mistyped once notices nothing. 2. **An extra proof** — a challenge, or a factor the member already holds — which a guessing run cannot satisfy and a real member can. 3. **A time-boxed refusal that clears itself**, so the worst outcome is a wait with a known end rather than an open-ended one. 4. **A recovery path that does not run through the refused credential**, so a member who genuinely cannot get in has somewhere to go while the window is still open. And whatever the ladder says, the response to an unauthenticated caller should not announce that the account is locked, or you have added a membership oracle to the sign-in page. ## What clears a counter A successful sign-in clears the pair and the account count. A completed password reset clears them too — a member who has just recovered arriving at a refusal left over from the attack that caused the recovery is a support call you wrote yourself. Where those counts are kept and how they stay consistent across instances is a separate subject; what matters here is that something clears them and that nothing needs a human to. ## What is load-bearing Remove the ladder and keep a hard per-account latch, and you have traded a bound on guessing — which a long password already gives you most of — for a reliable, targeted outage that anyone holding the roster can trigger. The ladder is the load-bearing control; the key choice is what decides who carries the weight.
- How does a refused member get back in during a launch window, with no administrator on hand?By waiting out a refusal that has a stated end, or by taking a recovery path that does not run through the refused credential — a proof they already hold, or the reset flow. Anything requiring a human to clear it is a lock whose duration an attacker chooses, because they picked the moment to spend the attempts.
- Does a completed password reset clear the failed-attempt counts?It should. The counts usually exist because of the attack that prompted the reset, and leaving them standing means a member recovers their account and is then refused by the leftovers. A successful sign-in should clear them for the same reason. Neither clearing weakens the bound: a run that starts again starts counting again.
- What should the refusal tell the caller?As little as the sign-in page tells anyone else. Saying "this account is locked" to an unauthenticated caller confirms the account exists, and it confirms that the attempts landed, which is useful feedback for whoever is spending them. Keep the failure generic and put the detail in the message sent to the member instead.
A fire alarm any passer-by can pull. It exists to empty the building when there is a fire, and because anyone outside can trigger it, it is also the cheapest way to empty the building when there is not. A lockout keyed on the account alone is that alarm with a member's name written on it.
saying these in an interview costs you the question
- Keying only on the account, and calling the resulting outage a security feature
- Locking until an administrator clears it, with nothing that expires by itself
- Keying only on the source, which refuses everyone behind one shared connection
- Leaving the count standing after a successful sign-in or a completed reset
- Assuming an attacker cannot change source addresses cheaply
- Announcing "this account is locked" to an unauthenticated caller