Skip to content

fix(macos/plist): add Bluetooth + privacy keys to prevent Gmeet sign-in crash (#1288)#1297

Merged
senamakel merged 5 commits intotinyhumansai:mainfrom
oxoxDev:fix/1288-macos-bluetooth-privacy-keys
May 6, 2026
Merged

fix(macos/plist): add Bluetooth + privacy keys to prevent Gmeet sign-in crash (#1288)#1297
senamakel merged 5 commits intotinyhumansai:mainfrom
oxoxDev:fix/1288-macos-bluetooth-privacy-keys

Conversation

@oxoxDev
Copy link
Copy Markdown
Contributor

@oxoxDev oxoxDev commented May 6, 2026

Summary

  • Adds NSBluetoothAlwaysUsageDescription to the macOS bundle Info.plist so embedded Chromium's WebAuthn caBLE / hybrid-transport probe (and enumerateDevices() Bluetooth audio listing) no longer SIGABRTs the production app on Google Meet sign-in.
  • Audits and ships six sibling preventative privacy keys (Location, Documents/Downloads/Desktop, Contacts, Calendars) in the same edit so future probes from embedded webviews can't crash the app.
  • Adds com.apple.security.device.bluetooth entitlement under the Hardened Runtime as belt-and-braces.
  • New Vitest unit test asserts all required NS*UsageDescription keys are present with non-empty descriptions — catches accidental removal.
  • Extends docs/RELEASE-MANUAL-SMOKE.md with three macOS line items covering the new privacy prompts.

Problem

Production OpenHuman desktop app on macOS 12.7.6 crashes on Google Meet web login (#1288). The crash report names the missing key:

This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSBluetoothAlwaysUsageDescription key with a string value explaining to the user how the app uses this data.

Root cause: macOS TCC enforces a hard SIGABRT (not graceful denial) when a non-sandboxed Hardened Runtime app probes a privacy-sensitive resource without the matching NS*UsageDescription key. Embedded Chromium walks CoreBluetooth via the WebAuthn caBLE/hybrid transport when Google sign-in offers cross-device passkey authentication ("use your phone as an authenticator"). The same probe is also reachable via getUserMedia() / enumerateDevices() audio peripheral listing on later macOS versions, so the description covers both surfaces.

Audit revealed six sibling privacy keys exposed to the same crash class — any embedded webview probe (Gmeet/Slack/Discord/Telegram) into Location, Documents/Downloads/Desktop folders, Contacts, or Calendars on macOS 11+ would SIGABRT a non-sandboxed Hardened Runtime app without the matching key. Fixing them in one PR prevents next-week QA crashes on a sibling key.

Solution

app/src-tauri/Info.plist now declares 10 privacy keys (was 3):

Key User-facing copy summary
NSMicrophoneUsageDescription unchanged
NSCameraUsageDescription unchanged
NSAppleEventsUsageDescription unchanged (#985)
NSBluetoothAlwaysUsageDescription cross-device passkey sign-in + audio device enumeration (NEW — Tier 1, fixes #1288)
NSLocationWhenInUseUsageDescription embedded webview geolocation (NEW — Tier 2)
NSDocumentsFolderUsageDescription embedded webview file picker (NEW — Tier 2)
NSDownloadsFolderUsageDescription embedded webview file picker (NEW — Tier 2)
NSDesktopFolderUsageDescription embedded webview file picker (NEW — Tier 2)
NSContactsUsageDescription embedded webview address book access (NEW — Tier 2)
NSCalendarsUsageDescription embedded webview calendar access (NEW — Tier 2)

app/src-tauri/entitlements.sidecar.plist adds com.apple.security.device.bluetooth (harmless under non-sandboxed Hardened Runtime, future-safe if Apple tightens the sandbox model).

app/test/info-plist-required-keys.test.ts (new): reads app/src-tauri/Info.plist, parses XML via DOMParser, asserts the 10 required keys exist with non-empty descriptions ≥ 30 chars (cheap placeholder check). Would have caught #1288 had it been in place pre-incident.

docs/RELEASE-MANUAL-SMOKE.md: three new macOS line items — Bluetooth prompt on first Gmeet sign-in (regression watch — hard fail mode is SIGABRT), Location no-crash invariant on Gmeet room-finder probe, file picker no-crash invariant for Documents/Downloads/Desktop.

Note on copy: macOS only fires the system prompt when the resource is actually probed — adding the keys does NOT pre-emptively annoy users.

Verification

  • plutil -lint both plists OK.
  • ✅ Vitest privacy-keys test passes 11/11.
  • ✅ Signed DMG built locally with APPLE_SIGNING_IDENTITY="OpenHuman Dev Signer" (per feedback_macos_release_build_signing.md). Bundle confirms:
    • flags=0x10000(runtime) — Hardened Runtime active
    • Authority=OpenHuman Dev Signer
    • All 10 NS*UsageDescription keys embedded in bundled Info.plist
    • com.apple.security.device.bluetooth entitlement embedded
  • ✅ Local repro on prod v0.53.17 (macOS 26.2, Apple M5) confirmed the SIGABRT — Termination Reason: Namespace TCC, Code 0 with the exact missing-key error text; faulting stack pinned to +[IOBluetoothDevice pairedDevices]-[IOBluetoothCoreBluetoothCoordinator init] from the CEF WebAuthn caBLE / passkey path during Google sign-in. Bug class is NOT Monterey-only — current macOS still hard-crashes on missing key.
  • Smoke pass on dev build with this PR: same machine, same Google sign-in flow that just crashed. Bundle reinstalled to /Applications/OpenHuman.app, BLE probe fired the proper TCC consent dialog instead of SIGABRT. Prompt rendered with title "OpenHuman" would like to use Bluetooth. and the body copy from the new NSBluetoothAlwaysUsageDescription string verbatim. Allow / Don't Allow both complete normally — no crash.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per docs/TESTING-STRATEGY.md
  • N/A: Diff coverage ≥ 80% — changed lines are XML plists + a markdown checklist + a Vitest test file (itself excluded from coverage). lcov does not instrument plist/markdown.
  • N/A: Coverage matrix updated — behaviour-only crash fix, no new feature row required in TEST-COVERAGE-MATRIX.md.
  • N/A: All affected feature IDs from the matrix listed under ## Related — no matrix row touched.
  • No new external network dependencies introduced (mock backend used per docs/TESTING-STRATEGY.md)
  • Manual smoke checklist updated if this touches release-cut surfaces (docs/RELEASE-MANUAL-SMOKE.md)
  • Linked issue closed via Closes #NNN in the ## Related section

Impact

  • Platforms affected: macOS only. Windows/Linux bundles ignore NS*UsageDescription keys — no behavioural change on those platforms.
  • Runtime: zero. Plist edits land in the bundled .app at build time, no Rust/TS code path changes. macOS only renders prompts when a resource is probed, so users see no surprise dialogs unless an embedded webview triggers the corresponding probe.
  • Security: positive — declared keys + matching entitlement convert hard SIGABRT crashes into the standard TCC consent flow (allow/deny). User retains full control via System Settings > Privacy & Security.
  • Migration: none. Existing installs simply gain prompt paths on next probe.
  • Performance: none.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Keep this section for AI-authored PRs. For human-only PRs, mark each field N/A.

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/1288-macos-bluetooth-privacy-keys
  • Commit SHA: c1da0c7

Summary by CodeRabbit

  • New Features

    • macOS now requests user permissions for Bluetooth, location, documents, downloads, desktop, contacts, and calendars access.
  • Tests

    • Added validation to ensure required macOS privacy descriptions are properly configured.
  • Documentation

    • Updated macOS release checklist with new smoke test items.

oxoxDev and others added 5 commits May 6, 2026 17:21
…eet sign-in crash (tinyhumansai#1288)

Embedded Chromium probes CoreBluetooth via WebAuthn caBLE / hybrid transport
when Google sign-in offers cross-device passkey authentication (phone-as-
authenticator). macOS TCC enforces hard SIGABRT (not graceful denial) when
a non-sandboxed Hardened Runtime app accesses a privacy-sensitive resource
without the matching usage-description key. The same Bluetooth probe is
also reachable via getUserMedia() / enumerateDevices() audio peripheral
listing on later macOS versions, so the description covers both surfaces.

Adding NSBluetoothAlwaysUsageDescription so the first probe prompts the
user instead of crashing the production app.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ew safety (tinyhumansai#1288)

Audit revealed six sibling privacy keys exposed to the same SIGABRT class as
NSBluetoothAlwaysUsageDescription: any embedded webview probe (Gmeet/Slack/
Discord/Telegram) into Location, Documents/Downloads/Desktop folders, Contacts,
or Calendars on macOS 11+ would crash a non-sandboxed Hardened Runtime app
without the matching usage-description key.

macOS only renders the system prompt when the resource is actually probed,
so adding the keys does NOT pre-emptively annoy users — the prompt path
exists only when a webview triggers it.

Keys added:
- NSLocationWhenInUseUsageDescription
- NSDocumentsFolderUsageDescription
- NSDownloadsFolderUsageDescription
- NSDesktopFolderUsageDescription
- NSContactsUsageDescription
- NSCalendarsUsageDescription

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dened Runtime (tinyhumansai#1288)

Belt-and-braces companion to NSBluetoothAlwaysUsageDescription in Info.plist.
Harmless under non-sandboxed Hardened Runtime (the current bundle config),
future-safe if Apple tightens the sandbox model in a later macOS release.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…#1288)

Vitest unit test reads `app/src-tauri/Info.plist`, parses XML via DOMParser,
asserts all 10 required NS*UsageDescription keys exist with non-empty
descriptions ≥ 30 chars (cheap placeholder check). Catches accidental
removal of any privacy key in future PRs — would have caught tinyhumansai#1288 had
it been in place pre-incident.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…yhumansai#1288)

Add three new line items under the macOS section covering the privacy
prompts gated on the keys added in tinyhumansai#1288:
- Bluetooth prompt on first Gmeet sign-in or audio device enumeration
  (regression watch — hard fail mode is SIGABRT before prompt renders).
- Location no-crash invariant on Gmeet room-finder probe.
- File picker no-crash invariant for Documents/Downloads/Desktop.

These line items make the issue's "Regression safety" acceptance criterion
concrete in the release-cut checklist.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@oxoxDev oxoxDev requested a review from a team May 6, 2026 12:24
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

📝 Walkthrough

Walkthrough

This PR adds missing macOS privacy usage descriptions (Bluetooth, Location, file access, Contacts, Calendars) to the app's Info.plist, enables Bluetooth device entitlement, and introduces validation tests to prevent regression of missing privacy declarations.

Changes

macOS Privacy Permissions & Validation

Layer / File(s) Summary
Privacy Declarations
app/src-tauri/Info.plist, app/src-tauri/entitlements.sidecar.plist
Seven NS…UsageDescription keys added: Bluetooth, Location, Documents, Downloads, Desktop, Contacts, Calendars. Bluetooth device entitlement (com.apple.security.device.bluetooth) enabled with explanatory comment.
Validation Logic
app/test/info-plist-required-keys.test.ts
New test suite parses Info.plist XML and validates each required privacy key has a non-empty user-facing description meeting minimum length. Also verifies declared keys match expected count.
Release Checklist
docs/RELEASE-MANUAL-SMOKE.md
Three new macOS smoke test items: Bluetooth prompt fires on first Google Meet call, Location prompt does not crash on room-finder probe, file picker does not crash on Documents/Downloads/Desktop selections.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • tinyhumansai/openhuman#721: Modifies macOS app manifests (Info.plist and entitlements.sidecar.plist) for privacy declarations and device entitlements.
  • tinyhumansai/openhuman#1000: Modifies docs/RELEASE-MANUAL-SMOKE.md checklist structure (same smoke test documentation file).

Poem

🐰 A rabbit hops through Info.plist fields,
Where privacy keys now bloom with seals,
Bluetooth, Location, Contacts too—
No more crashes when webviews call through!
Tests stand guard like watchful hares, 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main change: adding Bluetooth and privacy keys to prevent macOS crashes during Google Meet sign-in.
Linked Issues check ✅ Passed All acceptance criteria from #1288 are addressed: repro eliminated with privacy key additions, regression coverage via Vitest unit test, permission key present with descriptions, graceful handling enabled, and related privacy keys audited and added.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #1288: plist configurations, entitlement addition, regression test, and documentation updates for macOS privacy handling.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@oxoxDev
Copy link
Copy Markdown
Contributor Author

oxoxDev commented May 6, 2026

@0xpremgs — your repro on macOS 12.7.6 surfaced this. Local smoke on my machine couldn't deterministically reproduce the crash because Google offered local TouchID/platform authenticator instead of cross-device passkey, so the BLE probe path didn't fire during my sign-in.

Could you install the build from this PR and re-run the original Gmeet sign-in on the same Monterey machine? Specifically:

  1. Quit any running OpenHuman.
  2. tccutil reset Bluetooth com.openhuman.app (or System Settings → Privacy & Security → Bluetooth → toggle OpenHuman off, if listed).
  3. Install the PR build, launch from /Applications.
  4. Open Google Meet → start sign-in.
  5. Expected: macOS Bluetooth prompt fires once, copy starts with "OpenHuman uses Bluetooth for cross-device passkey sign-in (for example, when signing in to Google with your phone as an authenticator)…".
  6. Allow OR deny — neither path should crash.

Confirming the prompt copy renders + no crash closes the regression-safety acceptance criterion. Thanks!

@oxoxDev
Copy link
Copy Markdown
Contributor Author

oxoxDev commented May 6, 2026

Smoke pass — fix verified end-to-end on the same machine that just reproduced the crash.

Crash (prod v0.53.17, before fix)

Triggered Google sign-in inside Gmeet, picked cross-device passkey path. Hard SIGABRT, exact crash text from the macOS report:

Termination Reason: Namespace TCC, Code 0
This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSBluetoothAlwaysUsageDescription key with a string value explaining to the user how the app uses this data.

Faulting stack:

+[IOBluetoothDevice pairedDevices]
+[IOBluetoothCoreBluetoothCoordinator sharedInstance]
-[IOBluetoothCoreBluetoothCoordinator init]   ← TCC client crashes here

Called from ChromeWebAppShortcutCopierMain (CEF) — confirms the WebAuthn caBLE / hybrid-transport path is what probed CoreBluetooth.

Environment: macOS 26.2 (25C56), Apple M5 (Mac17,2), bundle id com.openhuman.app, signed by Steven Enamakel V76768QVFE. Bug class is NOT Monterey-only — current macOS still hard-crashes on missing key.

After PR build (this branch, same machine)

Replaced /Applications/OpenHuman.app with the locally-built signed bundle from this PR (Hardened Runtime, Authority OpenHuman Dev Signer, all 10 NS*UsageDescription keys + bluetooth entitlement embedded). Re-ran the same sign-in flow.

Result: macOS rendered the standard TCC consent dialog instead of SIGABRT.

  • Title: "OpenHuman" would like to use Bluetooth.
  • Body: full copy from the new NSBluetoothAlwaysUsageDescription value verbatim ("OpenHuman uses Bluetooth for cross-device passkey sign-in (for example, when signing in to Google with your phone as an authenticator) and to detect connected audio devices…").
  • Allow / Don't Allow both complete normally — no crash on either path.

This closes the regression-safety acceptance criterion and confirms the user-facing copy renders correctly.

(Screenshot of the prompt available locally; happy to drop it inline in the GitHub web UI if helpful.)

@senamakel senamakel merged commit 9379a2b into tinyhumansai:main May 6, 2026
21 of 23 checks passed
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.

Production app crashes during Google Meet login because Bluetooth usage description is missing

2 participants