Skip to content

[PM-39358] fix: Reject partial card scans via Luhn and brand-length validation#2845

Open
fedemkr wants to merge 1 commit into
mainfrom
PM-39358/fix-partial-card-scan
Open

[PM-39358] fix: Reject partial card scans via Luhn and brand-length validation#2845
fedemkr wants to merge 1 commit into
mainfrom
PM-39358/fix-partial-card-scan

Conversation

@fedemkr

@fedemkr fedemkr commented Jun 30, 2026

Copy link
Copy Markdown
Member

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-39358

📔 Objective

Partial OCR card scans could produce digit sequences that matched the regex in CardTextParser and were auto-filled into the card-number field despite being incomplete (e.g. the card was not fully in frame). This PR adds two validation layers inside CardTextParser.extractCardNumber(from:):

  1. Luhn mod-10 checksum — rejects numbers that fail the standard Luhn algorithm, which rules out nearly all truncated card numbers.
  2. Brand-specific digit-length enforcement — after inferring the brand from leading digits, confirms the digit count matches the expected PAN length for that network (e.g. Amex = 15, Visa = 13/16/19, Diners = 14).

CardComponent.Brand is extended with a validDigitLengths: Set<Int> property that drives the length check.

No UI changes; scanner behaviour is unchanged from the user's perspective — the form simply never populates from an invalid number.

…alidation

Partial OCR scans could produce digit sequences that matched the card-number
regex and were auto-filled despite being incomplete. CardTextParser now rejects
any candidate number that fails the Luhn mod-10 checksum or whose digit count
does not match the expected length for the detected brand.
@fedemkr fedemkr added the ai-review Request a Claude code review label Jun 30, 2026
@github-actions github-actions Bot added app:password-manager Bitwarden Password Manager app context t:bug Change Type - Bug labels Jun 30, 2026
@fedemkr fedemkr marked this pull request as ready for review June 30, 2026 20:33
@fedemkr fedemkr requested review from a team and matt-livefront as code owners June 30, 2026 20:33
@fedemkr fedemkr added the enhancement New feature or request label Jun 30, 2026
@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

Reviewed the addition of Luhn mod-10 checksum and brand-specific digit-length validation to CardTextParser.extractCardNumber(from:), plus the new CardComponent.Brand.validDigitLengths property. The validation is correctly placed at the single extraction chokepoint used by all callers, the Luhn implementation is the standard algorithm, and the brand-length tables match real-world PAN lengths. Test fixtures were updated consistently and new cases trace correctly through brand detection.

Code Review Details

No findings. Notable correctness checks that passed:

  • .other brand uses Set(13...19), identical to the pre-existing length guard, so unknown-brand scans are not newly rejected (no regression).
  • Brand-detection ordering (6011 → Discover before 60 → RuPay) keeps length checks aligned with the detected brand.
  • The parametrized fixture 60110009901394240006011000000000000001 was updated in both the input and expected arrays, keeping the test valid under the new Luhn check.

Comment on lines +172 to +189
/// Returns `true` if `digits` satisfies the Luhn mod-10 checksum.
private func isLuhnValid(_ digits: String) -> Bool {
var sum = 0
for (index, char) in digits.reversed().enumerated() {
guard var digit = char.wholeNumberValue else { return false }
if !index.isMultiple(of: 2) {
digit *= 2
if digit > 9 { digit -= 9 }
}
sum += digit
}
return sum.isMultiple(of: 10)
}

/// Returns `true` if `digits` has a length valid for the brand detected from its leading digits.
private func hasValidLength(_ digits: String) -> Bool {
CardComponent.Brand.detect(from: digits).validDigitLengths.contains(digits.count)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏️ Can you alphabetize these?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review Request a Claude code review app:password-manager Bitwarden Password Manager app context enhancement New feature or request t:bug Change Type - Bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants