Skip to content

fix(pin): coalesce concurrent PIN check/confirm (lockout under-count + torn salt/hash)#669

Open
joshuakrueger-dfx wants to merge 1 commit into
RealUnitCH:stagingfrom
joshuakrueger-dfx:joshua/fix-657-p2-pin-race
Open

fix(pin): coalesce concurrent PIN check/confirm (lockout under-count + torn salt/hash)#669
joshuakrueger-dfx wants to merge 1 commit into
RealUnitCH:stagingfrom
joshuakrueger-dfx:joshua/fix-657-p2-pin-race

Conversation

@joshuakrueger-dfx
Copy link
Copy Markdown
Collaborator

@joshuakrueger-dfx joshuakrueger-dfx commented Jun 3, 2026

Addresses Issue #657 — Part 2 Auth & PIN, findings F-01 (HIGH, security) and F-09 (HIGH).

Problem

  • F-01 (verify_pin_cubit.dart): concurrent checkPin() calls for one submission shared no serialization → two parallel wrong attempts read the same failed-attempts baseline and persisted the same incremented value, under-counting the lockout cascade and widening the brute-force window.
  • F-09 (setup_pin_cubit.dart): _confirmPin() could run twice (confirm re-typed during the slow PBKDF2 hash), interleaving two salt+hash writes into a torn pair that no longer verifies → user locked out of their own wallet.

Fix

Both critical sections are wrapped in a private in-flight Future that coalesces concurrent callers onto one round-trip and clears on completion → the lockout counter advances exactly once and the salt+hash pair is always written atomically. No public API added.

Tests (RED→GREEN)

  • verify_pin_cubit_test.dartF-01: two parallel checkPin of one submission advance the counter once
  • setup_pin_cubit_test.dartF-09: confirm re-typed during the in-flight hash writes one atomic salt+hash
  • Full PIN suite green (31 passed), flutter analyze clean.

…-count and torn salt+hash

Concurrent VerifyPinCubit.checkPin() calls for one submission shared no
serialization, so two parallel wrong attempts both read the same
failed-attempts baseline and persisted the same incremented value —
under-counting the lockout cascade and widening the brute-force window.
SetupPinCubit._confirmPin() could likewise run twice (a confirm re-typed
during the slow PBKDF2 hash), interleaving two salt+hash writes into a
torn pair that no longer verifies and locks the user out of their own
wallet. Both critical sections are now wrapped in a private in-flight
Future that coalesces concurrent callers onto one round-trip and clears
on completion, so the counter advances exactly once and the salt+hash
pair is always written atomically from a single run. No public API added.

Regression: test/screens/pin/verify_pin_cubit_test.dart:267
Regression: test/screens/pin/setup_pin_cubit_test.dart:163
Boundary: Tier 1
Architect-spec: fixer/fix-spec.md (loop fleet-20260603-190231-84831-2, iter 1)
mandate-hash: b59fe886f6702d4c
loop-id: fleet-20260603-190231-84831-2
iteration: 1
@joshuakrueger-dfx joshuakrueger-dfx force-pushed the joshua/fix-657-p2-pin-race branch from d541f9b to a527c7f Compare June 4, 2026 16:58
@joshuakrueger-dfx joshuakrueger-dfx changed the base branch from develop to staging June 4, 2026 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant