Skip to content

fix: pairing token enforcement, QR pairing, background notifications - #24

Merged
HMAKT99 merged 4 commits into
mainfrom
fix/pairing-security-and-adoption
Jul 4, 2026
Merged

fix: pairing token enforcement, QR pairing, background notifications#24
HMAKT99 merged 4 commits into
mainfrom
fix/pairing-security-and-adoption

Conversation

@HMAKT99

@HMAKT99 HMAKT99 commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the production pairing flow (which was broken end-to-end), enforces the pairing token as a real security boundary, and removes the biggest first-run UX gaps.

Stacked on #21 — will retarget to main automatically once that merges.

Security: pairing token now enforced

Pairing on real devices was broken three ways:

  • iOS sent raw JSON that the daemon's wire-format decoder rejects
  • The QR pairing token was generated but never transmitted or validated — effective model was trust-on-first-connect
  • Devices were stored under the BLE central UUID instead of their own deviceID, so challenge verification and re-identify could never find the key

Now PairRequestMessage carries deviceID + pairingToken, the daemon validates the token against the active 5-minute pairing window (wrong/missing/expired → rejected), and devices are stored under their own ID. 5 new tests cover enforcement.

Daemon hardening

  • DaemonCoordinator shared state is now lock-guarded (was @unchecked Sendable with real races from CB callbacks + Tasks)
  • pendingAuthentications entries no longer leak on the timeout path
  • Expired challenges and replay nonces pruned every 60s at runtime
  • PAM socket timeout is now daemon timeout + 2s, so an approval landing exactly at the deadline isn't lost

UX

  • touchbridge-test pair renders and opens an actual QR code image
  • iOS app: camera QR scanner as the primary pairing path (manual JSON entry stays as fallback); pairing only reports success after the Mac accepts; rejections show recovery instructions
  • Backgrounded app now posts a local notification when an auth request arrives and runs Face ID when opened — previously the advertised "phone buzzes → approve" flow silently never fired

Docs / distribution

  • New scripts/patch-pam.sh — setup.md sent Homebrew users to a script that didn't exist, so cask users couldn't activate sudo auth at all; now shipped in the pkg with a curl fallback for existing installs
  • build-release.sh versions artifacts from the git tag (v1.0.0 release had shipped 0.1.0-named artifacts)
  • Test counts synced to the verified 127 (README said 91, CHANGELOG 129); versions bumped to 1.0.0 in menubar/companion/SECURITY.md; stale single-device limitation corrected

Verification

  • daemon: 111 tests pass (swift test)
  • protocol: 16 tests pass
  • iOS app builds for simulator (with new scanner + camera permission)
  • menubar app builds Release; PAM universal binary compiles with -Werror

HMAKT99 added 3 commits July 4, 2026 10:10
Pairing was broken in production three ways: the iOS app sent raw JSON
the daemon's wire decoder rejects, the pairing token from the QR payload
was never transmitted or validated (trust-on-first-connect), and devices
were stored under the BLE central UUID instead of their own deviceID so
challenge verification and re-identify could never find the key.

- PairRequestMessage now carries deviceID and pairingToken
- Daemon validates the token against the active pairing window and
  stores devices under their own deviceID
- 5 new tests covering token enforcement (valid/wrong/missing/no-window
  plus post-pairing auth)
- DaemonCoordinator shared state now guarded by a lock (was racy
  @unchecked Sendable); pendingAuthentications entries cleaned up after
  each auth resolves; expired challenges/nonces pruned every 60s
- PAM socket timeout now daemon timeout + 2s so an approval arriving at
  the deadline is not lost to the tie
- touchbridge-test pair now renders the pairing payload as a QR code
  PNG and opens it (image deleted once pairing ends)
- iOS app gains a camera QR scanner as the primary pairing path;
  manual JSON entry stays as fallback
- iOS pairing flow now performs the real BLE ceremony: scan for the
  Mac from the payload, send a wire-format pair request with the
  token, and only report paired after the Mac accepts (previously the
  manual-entry path never sent the phone's key to the Mac at all)
- Challenges arriving while the app is backgrounded post a local
  notification and defer Face ID until the app foregrounds — before,
  the approval prompt silently never appeared
- Pairing rejection now surfaces an error with recovery instructions
- App version read from bundle instead of hardcoded 0.1.0-alpha
- Add scripts/patch-pam.sh: standalone PAM activation for Homebrew/pkg
  users — setup.md pointed at a script that did not exist, so cask
  users could not enable sudo auth by following the docs; now shipped
  in the pkg and documented with a curl fallback for older installs
- build-release.sh derives the artifact version from the git tag
  instead of hardcoded 0.1.0 (v1.0.0 release shipped 0.1.0-named
  artifacts); cask notes the legacy filename until the next release
- Sync test count to 127 (111 daemon + 16 protocol, verified) across
  README badge/body and launch copy — README said 91, CHANGELOG 129
- Bump menubar and companion versions to 1.0.0; SECURITY.md supported
  versions updated to 1.0.x
- limitations.md: correct stale single-device claim (daemon side is
  multi-device since PR #18), document SSH/iPad/locked-phone behavior
- setup.md: document QR-scan pairing flow and token expiry
@HMAKT99
HMAKT99 changed the base branch from feature/complete-e2e-tests to main July 4, 2026 04:50
@HMAKT99
HMAKT99 merged commit 25d3718 into main Jul 4, 2026
3 checks passed
@HMAKT99
HMAKT99 deleted the fix/pairing-security-and-adoption branch July 4, 2026 04:55
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.

1 participant