Skip to content

fix: add keychain-access-groups entitlement to preserve sessions across updates#562

Closed
DaniAkash wants to merge 4 commits intomainfrom
fix/add-keychain-access-groups
Closed

fix: add keychain-access-groups entitlement to preserve sessions across updates#562
DaniAkash wants to merge 4 commits intomainfrom
fix/add-keychain-access-groups

Conversation

@DaniAkash
Copy link
Copy Markdown
Contributor

@DaniAkash DaniAkash commented Mar 26, 2026

This pull request implements robust support for keychain access group migration on macOS, ensuring that encrypted user data (such as cookies and passwords) remains accessible after app updates or signing identity changes. It introduces logic to migrate the BrowserOS keychain item to a team-based access group, updates signing and entitlement configurations, and adds new code to handle keychain recovery and migration both at startup and during app updates.

Keychain Access Group Migration and Recovery:

  • Added new browseros_keychain_recovery.h and browseros_keychain_recovery.mm to check and migrate the BrowserOS Safe Storage keychain item to a team-based access group (<TeamID>.com.browseros), with interactive recovery if access is denied. This ensures future updates or signing identity changes do not break keychain access. [1] [2]
  • Modified keychain_password_mac.mm to always create and migrate the BrowserOS keychain item into the correct access group, both when adding and retrieving passwords, using the current Team ID from the app's code signature. [1] [2]
  • Updated app entitlements in app-entitlements.plist to include the new keychain access group, allowing the app to read/write the migrated keychain item.

App Signing and Build Process:

  • Enhanced the macOS signing process in macos.py to pin the designated requirement to the Team ID, ensuring keychain ACLs survive across builds with different binary hashes. The signing logic now passes the Team ID and logs a warning if it's missing. [1] [2] [3] [4]
  • Updated the build configuration (BUILD.gn) to include the new keychain recovery source files for macOS builds.

Update Process Integration:

  • Modified sparkle_glue.mm so that before an update replaces the app bundle, the keychain item's ACL is broadened to the team-based access group, ensuring the new app version can access the existing encryption key. [1] [2] [3]

These changes collectively ensure that BrowserOS users on macOS will not lose access to encrypted data after updates or signing changes, and that keychain items are always accessible to the app as long as it is signed by the same Team ID.

…ss updates

Add $(TeamIdentifierPrefix)com.browseros to keychain-access-groups in
app-entitlements.plist so that Keychain ACLs are tied to the Team ID
rather than the exact binary signature. This prevents users from losing
all sessions/cookies when BrowserOS is updated via Sparkle, since the
new binary's code signature no longer needs to exactly match the old one.
@github-actions github-actions bot added the fix label Mar 26, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 26, 2026

Greptile Summary

This PR implements a comprehensive, multi-layered strategy to keep the BrowserOS keychain encryption key accessible across app updates and signing-identity changes on macOS. It addresses the root cause (item ACL tied to a specific binary hash) at three independent layers: signing-time (Team ID–pinned designated requirement in macos.py), write-time (access group set on every AddGenericPassword/FindGenericPassword call in keychain_password_mac.mm), and update-time (ACL broadened via Sparkle's showReadyToInstallAndRelaunch callback in sparkle_glue.mm). The new MaybeMigrateKeychainAccess() function acts as a startup safety net for users upgrading from v0.44.0 who will not have received any of the preventive layers.\n\nKey changes:\n- browseros_keychain_recovery.mm — New startup migration; correctly uses SecKeychainFindGenericPassword (resolved from previous review) to trigger the macOS prompt for legacy ACL recovery, then promotes the item to the Team ID–scoped access group.\n- keychain_password_mac.mmGetBrowserOSAccessGroup() is cached via dispatch_once; access group is set opportunistically on every password create and read.\n- sparkle_glue.mmbroadenKeychainACLBeforeUpdate runs synchronously in the "ready to install" callback, before Sparkle atomically swaps the app bundle; includes full status logging.\n- macos.py — Designated requirement now includes certificate leaf[subject.OU] = \"<TeamID>\", so macOS considers any future build from the same team as satisfying the existing ACL.\n- app-entitlements.plist$(TeamIdentifierPrefix)com.browseros access group entitlement required for SecItemUpdate to be able to assign the group attribute.\n\nOne minor diagnostic gap: the SecItemUpdate results in both migration branches of MaybeMigrateKeychainAccess are silently discarded. The sparkle_glue.mm counterpart handles this correctly with explicit status logging; applying the same pattern here would make repeated recovery-dialog appearances easier to root-cause.

Confidence Score: 4/5

PR is on the happy path to merge; the previous critical review concern is resolved and the architecture is sound — one minor diagnostic gap remains in SecItemUpdate error handling.

The multi-layer keychain migration strategy is well-designed and the previously flagged SecKeychainSetUserInteractionAllowed no-op has been correctly fixed by switching to SecKeychainFindGenericPassword. The only remaining gap is that SecItemUpdate results are silently discarded in both branches of MaybeMigrateKeychainAccess, unlike the identical pattern in sparkle_glue.mm which logs all outcomes. This won't cause data loss or functional regression — the item remains accessible even if the access-group update fails — but will make repeated keychain prompts harder to diagnose. A one-line fix per call site resolves it.

packages/browseros/chromium_patches/chrome/browser/browseros/core/browseros_keychain_recovery.mm — SecItemUpdate status handling at lines 84–85 and 136–137

Important Files Changed

Filename Overview
packages/browseros/chromium_patches/chrome/browser/browseros/core/browseros_keychain_recovery.mm New file implementing startup keychain migration; uses SecKeychainFindGenericPassword for interactive recovery correctly (addressing prior review), but silently drops SecItemUpdate results in both migration branches
packages/browseros/chromium_patches/components/os_crypt/common/keychain_password_mac.mm Adds GetBrowserOSAccessGroup() with dispatch_once caching; migrates access group on both AddGenericPassword and FindGenericPassword paths; SecItemUpdate results are best-effort with documented intent in FindGenericPassword path
packages/browseros/chromium_patches/chrome/browser/mac/sparkle_glue.mm Adds broadenKeychainACLBeforeUpdate called from showReadyToInstallAndRelaunch; properly includes base/strings/sys_string_conversions.h and logs all SecItemUpdate outcomes
packages/browseros/build/modules/sign/macos.py Pins designated requirement to Team ID (certificate leaf[subject.OU]) so keychain ACLs survive binary hash changes; falls back gracefully with a log warning when team_id is absent
packages/browseros/resources/entitlements/app-entitlements.plist Adds keychain-access-groups entitlement with $(TeamIdentifierPrefix)com.browseros; required for SecItemUpdate to set kSecAttrAccessGroup on the keychain item
packages/browseros/chromium_patches/chrome/browser/browseros/core/browseros_keychain_recovery.h Clean header declaring MaybeMigrateKeychainAccess() with correct include guard and concise doc comment
packages/browseros/chromium_patches/chrome/browser/browseros/core/BUILD.gn Adds keychain_recovery source_set scoped to is_mac with correct Foundation and Security framework deps

Sequence Diagram

sequenceDiagram
    participant App as BrowserOS App
    participant KR as MaybeMigrateKeychainAccess
    participant KP as keychain_password_mac
    participant SG as SparkleGlue
    participant KC as macOS Keychain

    Note over App,KC: Startup (first launch after update from v0.44.0)
    App->>KR: MaybeMigrateKeychainAccess()
    KR->>KC: SecItemCopyMatching("BrowserOS Safe Storage")
    alt Access OK
        KC-->>KR: errSecSuccess
        KR->>KC: SecItemUpdate → set kSecAttrAccessGroup
        KR-->>App: return (migrated)
    else Item not found
        KC-->>KR: errSecItemNotFound
        KR-->>App: return (will be created on first use)
    else ACL mismatch (e.g. v0.44.0 → new binary)
        KC-->>KR: errSecAuthFailed
        KR->>KC: SecKeychainFindGenericPassword() [legacy prompt]
        KC-->>App: macOS dialog: "BrowserOS wants keychain access"
        App-->>KC: User clicks Allow
        KC-->>KR: errSecSuccess (binary added to ACL)
        KR->>KC: SecItemUpdate → set kSecAttrAccessGroup
        KR-->>App: return (recovered)
    end

    Note over App,KC: Normal operation — every password read/write
    App->>KP: GetOrCreateEncryptionKey()
    KP->>KC: AddGenericPassword / FindGenericPassword
    KC-->>KP: success
    KP->>KC: SecItemUpdate → set kSecAttrAccessGroup (best-effort)

    Note over App,KC: Before Sparkle installs update
    SG->>SG: showReadyToInstallAndRelaunch
    SG->>SG: broadenKeychainACLBeforeUpdate()
    SG->>KC: SecCodeCopySelf → get Team ID
    SG->>KC: SecItemUpdate → set kSecAttrAccessGroup = TeamID.com.browseros
    KC-->>SG: errSecSuccess / logged warning on failure
    SG->>App: proceed with update installation
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: packages/browseros/chromium_patches/chrome/browser/browseros/core/browseros_keychain_recovery.mm
Line: 84-85

Comment:
**`SecItemUpdate` result silently dropped in both migration paths**

The result of `SecItemUpdate` is never checked or logged here (or in the equivalent call at lines 136–137 in the recovery path). If the update fails — e.g., because `errSecItemNotFound` races with first-use creation, or because an entitlement mismatch prevents the write — the item never gets the access group set, and the same ACL-mismatch symptom will recur on every subsequent launch until the migration finally succeeds. The `sparkle_glue.mm` counterpart handles this correctly with a `LOG(WARNING)` on non-`errSecItemNotFound` failures; the same pattern would help here.

```suggestion
        OSStatus migrateStatus = SecItemUpdate(
            (__bridge CFDictionaryRef)updateQuery,
            (__bridge CFDictionaryRef)update);
        if (migrateStatus != errSecSuccess &&
            migrateStatus != errSecItemNotFound) {
          LOG(WARNING) << "browseros: Failed to set keychain access group"
                       << " (status=" << migrateStatus << ")";
        }
```

The same fix applies to the `SecItemUpdate` call at line 136 in the recovery branch — where the comment even asserts "SecItemUpdate will work because the user just granted access", making a silent failure especially confusing to diagnose.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (4): Last reviewed commit: "fix: add missing forward declaration and..." | Re-trigger Greptile

- Extend keychain_password_mac.mm patch to set kSecAttrAccessGroup on
  keychain writes and reads, ensuring items are stored under the shared
  BrowserOS access group rather than tied to a specific binary signature

- Pin the designated requirement in macos.py to the Team ID via
  certificate leaf[subject.OU] instead of wildcard /* exists */ checks,
  so Keychain ACLs survive across builds with different binary hashes

- Add browseros_keychain_recovery.mm that runs on startup to detect
  broken keychain access (errSecAuthFailed), prompt the user for access,
  and migrate the item to the BrowserOS access group

- Add broadenKeychainACLBeforeUpdate to sparkle_glue.mm that updates
  the keychain item's access group right before Sparkle replaces the
  app bundle, so the new binary can read it immediately

Resolves: TKT-666, TKT-668, TKT-669, TKT-670
@DaniAkash
Copy link
Copy Markdown
Contributor Author

@greptileai

SecKeychainSetUserInteractionAllowed only affects the legacy SecKeychain*
APIs, not SecItemCopyMatching. Replace the recovery path with
SecKeychainFindGenericPassword which actually triggers the macOS system
dialog asking the user to grant keychain access and updates the item's
ACL to include the new binary.
@DaniAkash
Copy link
Copy Markdown
Contributor Author

@greptileai

- Add broadenKeychainACLBeforeUpdate to SparkleGlue forward declaration
  so BrowserOSUserDriver can call it without -Werror compile failure
- Downgrade LOG(INFO) to VLOG(1) for normal keychain access paths that
  fire on every startup
@DaniAkash
Copy link
Copy Markdown
Contributor Author

@greptileai

@shadowfax92 shadowfax92 closed this Apr 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants