-
Notifications
You must be signed in to change notification settings - Fork 208
SELF-1381 (feat): Belgian (Arman) ID overflow format #1450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughVersion bump from 2.9.5 to 2.9.6 across Android and iOS platforms. Introduces TD1 overflow-aware MRZ document handling with check digit validation, extraction logic, and non-truncating padding. Removes passportNumber length validation guard in mobile SDK. Changes
Sequence Diagram(s)sequenceDiagram
participant Scanner as MRZ Scanner
participant Detector as TD1 Detector
participant Extractor as Document Extractor
participant Parser as MRZ Parser
participant Validator as Check Digit Validator
participant Result as Result Mapper
Scanner->>Detector: Detect MRZ block (3 lines, 30 chars)
alt TD1 Overflow Format
Detector->>Extractor: Route to TD1 handler
Extractor->>Extractor: Extract document number from line 1
Extractor->>Validator: Validate check digit
Validator-->>Extractor: ✓ Valid / ✗ Invalid
Extractor->>Parser: Parse remaining MRZ fields<br/>(dateOfBirth, dateOfExpiry)
Parser-->>Extractor: Parsed fields
Extractor->>Extractor: Store in overrideDocumentNumber
Extractor-->>Result: Full validated document number
else Standard Format
Detector->>Result: Use standard processing
end
Result->>Result: Map to result dictionary<br/>using overrideDocumentNumber if present
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (2)
🧰 Additional context used📓 Path-based instructions (4)**/{mobile,client,app,time,verification}/**/*.{ts,tsx,js,swift,kt}📄 CodeRabbit inference engine (.cursor/rules/compliance-verification.mdc)
Files:
**/{mobile,client,app,proof,zk}/**/*.{ts,tsx,js,swift,kt}📄 CodeRabbit inference engine (.cursor/rules/compliance-verification.mdc)
Files:
app/ios/**/*📄 CodeRabbit inference engine (app/AGENTS.md)
Files:
⚙️ CodeRabbit configuration file
Files:
app/android/**/*📄 CodeRabbit inference engine (app/AGENTS.md)
Files:
⚙️ CodeRabbit configuration file
Files:
🧠 Learnings (3)📓 Common learnings📚 Learning: 2025-11-18T12:17:14.819ZApplied to files:
📚 Learning: 2025-07-29T01:08:28.530ZApplied to files:
⏰ Context from checks skipped due to timeout of 300000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
🔇 Additional comments (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| print("[LiveMRZScannerView] TD1 document number check digit is INVALID") | ||
| return nil | ||
| } | ||
|
|
||
| // print("[LiveMRZScannerView] Belgium MRZ result: \(belgiumMRZResult)") | ||
| print("[LiveMRZScannerView] TD1 document number validated: \(documentNumber) ✓") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove PII logging in TD1 overflow handler
The new TD1 overflow flow prints the MRZ line and validated document number with unconditional print calls, e.g. logging documentNumber at line 228, and the surrounding lines are not wrapped in #if DEBUG. The app/AGENTS.md checklist explicitly calls out “No sensitive data in logs (PII, credentials, tokens)”, but this code will emit passport/ID numbers whenever a TD1 document is scanned in production builds, leaking PII to device logs.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (2)
packages/mobile-sdk-alpha/src/processing/mrz.ts (1)
188-202: Good addition of TD1 overflow support for Android.The commented-out length check allows document numbers exceeding 9 characters to pass through. Per the learnings, this TypeScript layer is only reached on Android (iOS uses native Swift parsing in
LiveMRZScannerView.swift). The remaining date validations correctly ensuredateOfBirthanddateOfExpiryare exactly 6 characters.Consider removing the commented code entirely rather than leaving it in place, since the explanatory comment adequately documents the intent.
export function checkScannedInfo(passportNumber: string, dateOfBirth: string, dateOfExpiry: string): boolean { // TD1 overflow format allows document numbers > 9 characters per ICAO 9303: // When document numbers exceed 9 characters, the overflow digits are stored in the optional data // field and the full document number is reconstructed during MRZ parsing. - // if (passportNumber.length > 9) { - // return false; - // } if (dateOfBirth.length !== 6) { return false; }app/ios/LiveMRZScannerView.swift (1)
13-13: Minor: Redundant nil initialization.Per SwiftLint, initializing an optional with
nilis redundant since optionals default tonil.- @State private var overrideDocumentNumber: String? = nil // for TD1 overflow format (ID cards) + @State private var overrideDocumentNumber: String? // for TD1 overflow format (ID cards)
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
app/android/app/build.gradle(1 hunks)app/ios/LiveMRZScannerView.swift(5 hunks)app/ios/OpenPassport/Info.plist(1 hunks)app/ios/Self.xcodeproj/project.pbxproj(2 hunks)app/package.json(1 hunks)packages/mobile-sdk-alpha/src/processing/mrz.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (19)
app/**/*.{ts,tsx,js,jsx,json,yml,yaml}
📄 CodeRabbit inference engine (app/AGENTS.md)
Ensure
yarn nicepasses (fixes linting and formatting) before creating a PR
Files:
app/package.json
app/android/**
📄 CodeRabbit inference engine (app/AGENTS.md)
Android build must succeed via
yarn androidcommand in target environments
Files:
app/android/app/build.gradle
app/{ios,android}/**
📄 CodeRabbit inference engine (app/AGENTS.md)
Document complex native module changes and explain platform-specific code paths
Files:
app/android/app/build.gradleapp/ios/OpenPassport/Info.plistapp/ios/LiveMRZScannerView.swiftapp/ios/Self.xcodeproj/project.pbxproj
app/android/**/*
⚙️ CodeRabbit configuration file
app/android/**/*: Review Android-specific code for:
- Platform-specific implementations
- Performance considerations
- Security best practices for mobile
Files:
app/android/app/build.gradle
app/ios/**
📄 CodeRabbit inference engine (app/AGENTS.md)
iOS build must succeed via
yarn ioscommand in target environments
Files:
app/ios/OpenPassport/Info.plistapp/ios/LiveMRZScannerView.swiftapp/ios/Self.xcodeproj/project.pbxproj
app/ios/**/*
⚙️ CodeRabbit configuration file
app/ios/**/*: Review iOS-specific code for:
- Platform-specific implementations
- Performance considerations
- Security best practices for mobile
Files:
app/ios/OpenPassport/Info.plistapp/ios/LiveMRZScannerView.swiftapp/ios/Self.xcodeproj/project.pbxproj
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{js,jsx,ts,tsx}: NEVER log sensitive data including PII (names, DOB, passport numbers, addresses), credentials, tokens, API keys, private keys, or session identifiers.
ALWAYS redact/mask sensitive fields in logs using consistent patterns (e.g.,***-***-1234for passport numbers,J*** D***for names).
Files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx,js,jsx}: Use React Navigation withcreateStaticNavigationfor type-safe navigation in React Native applications.
Implement platform-specific handling withPlatform.OS === 'ios' ? 'iOS' : 'Android'checks before platform-specific code in React Native.
Initialize native modules withinitializeNativeModules()before any native operations in React Native.
Implement lazy loading for screens usingReact.lazy()in React Native applications.
Implement custom modal system withuseModalhook and callback registry in React Native.
Integrate haptic feedback usinguseHapticNavigationhook in React Native navigation.
Use platform-specific initial routes: web uses 'Home', mobile uses 'Splash' in React Navigation.
Use Zustand for global state management in React Native applications.
Use custom hooks for complex state (useModal,useHapticNavigation) instead of inline logic.
Use AsyncStorage for simple data, SQLite for complex data, and Keychain for sensitive data in React Native.
Use@/alias for src imports and@tests/alias for test imports in TypeScript/JavaScript files.
Use conditional rendering with Platform.OS for platform-specific code in React Native.
Use Tamagui for UI components in React Native applications.
Do not log sensitive data in production, including identity verification and passport information.
Use Keychain for secure storage of sensitive data in React Native.
Implement proper cleanup of sensitive data after use.
Implement certificate validation for passport data verification.
Always use try-catch for async operations in React Native and TypeScript code.
Implement graceful degradation when native modules fail in React Native.
Provide user-friendly error messages in UI and error handlers.
Lazy load screens and components to optimize bundle size in React Native.
Prevent memory leaks in native modules in React Native.
Files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
**/*.{tsx,jsx,ts,js}
📄 CodeRabbit inference engine (.cursorrules)
Implement proper cleanup in useEffect and component unmount hooks in React.
Files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
packages/mobile-sdk-alpha/src/processing/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/mobile-sdk-migration.mdc)
Migrate MRZ processing helpers from app/src/utils/ to packages/mobile-sdk-alpha/src/processing/ with comprehensive tests for MRZ parsing and cross-platform compatibility
Files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/mobile-sdk-migration.mdc)
**/*.{ts,tsx,js}: Never log PII, credentials, or private keys in production code; use DEBUG_SECRETS_TOKEN flag for debug-level secrets
Use consistent redaction patterns for sensitive fields in logs and test data
Files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
packages/mobile-sdk-alpha/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/mobile-sdk-migration.mdc)
packages/mobile-sdk-alpha/src/**/*.{ts,tsx}: Test platform-specific code paths for React Native (iOS and Android) and web implementations
Ensure all exports in migrated modules support tree shaking for optimal bundle size
Use TypeScript adapter pattern for cross-platform implementations with consistent interfaces
Implement consistent SDKError class for error handling with error codes and details
Use lazy loading for modules to optimize runtime performance and memory usage
Use async/await patterns for non-blocking operations throughout SDK modules
Provide comprehensive TypeScript type definitions and maintain type safety across SDK exports
Create clear and actionable error messages with descriptive error codes for SDK consumers
Support flexible configuration options and partner branding customization in SDK integrations
Files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
packages/mobile-sdk-alpha/**/*.{ts,tsx}
📄 CodeRabbit inference engine (packages/mobile-sdk-alpha/AGENTS.md)
Use TypeScript with strict type checking for this package
Files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
packages/mobile-sdk-alpha/**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (packages/mobile-sdk-alpha/AGENTS.md)
Configure ESLint with TypeScript-specific rules
Files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
packages/mobile-sdk-alpha/**/*.{js,ts,tsx,json,md,yml,yaml}
📄 CodeRabbit inference engine (packages/mobile-sdk-alpha/AGENTS.md)
Use Prettier for code formatting with root Prettier and EditorConfig settings
Files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
packages/mobile-sdk-alpha/**/*.{ts,tsx,js,jsx}
⚙️ CodeRabbit configuration file
packages/mobile-sdk-alpha/**/*.{ts,tsx,js,jsx}: Review alpha mobile SDK code for:
- API consistency with core SDK
- Platform-neutral abstractions
- Performance considerations
- Clear experimental notes or TODOs
Files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
**/{mobile,client,app,time,verification}/**/*.{ts,tsx,js,swift,kt}
📄 CodeRabbit inference engine (.cursor/rules/compliance-verification.mdc)
Use server-signed time tokens or chain block timestamps for trusted time in mobile clients, do not trust device wall-clock alone
Files:
app/ios/LiveMRZScannerView.swift
**/{mobile,client,app,proof,zk}/**/*.{ts,tsx,js,swift,kt}
📄 CodeRabbit inference engine (.cursor/rules/compliance-verification.mdc)
**/{mobile,client,app,proof,zk}/**/*.{ts,tsx,js,swift,kt}: Include trusted time anchor in proof generation and verify time anchor authenticity before proof generation in mobile implementations
Achieve proof generation in <60 seconds on mid-tier mobile devices
Files:
app/ios/LiveMRZScannerView.swift
app/**/*.{ts,tsx,js,jsx,swift,kt,java}
📄 CodeRabbit inference engine (app/AGENTS.md)
app/**/*.{ts,tsx,js,jsx,swift,kt,java}: Flag security-sensitive operations and note performance implications in code comments
Ensure no sensitive data (PII, credentials, tokens) is present in logs
Files:
app/ios/LiveMRZScannerView.swift
🧠 Learnings (17)
📓 Common learnings
Learnt from: CR
Repo: selfxyz/self PR: 0
File: .cursor/rules/mobile-sdk-migration.mdc:0-0
Timestamp: 2025-11-25T14:07:55.494Z
Learning: Applies to packages/mobile-sdk-alpha/src/processing/**/*.{ts,tsx} : Migrate MRZ processing helpers from app/src/utils/ to packages/mobile-sdk-alpha/src/processing/ with comprehensive tests for MRZ parsing and cross-platform compatibility
Learnt from: seshanthS
Repo: selfxyz/self PR: 1337
File: packages/mobile-sdk-alpha/src/processing/mrz.ts:189-194
Timestamp: 2025-11-18T12:17:14.819Z
Learning: In packages/mobile-sdk-alpha/src/processing/mrz.ts, the checkScannedInfo function and related TD1 extraction/validation logic are only reached on Android. iOS uses native Swift parsing (LiveMRZScannerView.swift) that bypasses this TypeScript layer.
Learnt from: CR
Repo: selfxyz/self PR: 0
File: packages/mobile-sdk-alpha/AGENTS.md:0-0
Timestamp: 2025-11-25T14:08:51.167Z
Learning: Applies to packages/mobile-sdk-alpha/**/*.test.{ts,tsx} : Verify `extractMRZInfo()` using published sample MRZ strings (e.g., ICAO examples) rather than fake data
Learnt from: CR
Repo: selfxyz/self PR: 0
File: .cursor/rules/mobile-sdk-migration.mdc:0-0
Timestamp: 2025-11-25T14:07:55.494Z
Learning: Applies to packages/mobile-sdk-alpha/samples/**/*.{ts,tsx,js,jsx} : Create sample React Native and web applications in packages/mobile-sdk-alpha/samples/ demonstrating MRZ parsing, proof flow, and iOS URL scheme integration
📚 Learning: 2025-11-25T14:08:51.167Z
Learnt from: CR
Repo: selfxyz/self PR: 0
File: packages/mobile-sdk-alpha/AGENTS.md:0-0
Timestamp: 2025-11-25T14:08:51.167Z
Learning: Applies to packages/mobile-sdk-alpha/**/package.json : Ensure exports are properly configured before publishing the package
Applied to files:
app/package.json
📚 Learning: 2025-11-25T14:08:51.167Z
Learnt from: CR
Repo: selfxyz/self PR: 0
File: packages/mobile-sdk-alpha/AGENTS.md:0-0
Timestamp: 2025-11-25T14:08:51.167Z
Learning: Applies to packages/mobile-sdk-alpha/**/package.json : Verify package conditions are valid before publishing
Applied to files:
app/package.json
📚 Learning: 2025-07-29T01:08:28.530Z
Learnt from: transphorm
Repo: selfxyz/self PR: 795
File: app/android/app/build.gradle:157-158
Timestamp: 2025-07-29T01:08:28.530Z
Learning: For this React Native project, the team prefers build flexibility over fail-fast behavior for release builds in app/android/app/build.gradle. They intentionally allow fallback to debug signing for local development runs, relying on Google Play Console validation to catch any improperly signed releases during upload.
Applied to files:
app/android/app/build.gradle
📚 Learning: 2025-11-25T14:07:55.494Z
Learnt from: CR
Repo: selfxyz/self PR: 0
File: .cursor/rules/mobile-sdk-migration.mdc:0-0
Timestamp: 2025-11-25T14:07:55.494Z
Learning: Applies to packages/mobile-sdk-alpha/src/processing/**/*.{ts,tsx} : Migrate MRZ processing helpers from app/src/utils/ to packages/mobile-sdk-alpha/src/processing/ with comprehensive tests for MRZ parsing and cross-platform compatibility
Applied to files:
packages/mobile-sdk-alpha/src/processing/mrz.tsapp/ios/LiveMRZScannerView.swift
📚 Learning: 2025-11-18T12:17:14.819Z
Learnt from: seshanthS
Repo: selfxyz/self PR: 1337
File: packages/mobile-sdk-alpha/src/processing/mrz.ts:189-194
Timestamp: 2025-11-18T12:17:14.819Z
Learning: In packages/mobile-sdk-alpha/src/processing/mrz.ts, the checkScannedInfo function and related TD1 extraction/validation logic are only reached on Android. iOS uses native Swift parsing (LiveMRZScannerView.swift) that bypasses this TypeScript layer.
Applied to files:
packages/mobile-sdk-alpha/src/processing/mrz.tsapp/ios/LiveMRZScannerView.swift
📚 Learning: 2025-11-25T14:08:51.167Z
Learnt from: CR
Repo: selfxyz/self PR: 0
File: packages/mobile-sdk-alpha/AGENTS.md:0-0
Timestamp: 2025-11-25T14:08:51.167Z
Learning: Applies to packages/mobile-sdk-alpha/**/*.test.{ts,tsx} : Verify `extractMRZInfo()` using published sample MRZ strings (e.g., ICAO examples) rather than fake data
Applied to files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
📚 Learning: 2025-11-25T14:07:28.166Z
Learnt from: CR
Repo: selfxyz/self PR: 0
File: .cursor/rules/compliance-verification.mdc:0-0
Timestamp: 2025-11-25T14:07:28.166Z
Learning: Applies to **/{compliance,ofac,verification,identity}/**/*.{ts,tsx,js,py} : Validate passport numbers by removing whitespace/punctuation and performing country-specific format validation
Applied to files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
📚 Learning: 2025-11-25T14:08:51.167Z
Learnt from: CR
Repo: selfxyz/self PR: 0
File: packages/mobile-sdk-alpha/AGENTS.md:0-0
Timestamp: 2025-11-25T14:08:51.167Z
Learning: Applies to packages/mobile-sdk-alpha/**/*.test.{ts,tsx} : Test `isPassportDataValid()` with realistic, synthetic passport data and never use real user PII
Applied to files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
📚 Learning: 2025-11-25T14:07:55.494Z
Learnt from: CR
Repo: selfxyz/self PR: 0
File: .cursor/rules/mobile-sdk-migration.mdc:0-0
Timestamp: 2025-11-25T14:07:55.494Z
Learning: Applies to packages/mobile-sdk-alpha/src/validation/**/*.{ts,tsx} : Migrate document validation logic from app/src/utils/ to packages/mobile-sdk-alpha/src/validation/ with unit tests for each validation rule and edge cases
Applied to files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
📚 Learning: 2025-10-23T12:08:55.529Z
Learnt from: shazarre
Repo: selfxyz/self PR: 1236
File: packages/mobile-sdk-alpha/src/flows/onboarding/document-nfc-screen.tsx:356-378
Timestamp: 2025-10-23T12:08:55.529Z
Learning: In packages/mobile-sdk-alpha/src/flows/onboarding/document-nfc-screen.tsx, the NFC native events emitted via NativeEventEmitter are generic status strings (e.g., "PACE succeeded", "BAC failed", "Reading DG1 succeeded") and do not contain sensitive MRZ data or passport numbers, so they do not require sanitization before logging.
Applied to files:
packages/mobile-sdk-alpha/src/processing/mrz.tsapp/ios/LiveMRZScannerView.swift
📚 Learning: 2025-11-25T14:07:28.166Z
Learnt from: CR
Repo: selfxyz/self PR: 0
File: .cursor/rules/compliance-verification.mdc:0-0
Timestamp: 2025-11-25T14:07:28.166Z
Learning: Applies to **/{compliance,ofac,verification,identity}/**/*.{ts,tsx,js,py} : Implement three-tier OFAC verification system: Passport Number Check (direct passport validation), Name + DOB Check (full name with exact date of birth), and Name + Year Check (name with year of birth, defaulting to Jan-01)
Applied to files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
📚 Learning: 2025-11-25T14:07:55.494Z
Learnt from: CR
Repo: selfxyz/self PR: 0
File: .cursor/rules/mobile-sdk-migration.mdc:0-0
Timestamp: 2025-11-25T14:07:55.494Z
Learning: Applies to packages/mobile-sdk-alpha/src/index.ts : Re-export all new migrated modules via packages/mobile-sdk-alpha/src/index.ts and document them in packages/mobile-sdk-alpha/README.md
Applied to files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
📚 Learning: 2025-11-25T14:07:55.494Z
Learnt from: CR
Repo: selfxyz/self PR: 0
File: .cursor/rules/mobile-sdk-migration.mdc:0-0
Timestamp: 2025-11-25T14:07:55.494Z
Learning: Applies to packages/mobile-sdk-alpha/src/attestation/**/*.{ts,tsx} : Migrate attestation verification from app/src/utils/ to packages/mobile-sdk-alpha/src/attestation/ with tests for PCR0 validation and certificate chain validation
Applied to files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
📚 Learning: 2025-11-25T14:06:55.954Z
Learnt from: CR
Repo: selfxyz/self PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T14:06:55.954Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Implement certificate validation for passport data verification.
Applied to files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
📚 Learning: 2025-11-25T14:07:28.166Z
Learnt from: CR
Repo: selfxyz/self PR: 0
File: .cursor/rules/compliance-verification.mdc:0-0
Timestamp: 2025-11-25T14:07:28.166Z
Learning: Applies to **/{age,verification,identity,compliance}/**/*.{ts,tsx,js,py} : Implement age verification with day-level precision for 'olderThan' checks accepting ISO 8601 date inputs
Applied to files:
packages/mobile-sdk-alpha/src/processing/mrz.ts
📚 Learning: 2025-11-22T23:16:26.696Z
Learnt from: transphorm
Repo: selfxyz/self PR: 1446
File: .github/workflows/mobile-bundle-analysis.yml:117-117
Timestamp: 2025-11-22T23:16:26.696Z
Learning: In the selfxyz/self repository, for mobile workflows (bundle analysis, deployment, CI/CD):
- Both iOS and Android builds now cache Ruby gems at the unified path `app/vendor/bundle`
- The previous separate paths (app/ios/vendor/bundle for iOS) have been deprecated in favor of this unified approach
Applied to files:
app/ios/Self.xcodeproj/project.pbxproj
🪛 SwiftLint (0.57.0)
app/ios/LiveMRZScannerView.swift
[Warning] 13-13: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
🔇 Additional comments (7)
app/android/app/build.gradle (1)
137-138: LGTM - Version bump to 2.9.3.Version name updated consistently with the iOS and package.json changes.
app/ios/OpenPassport/Info.plist (1)
23-24: LGTM - iOS version string updated to 2.9.3.Consistent with the Android and package.json version bumps.
app/ios/Self.xcodeproj/project.pbxproj (2)
549-549: LGTM - MARKETING_VERSION updated in Debug configuration.Version 2.9.3 is consistent across all platform configurations.
689-689: LGTM - MARKETING_VERSION updated in Release configuration.Both Debug and Release configurations are now aligned at 2.9.3.
app/package.json (1)
2-3: LGTM - Package version updated to 2.9.3.Version is synchronized across Android, iOS, and package.json.
app/ios/LiveMRZScannerView.swift (2)
89-112: Well-implemented ICAO 9303 check digit calculation.The weighting scheme
[7, 3, 1]and character-to-value mappings correctly follow the ICAO 9303 standard for MRZ check digit calculation.
289-298: Good routing logic for TD1 overflow detection.The detection heuristic (3+ lines with first line exactly 30 characters) correctly identifies TD1 format MRZ blocks and routes them to the specialized overflow handler before falling back to the existing correction logic.
| /// Extracts and validates the document number from TD1 MRZ line 1, handling both standard and overflow formats. | ||
| /// TD1 format uses an overflow mechanism when document numbers exceed 9 digits. | ||
| /// Example overflow format: IDBEL595392450<8039<<<<<<<<<< where positions 6-14 contain the principal part (595392450), | ||
| /// position 15 contains the overflow indicator (<), positions 16-18 contain overflow digits (803), and position 19 contains the check digit (9). | ||
| /// The full document number becomes: 595392450803. | ||
| /// This overflow format can occur for any country using TD1 MRZ (ID cards). | ||
| private func extractAndValidateTD1DocumentNumber(line1: String) -> (documentNumber: String, isValid: Bool)? { | ||
| guard line1.count == 30 else { return nil } | ||
|
|
||
| // extracting positions 6-14 (9 characters - principal part) | ||
| let startIndex6 = line1.index(line1.startIndex, offsetBy: 5) | ||
| let endIndex14 = line1.index(line1.startIndex, offsetBy: 14) | ||
| let principalPart = String(line1[startIndex6..<endIndex14]) | ||
|
|
||
| // checking position 15 for overflow indicator | ||
| let pos15Index = line1.index(line1.startIndex, offsetBy: 14) | ||
| let pos15 = line1[pos15Index] | ||
|
|
||
| if pos15 != "<" { | ||
| // handling standard format where position 15 is the check digit | ||
| let checkDigit = Int(String(pos15)) ?? -1 | ||
| let calculatedCheck = calculateMRZCheckDigit(principalPart) | ||
| let isValid = (checkDigit == calculatedCheck) | ||
| print("[extractAndValidateTD1DocumentNumber] Standard format: \(principalPart), check=\(checkDigit), calculated=\(calculatedCheck), valid=\(isValid)") | ||
| return (principalPart, isValid) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| private func cleanBelgiumDocumentNumber(doc9: String, doc3: String, checkDigit: String) -> String? { | ||
| // For Belgium TD1 format: IDBEL000001115<7027 | ||
| // doc9 = "000001115" (9 digits) | ||
| // doc3 = "702" (3 digits after <) | ||
| // checkDigit = "7" (single check digit) | ||
| // handling overflow format: scanning positions 16+ until we hit < | ||
| let pos16Index = line1.index(line1.startIndex, offsetBy: 15) | ||
| let remainingPart = String(line1[pos16Index...]) | ||
|
|
||
| // finding the overflow digits and the check digit | ||
| var overflowDigits = "" | ||
| var checkDigitChar: Character? | ||
|
|
||
| var cleanDoc9 = doc9 | ||
| // Strip first 3 characters | ||
| let startIndex = cleanDoc9.index(cleanDoc9.startIndex, offsetBy: 3) | ||
| cleanDoc9 = String(cleanDoc9[startIndex...]) | ||
| for char in remainingPart { | ||
| if char == "<" { | ||
| break | ||
| } | ||
| overflowDigits.append(char) | ||
| } | ||
|
|
||
| guard overflowDigits.count > 0 else { | ||
| print("[extractAndValidateTD1DocumentNumber] ERROR: No overflow digits found") | ||
| return nil | ||
| } | ||
|
|
||
| let fullDocumentNumber = cleanDoc9 + doc3 | ||
| // extracting check digit (last character of overflow) | ||
| checkDigitChar = overflowDigits.last | ||
| let overflowWithoutCheck = String(overflowDigits.dropLast()) | ||
|
|
||
| // constructing full document number: principal + overflow (without check digit) | ||
| let fullDocumentNumber = principalPart + overflowWithoutCheck | ||
|
|
||
| return fullDocumentNumber | ||
| // validating check digit against full document number | ||
| guard let checkDigitChar = checkDigitChar, | ||
| let checkDigit = Int(String(checkDigitChar)) else { | ||
| print("[extractAndValidateTD1DocumentNumber] ERROR: Invalid check digit") | ||
| return nil | ||
| } | ||
| let calculatedCheck = calculateMRZCheckDigit(fullDocumentNumber) | ||
| let isValid = (checkDigit == calculatedCheck) | ||
|
|
||
| #if DEBUG | ||
| print("[extractAndValidateTD1DocumentNumber] Overflow format:") | ||
| print(" Principal part (6-14): \(principalPart)") | ||
| print(" Overflow with check: \(overflowDigits)") | ||
| print(" Overflow without check: \(overflowWithoutCheck)") | ||
| print(" Full document number: \(fullDocumentNumber)") | ||
| print(" Check digit: \(checkDigit)") | ||
| print(" Calculated check: \(calculatedCheck)") | ||
| print(" Valid: \(isValid)") | ||
| #endif | ||
|
|
||
| return (fullDocumentNumber, isValid) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Solid TD1 overflow extraction logic with one concern: document numbers logged in production.
The extraction and validation logic correctly handles both standard TD1 format (9-digit document number with check digit at position 15) and the overflow format per ICAO 9303. The check digit validation against the reconstructed full document number is properly implemented.
However, lines 137-138 log the document number in production builds (outside #if DEBUG). Per coding guidelines, passport/document numbers are PII and should never be logged in production.
- print("[extractAndValidateTD1DocumentNumber] Standard format: \(principalPart), check=\(checkDigit), calculated=\(calculatedCheck), valid=\(isValid)")
+ #if DEBUG
+ print("[extractAndValidateTD1DocumentNumber] Standard format: [REDACTED], check=\(checkDigit), calculated=\(calculatedCheck), valid=\(isValid)")
+ #endif
return (principalPart, isValid)🤖 Prompt for AI Agents
In app/ios/LiveMRZScannerView.swift around lines 114 to 189, remove or guard the
production logs that print document numbers (lines ~137-138 and any other prints
that expose principalPart/fullDocumentNumber) so PII is never logged in
non-DEBUG builds; wrap all diagnostic print statements that include document
numbers in #if DEBUG ... #endif blocks or replace them with non-PII status logs
(e.g., "document validation succeeded/failed") and, if needed, log only a
redacted/hashed identifier instead of the raw document number.
| /// Processes TD1 documents (ID cards) by manually extracting and validating the document number using the overflow format handler, | ||
| /// then parses the remaining MRZ fields (name, dates, etc.) using QKMRZParser. This bypasses QKMRZParser's validation for the | ||
| /// document number field since it doesn't handle TD1 overflow format correctly. | ||
| private func processTD1DocumentWithOverflow(result: String, parser: QKMRZParser) -> QKMRZResult? { | ||
| print("[LiveMRZScannerView] Processing TD1 document with manual overflow validation") | ||
|
|
||
| guard let correctedBelgiumLine = correctBelgiumDocumentNumber(result: result) else { | ||
| print("[LiveMRZScannerView] Failed to correct Belgium document number") | ||
| return nil | ||
| } | ||
|
|
||
| // print("[LiveMRZScannerView] Belgium corrected line: \(correctedBelgiumLine)") | ||
|
|
||
| // Split MRZ into lines and replace the first line | ||
| let lines = result.components(separatedBy: "\n") | ||
| guard lines.count >= 3 else { | ||
| print("[LiveMRZScannerView] Invalid MRZ format - not enough lines") | ||
| return nil | ||
| } | ||
|
|
||
| let originalFirstLine = lines[0] | ||
| // print("[LiveMRZScannerView] Original first line: \(originalFirstLine)") | ||
|
|
||
| // Pad the corrected line to 30 characters (TD1 format) | ||
| let paddedCorrectedLine = correctedBelgiumLine.padding(toLength: 30, withPad: "<", startingAt: 0) | ||
| // print("[LiveMRZScannerView] Padded corrected line: \(paddedCorrectedLine)") | ||
| let line1 = lines[0] | ||
| print("[LiveMRZScannerView] Line 1: \(line1)") | ||
|
|
||
| // Reconstruct the MRZ with the corrected first line | ||
| var correctedLines = lines | ||
| correctedLines[0] = paddedCorrectedLine | ||
| let correctedMRZString = correctedLines.joined(separator: "\n") | ||
| // print("[LiveMRZScannerView] Corrected MRZ string: \(correctedMRZString)") | ||
| // extracting and validating document number manually using overflow format handler | ||
| guard let (documentNumber, isDocNumberValid) = extractAndValidateTD1DocumentNumber(line1: line1) else { | ||
| print("[LiveMRZScannerView] Failed to extract TD1 document number") | ||
| return nil | ||
| } | ||
|
|
||
| guard let belgiumMRZResult = parser.parse(mrzString: correctedMRZString) else { | ||
| print("[LiveMRZScannerView] Belgium MRZ result is not valid") | ||
| if !isDocNumberValid { | ||
| print("[LiveMRZScannerView] TD1 document number check digit is INVALID") | ||
| return nil | ||
| } | ||
|
|
||
| // print("[LiveMRZScannerView] Belgium MRZ result: \(belgiumMRZResult)") | ||
| print("[LiveMRZScannerView] TD1 document number validated: \(documentNumber) ✓") | ||
|
|
||
| // Try the corrected MRZ first | ||
| if isValidMRZResult(belgiumMRZResult) { | ||
| return belgiumMRZResult | ||
| // parsing the original MRZ to get all other fields (name, birthdate, etc.) | ||
| // using QKMRZParser for non-documentNumber fields | ||
| guard let mrzResult = parser.parse(mrzString: result) else { | ||
| print("[LiveMRZScannerView] Failed to parse MRZ with QKMRZParser") | ||
| return nil | ||
| } | ||
|
|
||
| // If document number is still invalid, try single character correction | ||
| if !belgiumMRZResult.isDocumentNumberValid { | ||
| if let correctedResult = singleCorrectDocumentNumberInMRZ(result: correctedMRZString, docNumber: belgiumMRZResult.documentNumber, parser: parser) { | ||
| // print("[LiveMRZScannerView] Single correction successful: \(correctedResult)") | ||
| if isValidMRZResult(correctedResult) { | ||
| return correctedResult | ||
| } | ||
| } | ||
| // validating that other fields are also correct | ||
| if !mrzResult.isBirthdateValid || !mrzResult.isExpiryDateValid { | ||
| print("[LiveMRZScannerView] TD1 document has invalid birthdate or expiry date") | ||
| return nil | ||
| } | ||
|
|
||
| return nil | ||
| #if DEBUG | ||
| print("[LiveMRZScannerView] QKMRZParser extracted fields:") | ||
| print(" countryCode: \(mrzResult.countryCode)") | ||
| print(" surnames: \(mrzResult.surnames)") | ||
| print(" givenNames: \(mrzResult.givenNames)") | ||
| print(" birthdate: \(mrzResult.birthdate?.description ?? "nil")") | ||
| print(" sex: \(mrzResult.sex ?? "nil")") | ||
| print(" expiryDate: \(mrzResult.expiryDate?.description ?? "nil")") | ||
| print(" personalNumber: \(mrzResult.personalNumber)") | ||
| print(" Parser's documentNumber: \(mrzResult.documentNumber)") | ||
| print(" Our validated documentNumber: \(documentNumber)") | ||
| #endif | ||
|
|
||
| // storing the manually validated full document number | ||
| // this will be used for NFC chip authentication (BAC keys) | ||
| overrideDocumentNumber = documentNumber | ||
| #if DEBUG | ||
| print("[LiveMRZScannerView] Set overrideDocumentNumber to: \(documentNumber)") | ||
| #endif | ||
|
|
||
| // returning MRZ result, the document number will be overridden in mapVisionResultToDictionary | ||
| return mrzResult | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TD1 processing flow is well-structured, but production logs expose PII.
The processTD1DocumentWithOverflow function correctly:
- Validates document number using the overflow format handler
- Parses remaining fields via QKMRZParser
- Validates birthdate and expiry dates
- Stores the validated document number for NFC chip authentication
However, lines 206, 215, 228 log document numbers and MRZ line data in production builds. These contain PII and should be guarded with #if DEBUG or redacted.
private func processTD1DocumentWithOverflow(result: String, parser: QKMRZParser) -> QKMRZResult? {
- print("[LiveMRZScannerView] Processing TD1 document with manual overflow validation")
+ #if DEBUG
+ print("[LiveMRZScannerView] Processing TD1 document with manual overflow validation")
+ #endif
let lines = result.components(separatedBy: "\n")
guard lines.count >= 3 else {
- print("[LiveMRZScannerView] Invalid MRZ format - not enough lines")
+ #if DEBUG
+ print("[LiveMRZScannerView] Invalid MRZ format - not enough lines")
+ #endif
return nil
}
let line1 = lines[0]
- print("[LiveMRZScannerView] Line 1: \(line1)")
+ #if DEBUG
+ print("[LiveMRZScannerView] Line 1: [REDACTED]")
+ #endif
// extracting and validating document number manually using overflow format handler
guard let (documentNumber, isDocNumberValid) = extractAndValidateTD1DocumentNumber(line1: line1) else {
- print("[LiveMRZScannerView] Failed to extract TD1 document number")
+ #if DEBUG
+ print("[LiveMRZScannerView] Failed to extract TD1 document number")
+ #endif
return nil
}
if !isDocNumberValid {
- print("[LiveMRZScannerView] TD1 document number check digit is INVALID")
+ #if DEBUG
+ print("[LiveMRZScannerView] TD1 document number check digit is INVALID")
+ #endif
return nil
}
- print("[LiveMRZScannerView] TD1 document number validated: \(documentNumber) ✓")
+ #if DEBUG
+ print("[LiveMRZScannerView] TD1 document number validated: [REDACTED] ✓")
+ #endif
// parsing the original MRZ to get all other fields (name, birthdate, etc.)
// using QKMRZParser for non-documentNumber fields
guard let mrzResult = parser.parse(mrzString: result) else {
- print("[LiveMRZScannerView] Failed to parse MRZ with QKMRZParser")
+ #if DEBUG
+ print("[LiveMRZScannerView] Failed to parse MRZ with QKMRZParser")
+ #endif
return nil
}
// validating that other fields are also correct
if !mrzResult.isBirthdateValid || !mrzResult.isExpiryDateValid {
- print("[LiveMRZScannerView] TD1 document has invalid birthdate or expiry date")
+ #if DEBUG
+ print("[LiveMRZScannerView] TD1 document has invalid birthdate or expiry date")
+ #endif
return nil
}🤖 Prompt for AI Agents
In app/ios/LiveMRZScannerView.swift around lines 202–265, several print
statements (notably at ~206, ~215, ~228) emit MRZ line data and the document
number in production; wrap all prints that expose PII (line1, parser
documentNumber, our validated documentNumber, and any raw MRZ content) in #if
DEBUG / #endif blocks or redact the sensitive values before logging, and ensure
overrideDocumentNumber assignment remains but its logging is only inside DEBUG;
update all debug prints in this function accordingly so no PII is logged in
release builds.
|
not sure why the demo sdk e2e is failing...but tested the mobile build and it builds fine |
0f3abc8 to
4a91ca7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| minSdkVersion rootProject.ext.minSdkVersion | ||
| targetSdkVersion rootProject.ext.targetSdkVersion | ||
| versionCode 121 | ||
| versionName "2.9.5" | ||
| versionName "2.9.6" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bump Android versionCode for 2.9.6
The Android defaultConfig now advertises versionName 2.9.6 but still uses versionCode 121; Google Play enforces monotonically increasing versionCode values, so if 2.9.5 was already uploaded at 121 this build will be rejected as a duplicate and the 2.9.6 release cannot be shipped.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the final PR Bugbot will review for you during this billing cycle
Your free Bugbot reviews will reset on January 17
Details
Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
| if lines.count >= 3 && lines[0].count == 30 { | ||
| // trying overflow validation | ||
| if let td1Result = processTD1DocumentWithOverflow(result: result, parser: parser) { | ||
| handleValidMRZResult(td1Result) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TD1 documents lose OCR correction fallback for document numbers
The old processBelgiumDocument function included a fallback to singleCorrectDocumentNumberInMRZ when the document number check digit was invalid (likely due to OCR errors). The new processTD1DocumentWithOverflow function lacks this fallback—when isDocNumberValid is false, it simply returns nil. Additionally, the main flow now returns early at line 297 for ALL TD1 documents (not just Belgium), preventing them from reaching the singleCorrectDocumentNumberInMRZ fallback at lines 301-308. This regression means TD1 documents with minor OCR errors in the document number that could have been corrected by single character replacement will now fail to scan.
Additional Locations (1)
| value = Int(String(char)) ?? 0 | ||
| } else if char.isLetter { | ||
| // mapping letters to values: A=10, B=11, ..., Z=35 | ||
| value = Int(char.asciiValue ?? 0) - Int(Character("A").asciiValue ?? 0) + 10 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check digit calculation incorrect for lowercase letters
The calculateMRZCheckDigit function uses char.isLetter which matches both uppercase and lowercase letters, but the value calculation assumes uppercase ASCII values ('A' = 65). For lowercase letters like 'a' (ASCII 97), the calculation produces 97 - 65 + 10 = 42 instead of the correct value 10. This would cause incorrect check digit results if OCR produces lowercase characters, potentially rejecting valid documents. While MRZ data is defined as uppercase per ICAO 9303, the function would silently produce wrong results rather than handling this case explicitly.
Related pr:
https://github.com/selfxyz/self/pulls
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Chores
✏️ Tip: You can customize this high-level summary in your review settings.
Note
Improves MRZ handling for TD1 IDs where document numbers overflow beyond 9 chars and ensures correct NFC BAC key generation.
LiveMRZScannerViewadds manual TD1 line-1 extraction/check-digit validation, overridesdocumentNumberin results, and only applies when TD1 parsing is neededPassportReader.padno longer truncates values ≥ field length to preserve full TD1 document numbers for MRZ key derivationcheckScannedInfoto allow document numbers > 9 (TD1 overflow per ICAO 9303)versionNameand iOSCFBundleShortVersionString/MARKETING_VERSIONto2.9.6Written by Cursor Bugbot for commit 9ff8bef. This will update automatically on new commits. Configure here.