Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -366,18 +366,21 @@ jobs:
exit 1
fi
ENTITLEMENTS="cmux.entitlements"
EMBEDDED_ENTITLEMENTS="cmux.embedded.entitlements"
for APP_PATH in \
"build-universal/Build/Products/Release/cmux NIGHTLY.app"
do
CLI_PATH="$APP_PATH/Contents/Resources/bin/cmux"
HELPER_PATH="$APP_PATH/Contents/Resources/bin/ghostty"
# Sign app bundle first (--deep signs all nested content with full entitlements),
# then re-sign embedded binaries with narrower entitlements.
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" --deep "$APP_PATH"
if [ -f "$CLI_PATH" ]; then
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" "$CLI_PATH"
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$EMBEDDED_ENTITLEMENTS" "$CLI_PATH"
fi
if [ -f "$HELPER_PATH" ]; then
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" "$HELPER_PATH"
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$EMBEDDED_ENTITLEMENTS" "$HELPER_PATH"
fi
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" --deep "$APP_PATH"
/usr/bin/codesign --verify --deep --strict --verbose=2 "$APP_PATH"
done

Expand Down
9 changes: 6 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -235,15 +235,18 @@ jobs:
fi
APP_PATH="build/Build/Products/Release/cmux.app"
ENTITLEMENTS="cmux.entitlements"
EMBEDDED_ENTITLEMENTS="cmux.embedded.entitlements"
CLI_PATH="$APP_PATH/Contents/Resources/bin/cmux"
HELPER_PATH="$APP_PATH/Contents/Resources/bin/ghostty"
# Sign app bundle first (--deep signs all nested content with full entitlements),
# then re-sign embedded binaries with narrower entitlements.
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" --deep "$APP_PATH"
if [ -f "$CLI_PATH" ]; then
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" "$CLI_PATH"
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$EMBEDDED_ENTITLEMENTS" "$CLI_PATH"
fi
if [ -f "$HELPER_PATH" ]; then
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" "$HELPER_PATH"
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$EMBEDDED_ENTITLEMENTS" "$HELPER_PATH"
fi
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" --deep "$APP_PATH"
/usr/bin/codesign --verify --deep --strict --verbose=2 "$APP_PATH"

