Android had the lock and none of the back-off. iOS has counted failed unlocks
and imposed a growing wait since ios#41; here every attempt was free, so
somebody holding an unlocked phone could work through PINs as fast as the
prompt would reappear. Two platforms disagreeing about a security guarantee is
how one of them ends up being the one that leaks — the same argument that put
the lock on Android in the first place.
Same schedule as iOS: three free attempts, then 30 seconds doubling to a
five-minute ceiling. Both ends matter. A mistyped PIN or a fingerprint that did
not read is ordinary, and making the first slip cost a wait would punish the
owner far more often than anybody else; and an unbounded back-off locks
somebody out of their own messages permanently, which is worse than what it
defends against — no reset, no support desk, and the data is on their phone.
One real divergence from iOS, and it is a bug the Swift version cannot have.
iOS computes 30 * pow(2, steps) in a Double, which saturates to infinity and
still caps correctly. Kotlin shifting a Long does not: 30L shl 60 wraps
NEGATIVE, and minOf(negative, cap) returns the negative — a "wait" in the
past, which reads as no wait at all and silently removes the whole feature at
exactly the point somebody is grinding attempts. The steps are clamped before
the shift. Proven: removing the clamp turns that test red.
The refusal happens BEFORE the prompt is shown. Letting the system sheet
appear and then discarding the answer would teach somebody that attempts are
free, which is the opposite of the point.
Only a genuine wrong answer counts. onAuthenticationFailed is a
fingerprint that did not match; onAuthenticationError is a cancel, a platform
lockout or a hardware problem. Only the first moves the counter, so dismissing
the prompt cannot back the owner into a wait — and the platform's own lockout is
not counted on top of ours, because two back-offs stacking produce a wait nobody
can predict from either rule.
Switching the lock off clears any outstanding wait. Leaving one behind
strands somebody behind a countdown for a screen that no longer appears.
7 unit tests. App builds, every JVM suite green. PARITY.md gains a Hardening
section listing the three that remain (ios#19, #39, #45).
Android had the lock and none of the back-off. iOS has counted failed unlocks
and imposed a growing wait since ios#41; here every attempt was free, so
somebody holding an unlocked phone could work through PINs as fast as the
prompt would reappear. Two platforms disagreeing about a security guarantee is
how one of them ends up being the one that leaks — the same argument that put
the lock on Android in the first place.
Same schedule as iOS: three free attempts, then 30 seconds doubling to a
five-minute ceiling. Both ends matter. A mistyped PIN or a fingerprint that did
not read is ordinary, and making the first slip cost a wait would punish the
owner far more often than anybody else; and an unbounded back-off locks
somebody out of their own messages permanently, which is worse than what it
defends against — no reset, no support desk, and the data is on their phone.
One real divergence from iOS, and it is a bug the Swift version cannot have.
iOS computes `30 * pow(2, steps)` in a Double, which saturates to infinity and
still caps correctly. Kotlin shifting a Long does not: `30L shl 60` wraps
NEGATIVE, and `minOf(negative, cap)` returns the negative — a "wait" in the
past, which reads as no wait at all and silently removes the whole feature at
exactly the point somebody is grinding attempts. The steps are clamped before
the shift. Proven: removing the clamp turns that test red.
The refusal happens BEFORE the prompt is shown. Letting the system sheet
appear and then discarding the answer would teach somebody that attempts are
free, which is the opposite of the point.
Only a genuine wrong answer counts. `onAuthenticationFailed` is a
fingerprint that did not match; `onAuthenticationError` is a cancel, a platform
lockout or a hardware problem. Only the first moves the counter, so dismissing
the prompt cannot back the owner into a wait — and the platform's own lockout is
not counted on top of ours, because two back-offs stacking produce a wait nobody
can predict from either rule.
Switching the lock off clears any outstanding wait. Leaving one behind
strands somebody behind a countdown for a screen that no longer appears.
7 unit tests. App builds, every JVM suite green. PARITY.md gains a Hardening
section listing the three that remain (ios#19, #39, #45).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Android had the lock and none of the back-off. iOS has counted failed unlocks
and imposed a growing wait since ios#41; here every attempt was free, so
somebody holding an unlocked phone could work through PINs as fast as the
prompt would reappear. Two platforms disagreeing about a security guarantee is
how one of them ends up being the one that leaks — the same argument that put
the lock on Android in the first place.
Same schedule as iOS: three free attempts, then 30 seconds doubling to a
five-minute ceiling. Both ends matter. A mistyped PIN or a fingerprint that did
not read is ordinary, and making the first slip cost a wait would punish the
owner far more often than anybody else; and an unbounded back-off locks
somebody out of their own messages permanently, which is worse than what it
defends against — no reset, no support desk, and the data is on their phone.
One real divergence from iOS, and it is a bug the Swift version cannot have.
iOS computes
30 * pow(2, steps)in a Double, which saturates to infinity andstill caps correctly. Kotlin shifting a Long does not:
30L shl 60wrapsNEGATIVE, and
minOf(negative, cap)returns the negative — a "wait" in thepast, which reads as no wait at all and silently removes the whole feature at
exactly the point somebody is grinding attempts. The steps are clamped before
the shift. Proven: removing the clamp turns that test red.
The refusal happens BEFORE the prompt is shown. Letting the system sheet
appear and then discarding the answer would teach somebody that attempts are
free, which is the opposite of the point.
Only a genuine wrong answer counts.
onAuthenticationFailedis afingerprint that did not match;
onAuthenticationErroris a cancel, a platformlockout or a hardware problem. Only the first moves the counter, so dismissing
the prompt cannot back the owner into a wait — and the platform's own lockout is
not counted on top of ours, because two back-offs stacking produce a wait nobody
can predict from either rule.
Switching the lock off clears any outstanding wait. Leaving one behind
strands somebody behind a countdown for a screen that no longer appears.
7 unit tests. App builds, every JVM suite green. PARITY.md gains a Hardening
section listing the three that remain (ios#19, #39, #45).
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
assigned to @paulhitt
restored source branch
feat/app-lock-backoffdeleted the
fix/tests-must-not-publish-credentialsbranch. This merge request now targets themainbranchmentioned in commit
e6e7d1cc73changed the description