Skip to content

feat: macOS QR code pairing + iOS scanner + TLS certificate pinning#6656

Merged
ashleeradka merged 9 commits into
mainfrom
safe-plan/ios-connection-plan/pr2-qr-pairing
Feb 22, 2026
Merged

feat: macOS QR code pairing + iOS scanner + TLS certificate pinning#6656
ashleeradka merged 9 commits into
mainfrom
safe-plan/ios-connection-plan/pr2-qr-pairing

Conversation

@ashleeradka
Copy link
Copy Markdown
Contributor

@ashleeradka ashleeradka commented Feb 22, 2026

Summary

  • Add one-scan QR code pairing from macOS to iOS: macOS generates a QR code containing IP, port, session token, TLS fingerprint, and host ID; iOS scans it with AVFoundation and auto-configures the connection
  • Add TLS certificate pinning on iOS using sec_protocol_options_set_verify_block with SHA-256 fingerprint comparison against the stored value from QR pairing
  • Add macOS Settings UI for enabling/disabling iOS pairing, showing the QR code, and regenerating session tokens

Part of plan: ios-connection-plan.md (PR 2 of 3)

New Files

  • clients/shared/Utilities/QRCodeGenerator.swift — CoreImage QR code generation (macOS only)
  • clients/shared/Utilities/NetworkInterfaceResolver.swift — Local IPv4 detection via getifaddrs (macOS only, en0 > en1 > fallback)
  • clients/macos/.../Settings/PairingQRCodeSheet.swift — QR display sheet with connection payload and info
  • clients/ios/Views/QRScannerView.swift — AVFoundation QR scanner with haptic feedback and auto-stop
  • clients/ios/Views/Settings/QRPairingSheet.swift — Full pairing flow: scan > confirm > save config > connect

Modified Files

  • clients/macos/.../Settings/SettingsAdvancedTab.swift — Replace token-copy UI with pairing toggle, QR code button, and regenerate action
  • clients/shared/IPC/DaemonConnection.swift — Add iOS TLS certificate pinning via custom NWParameters verify block
  • clients/ios/Views/Settings/ConnectionSettingsSection.swift — Add "Scan QR Code" button, update hostname placeholder
  • clients/ios/Views/OnboardingView.swift — Add QR scanner to DaemonSetupStep, fix Continue button to enable after QR pairing
  • clients/ios/Resources/Info.plist — Add NSCameraUsageDescription for QR scanning

Key Technical Decisions

  • TOFU pinning model: If no fingerprint is stored, the first TLS connection accepts any cert. Once a fingerprint is stored via QR, subsequent connections are pinned.
  • Host ID for IP migration: SHA-256 of IOPlatformUUID + salt detects "same Mac, new IP" and auto-migrates stored credentials.
  • QR payload format: Versioned JSON with type checking and compact keys for small QR codes.
  • ios-pairing-enabled flag: macOS toggle creates/removes ~/.vellum/ios-pairing-enabled file, which controls 0.0.0.0 binding (from PR 1).
Plan: ios-connection-plan.md

$(cat /Users/ashleeradka/Development/vellum-assistant/.private/plans/ios-connection-plan.md)

🤖 Generated with Claude Code


Open with Devin

@ashleeradka
Copy link
Copy Markdown
Contributor Author

👀 Where to focus your review

  • clients/shared/IPC/DaemonConnection.swift (TLS pinning): New sec_protocol_options_set_verify_block implementation extracts leaf cert, computes SHA-256, and compares against stored fingerprint. Security-sensitive — verify the fingerprint comparison logic and TOFU behavior (accepts any cert when no fingerprint stored).
  • clients/ios/Views/Settings/QRPairingSheet.swift (credential storage): Saves session token to Keychain with host-specific key, stores fingerprint/hostId in UserDefaults. Verify the host migration logic that detects "same Mac, new IP" via hostId.
  • clients/macos/.../PairingQRCodeSheet.swift (host ID generation): Uses SHA-256 of IOPlatformUUID + app-specific salt for privacy-safe host identification. Verify the IORegistry access pattern is correct.

Risk level: Medium — introduces TLS certificate pinning and credential management patterns that set precedent for future pairing flows.