- name: Notarize app
Expand Down
8 changes: 8 additions & 0 deletions GhosttyTabs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
A5001403 /* TerminalPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001413 /* TerminalPanelView.swift */; };
A5001404 /* BrowserPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001414 /* BrowserPanelView.swift */; };
A5007420 /* BrowserPopupWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5007421 /* BrowserPopupWindowController.swift */; };
A5009900 /* WebAuthnBridgeJavaScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5009901 /* WebAuthnBridgeJavaScript.swift */; };
A5009902 /* WebAuthnCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5009903 /* WebAuthnCoordinator.swift */; };
A5001420 /* MarkdownPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001418 /* MarkdownPanel.swift */; };
A5001421 /* MarkdownPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001419 /* MarkdownPanelView.swift */; };
A5001290 /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = A5001291 /* MarkdownUI */; };
Expand Down Expand Up @@ -201,6 +203,8 @@
A5001413 /* TerminalPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/TerminalPanelView.swift; sourceTree = "<group>"; };
A5001414 /* BrowserPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/BrowserPanelView.swift; sourceTree = "<group>"; };
A5007421 /* BrowserPopupWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/BrowserPopupWindowController.swift; sourceTree = "<group>"; };
A5009901 /* WebAuthnBridgeJavaScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/WebAuthnBridgeJavaScript.swift; sourceTree = "<group>"; };
A5009903 /* WebAuthnCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/WebAuthnCoordinator.swift; sourceTree = "<group>"; };
A5001415 /* PanelContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/PanelContentView.swift; sourceTree = "<group>"; };
A5001418 /* MarkdownPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/MarkdownPanel.swift; sourceTree = "<group>"; };
A5001419 /* MarkdownPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/MarkdownPanelView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -431,6 +435,8 @@
A5001418 /* MarkdownPanel.swift */,
A5001419 /* MarkdownPanelView.swift */,
A5001510 /* CmuxWebView.swift */,
A5009901 /* WebAuthnBridgeJavaScript.swift */,
A5009903 /* WebAuthnCoordinator.swift */,
A5001415 /* PanelContentView.swift */,
A5001211 /* UpdateController.swift */,
A5001212 /* UpdateDelegate.swift */,
Expand Down Expand Up @@ -723,6 +729,8 @@
A5001420 /* MarkdownPanel.swift in Sources */,
A5001421 /* MarkdownPanelView.swift in Sources */,
A5001500 /* CmuxWebView.swift in Sources */,
A5009900 /* WebAuthnBridgeJavaScript.swift in Sources */,
A5009902 /* WebAuthnCoordinator.swift in Sources */,
A5001405 /* PanelContentView.swift in Sources */,
A5001201 /* UpdateController.swift in Sources */,
A5001202 /* UpdateDelegate.swift in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions Resources/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
<string>A program running within cmux would like to use your microphone.</string>
<key>NSCameraUsageDescription</key>
<string>A program running within cmux would like to use your camera.</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>cmux uses Bluetooth to communicate with nearby security keys for passkey authentication.</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
Expand Down
17 changes: 17 additions & 0 deletions Resources/InfoPlist.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@
"sourceLanguage": "en",
"version": "1.0",
"strings": {
"NSBluetoothAlwaysUsageDescription": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "cmux uses Bluetooth to communicate with nearby security keys for passkey authentication."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "cmux はパスキー認証のために近くのセキュリティキーと Bluetooth で通信します。"
}
}
}
},
"NSCameraUsageDescription": {
"extractionState": "manual",
"localizations": {
Expand Down
10 changes: 10 additions & 0 deletions Sources/Panels/BrowserPanel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1869,6 +1869,9 @@ final class BrowserPanel: Panel, ObservableObject {
private(set) var webView: WKWebView
private var websiteDataStore: WKWebsiteDataStore

/// WebAuthn/FIDO2 coordinator for hardware security key and passkey support.
private var webAuthnCoordinator: WebAuthnCoordinator?

/// Monotonic identity for the current WKWebView instance.
/// Incremented whenever we replace the underlying WKWebView after a process crash.
@Published private(set) var webViewInstanceID: UUID = UUID()
Expand Down Expand Up @@ -2504,6 +2507,13 @@ final class BrowserPanel: Panel, ObservableObject {
webView.navigationDelegate = navigationDelegate
webView.uiDelegate = uiDelegate
setupObservers(for: webView)

// Cancel any previous WebAuthn ceremony and install a fresh coordinator
// on the new web view's content controller.
webAuthnCoordinator?.cancelPendingCeremony()
let coordinator = WebAuthnCoordinator(webView: webView)
coordinator.install(on: webView.configuration.userContentController)
webAuthnCoordinator = coordinator
}

private func configureNavigationDelegateCallbacks() {
Expand Down
11 changes: 11 additions & 0 deletions Sources/Panels/BrowserPopupWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ final class BrowserPopupWindowController: NSObject, NSWindowDelegate {
private var urlObservation: NSKeyValueObservation?
private var childPopups: [BrowserPopupWindowController] = []
private let popupUIDelegate: PopupUIDelegate
private var webAuthnCoordinator: WebAuthnCoordinator?
private let popupNavigationDelegate: PopupNavigationDelegate
private let downloadDelegate: BrowserDownloadDelegate

Expand All @@ -102,6 +103,12 @@ final class BrowserPopupWindowController: NSObject, NSWindowDelegate {
webView.customUserAgent = BrowserUserAgentSettings.safariUserAgent
self.webView = webView

// Install WebAuthn/FIDO2 bridge on the popup's content controller so
// security key authentication works in popup-based OAuth flows.
let coordinator = WebAuthnCoordinator(webView: webView)
coordinator.install(on: configuration.userContentController)
self.webAuthnCoordinator = coordinator

// --- Window sizing from WKWindowFeatures ---
let defaultWidth: CGFloat = 800
let defaultHeight: CGFloat = 600
Expand Down Expand Up @@ -270,6 +277,10 @@ final class BrowserPopupWindowController: NSObject, NSWindowDelegate {
urlObservation?.invalidate()
urlObservation = nil

// Cancel any pending WebAuthn ceremony
webAuthnCoordinator?.cancelPendingCeremony()
webAuthnCoordinator = nil

// Tear down web view
webView.stopLoading()
webView.navigationDelegate = nil
Expand Down
Loading
Loading