RocketChat / RocketChat/Rocket.Chat.ReactNative

bug: Passcode attempt counter resets on remount lockout can be bypassed indefinitely

Open
#7,018 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

🐛 bug type: bug
Dominant language
TypeScript
Stars
2.4k
Forks
1.5k
Avg merge
1d 18h
Merged PRs (30d)
90

Description

Describe the Bug

In app/containers/Passcode/PasscodeEnter.tsx, the failed passcode attempt counter is a plain local variable (let attempts = 0) that resets to 0 every time the component remounts.

While failed attempts are persisted to AsyncStorage via setItem, the stored value is never read back on mount. getItem is never called for ATTEMPTS_KEY anywhere in the component — only setItem is destructured:

const { setItem: setAttempts } = useAsyncStorage(ATTEMPTS_KEY);

This means a user can bypass the lockout by backgrounding and reopening the app (or navigating away and back) before the final attempt, resetting the in-memory counter to 0 while AsyncStorage still holds the real count.

Steps to Reproduce
  1. Enable Passcode in the app (Profile → Security → Passcode → set any passcode)
  2. Lock the app and open the Passcode entry screen
  3. Enter the wrong passcode 5 times (one before the lockout threshold of 6)
  4. Background the app by pressing Home, then reopen it — [PasscodeEnter] remounts
  5. The attempt counter resets to 0 in memory even though AsyncStorage still holds "5"
  6. Enter 5 more wrong passcodes — lockout does not trigger
  7. Repeat steps 4–6 indefinitely to get unlimited passcode attempts
Expected Behavior

On mount, the component should read the persisted attempt count from AsyncStorage and resume from that value. A user who has already made 5 failed attempts should be locked out on the 6th wrong attempt regardless of whether the app was backgrounded or the component remounted between attempts.

Actual Behavior

The attempts variable is always initialized to 0 on every mount. The value saved in AsyncStorage is never read. A user can background and reopen the app before the final wrong attempt, getting a completely fresh set of 6 tries each time. Lockout is never permanently enforced and the passcode can be brute-forced with unlimited attempts.

Rocket.Chat Server Version

8.2.0

Rocket.Chat App Version

4.70.0.9999999999

Device Name

Realme 12+

OS Version

Android 15

Additional Context

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in app/containers/Passcode/PasscodeEnter.tsx, tracing the local attempts value and the useAsyncStorage(ATTEMPTS_KEY) setup, including the mount path. Verify that a remount resumes the persisted count and that the sixth wrong attempt triggers lockout without resetting the count.

Written by the indexing model from the issue text.

Assessment

Tech stack
react-native, typescript
Domain
authentication, mobile, security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.