Skip to content

Conversation

@fedemkr
Copy link
Member

@fedemkr fedemkr commented Nov 28, 2025

🎟️ Tracking

PM-28855

📔 Objective

Add cipher changes subscription to update credential identities store on iOS extensions.
This differs from the current approach where all ciphers/credentials get updated to be done on individual cipher CUD helping memory efficiency.
As you can see the new CipherChangePublisher publishes only when "one" cipher operation was done, but it doesn't when there's a batch operation like replacing all ciphers to avoid memory problems on the extensions.

Important

This is a solution for iOS 17+ versions as they have incremental updates on the credential identities store. I didn't add the fallback as it'd imply replacing all identities from all ciphers which means loading and decrypting all (or most) ciphers which may crash on large vaults or have the extension use more memory leaving less for other operations like searching before crashing. The user can always force a sync on the main app to update the OS store and have autofill working for that specific credential again.

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

…tities store when cipher get CUD on iOS extensions.
@fedemkr fedemkr marked this pull request as ready for review November 28, 2025 20:23
@fedemkr fedemkr requested review from a team and matt-livefront as code owners November 28, 2025 20:24
@claude
Copy link

claude bot commented Nov 28, 2025

Claude finished @fedemkr's task —— View job


Code Review Complete

Summary

This PR implements memory-efficient credential identity updates for iOS extensions by subscribing to individual cipher changes instead of batch-loading the entire vault. The implementation is architecturally sound and well-tested.

Findings

Finding 1: Potential memory leak in deinit with Task cancellation

⚠️ Minor Issue

The deinit in AutofillCredentialService.swift:219-223 explicitly cancels and nils the task, but this may not be necessary as Task cancellation happens automatically when references are released. However, setting the reference to nil is good practice for clarity.

Current implementation
deinit {
    cipherChangesSubscriptionTask?.cancel()
    cipherChangesSubscriptionTask = nil
}

Recommendation: Consider if the explicit cancel() call is necessary, or if it's defensive programming. The current approach is safe but potentially redundant. This is acceptable given the safety-first approach.


Finding 2: Typo in DocC comment

⚠️ Minor Issue

AutofillCredentialService.swift:515 contains a typo: "identiteis" should be "identities"

/// - Parameter cipher: The cipher to get the credential identiteis from.

Should be:

/// - Parameter cipher: The cipher to get the credential identities from.

Finding 3: Error handling silently logs but doesn't retry

💭 Question

In AutofillCredentialService.swift:244-246, when cipherService.cipherChangesPublisher() throws an error, it's logged but the subscription ends permanently. Is this the intended behavior, or should there be a retry mechanism?

} catch {
    errorReporter.log(error: error)
}

If the publisher fails once (e.g., due to database corruption), the extension will stop receiving updates for the rest of its lifetime. Consider if this is acceptable or if recovery/retry logic is needed.


Finding 4: CipherChangePublisher doesn't filter by cipher type

💭 Observation

CipherChangePublisher.swift:140-168 emits changes for all cipher types, but AutofillCredentialService only processes login ciphers (implicitly through credentialIdentityFactory). Consider if filtering at the publisher level would improve performance.

Currently, deleted secure notes, cards, and identities trigger decryption operations that produce no identities. This may be intentional for simplicity, but filtering by type == .login in the publisher could reduce unnecessary work.


Finding 5: iOS 17+ availability guard spans large code blocks

🎨 Code Structure

Both upsertCredentialsInStore (lines 620-637) and removeCredentialsInStore (lines 599-616) have @available(iOS 17.0, *) guards with early returns. The entire method bodies are iOS 17+ only.

Consider alternative structure
@available(iOS 17.0, *)
private func upsertCredentialsInStore(for cipher: Cipher) async {
    guard await identityStore.state().isEnabled,
          await identityStore.state().supportsIncrementalUpdates else {
        return
    }
    // ... rest of implementation
}

And for iOS 16 and below, provide a no-op stub or handle differently in the calling code. This makes the availability requirements clearer at the method signature level.


Finding 6: Test coverage for error scenarios

🎨 Suggestion

AutofillCredentialService+AppExtensionTests.swift has excellent coverage for happy paths and disabled store scenarios, but could benefit from testing:

  1. What happens when getCredentialIdentities(from:) throws during insert/update
  2. What happens when identityStore.saveCredentialIdentities(_:) throws
  3. What happens when identityStore.removeCredentialIdentities(_:) throws

These error paths exist and are logged, but aren't explicitly tested. Consider adding tests to verify errors are properly logged and don't crash the subscription.


Good Practices Observed

  • Memory-efficient design using incremental updates instead of batch operations
  • Comprehensive test coverage including edge cases (store disabled, incremental updates not supported)
  • Proper use of weak self in async closures to prevent retain cycles
  • Context-aware initialization (main app vs extensions)
  • Appropriate iOS version availability checks
  • Well-documented public interfaces with DocC

Action Items

  1. ⚠️ Fix typo in line 515: "identiteis" → "identities"
  2. 💭 Consider whether error recovery/retry logic is needed for the cipher changes subscription
  3. 💭 Evaluate if filtering by cipher type in the publisher would improve performance
  4. 🎨 Consider restructuring iOS 17+ availability guards for clarity
  5. 🎨 Add tests for error handling scenarios in credential identity operations

@github-actions
Copy link
Contributor

Logo
Checkmarx One – Scan Summary & Details8ea8c1f3-b977-449a-a879-2d5d5dfb6249

Great job! No new security vulnerabilities introduced in this pull request

@codecov
Copy link

codecov bot commented Nov 28, 2025

Codecov Report

❌ Patch coverage is 94.90835% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.42%. Comparing base (b734949) to head (c310353).

Files with missing lines Patch % Lines
...e/Vault/Services/Stores/CipherDataStoreTests.swift 89.36% 10 Missing ⚠️
.../Autofill/Services/AutofillCredentialService.swift 90.90% 8 Missing ⚠️
...hared/Core/Vault/Services/CipherServiceTests.swift 87.50% 3 Missing ⚠️
.../AutofillCredentialService+AppExtensionTests.swift 98.83% 2 Missing ⚠️
...ore/Platform/Utilities/CipherChangePublisher.swift 98.52% 1 Missing ⚠️
...vices/Stores/TestHelpers/MockCipherDataStore.swift 90.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2169      +/-   ##
==========================================
+ Coverage   85.39%   85.42%   +0.03%     
==========================================
  Files        1731     1732       +1     
  Lines      145718   146209     +491     
==========================================
+ Hits       124434   124899     +465     
- Misses      21284    21310      +26     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

2 participants