devin-ai-integration[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

@ashleeradka ashleeradka force-pushed the safe-plan/ios-connection-plan/pr2-qr-pairing branch 2 times, most recently from b634cee to ebd595c Compare February 22, 2026 20:12
ashleeradka and others added 8 commits February 22, 2026 15:17
Co-Authored-By: Claude <noreply@anthropic.com>
…t, messaging

- Guard null ifa_addr in NetworkInterfaceResolver (crash on awdl0/tunnel interfaces)
- Validate QR port range 1-65535 before UInt16 conversion (prevents crash on malformed QR)
- Inject ClientProvider environmentObject into OnboardingView (crash when opening QR sheet)
- Improve regenerate token messaging to mention daemon restart requirement

Co-Authored-By: Claude <noreply@anthropic.com>
…s control

- Replace deprecated NavigationView with NavigationStack (project convention)
- Check AVCaptureDevice.authorizationStatus before configuring session
- Handle .notDetermined by requesting access first (proper iOS permission flow)
- Make stopScanning() private (not used externally)

Co-Authored-By: Claude <noreply@anthropic.com>
- Make QRCodeGenerator and NetworkInterfaceResolver public (visible across SPM modules)
- Inline fingerprint key construction in DaemonConnection (UserDefaultsKeys is iOS-app-only)
- Regenerate xcodeproj to include new QRScannerView and QRPairingSheet files

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
New IPC type from main that the macOS client intentionally does not decode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…val type errors

- Add guardian_verification (client) and guardian_verification_response
  (server) entries to the IPC snapshot test
- Fix channel-approval-routes and channel-approvals tests to use raw
  bun:sqlite Database client instead of drizzle's run/prepare methods
  which changed their type signatures
- Correct SQL column names (created_at/updated_at, conversation_id) to
  match actual schema

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ashleeradka ashleeradka force-pushed the safe-plan/ios-connection-plan/pr2-qr-pairing branch from d6414a8 to 5f5138d Compare February 22, 2026 20:19
…t-import tests

Prefix unused variables with _ and add eslint-disable comments for
require() imports in test helpers. These are pre-existing issues
exposed by the assistant CI being triggered for the first time in a while.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ashleeradka ashleeradka merged commit 4004d71 into main Feb 22, 2026
3 of 4 checks passed
@ashleeradka ashleeradka deleted the safe-plan/ios-connection-plan/pr2-qr-pairing branch February 22, 2026 20:37
devin-ai-integration Bot added a commit that referenced this pull request Mar 3, 2026
Remove 4 unused files (~297 lines) from clients/shared/:

- NetworkInterfaceResolver.swift (58 lines) — zero references outside file.
  Added in PR #6656 (QR code pairing) but never used.
- HoverEffect.swift (41 lines) — zero references outside file.
  Added in PR #1868 (Extract design system) but never used.
- InlineWidgetCardModifier.swift (114 lines) — zero references outside file.
  Last touched in PR #3473 (platform guard fix).
- VWaveformView.swift (84 lines) — zero references outside file.
  Added in PR #6095 (two-way voice mode) but never used.

Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>
ashleeradka added a commit that referenced this pull request Mar 3, 2026
* chore: remove dead shared library files

Remove 4 unused files (~297 lines) from clients/shared/:

- NetworkInterfaceResolver.swift (58 lines) — zero references outside file.
  Added in PR #6656 (QR code pairing) but never used.
- HoverEffect.swift (41 lines) — zero references outside file.
  Added in PR #1868 (Extract design system) but never used.
- InlineWidgetCardModifier.swift (114 lines) — zero references outside file.
  Last touched in PR #3473 (platform guard fix).
- VWaveformView.swift (84 lines) — zero references outside file.
  Added in PR #6095 (two-way voice mode) but never used.

Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>

* fix: restore InlineWidgetCardModifier and HoverEffect — actively used

InlineWidgetCardModifier is used by ConfirmationSurfaceView.swift:48
(.inlineWidgetCard() call). HoverEffect is used by
ModifiersGallerySection.swift:86 (.vHover() call in DEBUG gallery).

Only NetworkInterfaceResolver and VWaveformView are confirmed dead
in this PR.

Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai>
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