From 606eb3ab4da1c14ddf2d51317ee1acc6e55b5a31 Mon Sep 17 00:00:00 2001 From: ethan Date: Wed, 4 Mar 2026 17:00:24 +1100 Subject: [PATCH 1/4] feat: add DangerousDisableCoderSignatureValidation UserDefault to skip tunnel binary codesign check --- Coder-Desktop/Coder-Desktop/State.swift | 7 +++++++ .../Coder-DesktopHelper/HelperXPCListeners.swift | 4 +++- Coder-Desktop/Coder-DesktopHelper/Manager.swift | 12 +++++++++--- Coder-Desktop/VPN/NEHelperXPCClient.swift | 4 ++-- Coder-Desktop/VPN/PacketTunnelProvider.swift | 4 +++- Coder-Desktop/VPNLib/XPC.swift | 2 +- 6 files changed, 25 insertions(+), 8 deletions(-) diff --git a/Coder-Desktop/Coder-Desktop/State.swift b/Coder-Desktop/Coder-Desktop/State.swift index faf15e05..954dfa43 100644 --- a/Coder-Desktop/Coder-Desktop/State.swift +++ b/Coder-Desktop/Coder-Desktop/State.swift @@ -10,6 +10,8 @@ class AppState: ObservableObject { private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "AppState") let appId = Bundle.main.bundleIdentifier! + let dangerousDisableCoderSignatureValidation: Bool + // Stored in UserDefaults @Published private(set) var hasSession: Bool { didSet { @@ -87,6 +89,7 @@ class AppState: ObservableObject { if useLiteralHeaders, let headers = try? JSONEncoder().encode(literalHeaders) { proto.providerConfiguration?["literalHeaders"] = headers } + proto.providerConfiguration?["dangerousDisableCoderSignatureValidation"] = dangerousDisableCoderSignatureValidation proto.serverAddress = baseAccessURL!.absoluteString return proto } @@ -106,6 +109,9 @@ class AppState: ObservableObject { { self.persistent = persistent self.onChange = onChange + dangerousDisableCoderSignatureValidation = persistent + ? UserDefaults.standard.bool(forKey: Keys.dangerousDisableCoderSignatureValidation) + : false keychain = Keychain(service: Bundle.main.bundleIdentifier!) _hasSession = Published(initialValue: persistent ? UserDefaults.standard.bool(forKey: Keys.hasSession) : false) _baseAccessURL = Published( @@ -219,6 +225,7 @@ class AppState: ObservableObject { static let stopVPNOnQuit = "StopVPNOnQuit" static let startVPNOnLaunch = "StartVPNOnLaunch" + static let dangerousDisableCoderSignatureValidation = "DangerousDisableCoderSignatureValidation" static let skipHiddenIconAlert = "SkipHiddenIconAlert" } } diff --git a/Coder-Desktop/Coder-DesktopHelper/HelperXPCListeners.swift b/Coder-Desktop/Coder-DesktopHelper/HelperXPCListeners.swift index 9b65d8e5..e7cb04b9 100644 --- a/Coder-Desktop/Coder-DesktopHelper/HelperXPCListeners.swift +++ b/Coder-Desktop/Coder-DesktopHelper/HelperXPCListeners.swift @@ -80,6 +80,7 @@ extension HelperNEXPCServer: HelperNEXPCInterface { token: String, tun: FileHandle, headers: Data?, + dangerousDisableSignatureValidation: Bool, reply: @escaping (Error?) -> Void ) { logger.info("startDaemon called") @@ -92,7 +93,8 @@ extension HelperNEXPCServer: HelperNEXPCInterface { apiToken: token, serverUrl: accessURL, tunFd: tun.fileDescriptor, - literalHeaders: headers.flatMap { try? JSONDecoder().decode([HTTPHeader].self, from: $0) } ?? [] + literalHeaders: headers.flatMap { try? JSONDecoder().decode([HTTPHeader].self, from: $0) } ?? [], + dangerousDisableSignatureValidation: dangerousDisableSignatureValidation ) ) try await manager.startVPN() diff --git a/Coder-Desktop/Coder-DesktopHelper/Manager.swift b/Coder-Desktop/Coder-DesktopHelper/Manager.swift index 7ef3d617..cafeef58 100644 --- a/Coder-Desktop/Coder-DesktopHelper/Manager.swift +++ b/Coder-Desktop/Coder-DesktopHelper/Manager.swift @@ -74,12 +74,17 @@ actor Manager { } catch { throw .download(error) } - pushProgress(stage: .validating) + do { - try Validator.validateSignature(binaryPath: dest) + if cfg.dangerousDisableSignatureValidation { + logger.warning("Skipping code signature validation of downloaded binary (disabled by configuration)") + } else { + pushProgress(stage: .validating) + try Validator.validateSignature(binaryPath: dest) + } try await Validator.validateVersion(binaryPath: dest, serverVersion: buildInfo.version) } catch { - // Cleanup unvalid binary + // Cleanup invalid binary try? FileManager.default.removeItem(at: dest) throw .validation(error) } @@ -270,6 +275,7 @@ struct ManagerConfig { let serverUrl: URL let tunFd: Int32 let literalHeaders: [HTTPHeader] + let dangerousDisableSignatureValidation: Bool } enum ManagerError: Error { diff --git a/Coder-Desktop/VPN/NEHelperXPCClient.swift b/Coder-Desktop/VPN/NEHelperXPCClient.swift index 05737c46..a6fac0a3 100644 --- a/Coder-Desktop/VPN/NEHelperXPCClient.swift +++ b/Coder-Desktop/VPN/NEHelperXPCClient.swift @@ -35,7 +35,7 @@ final class HelperXPCClient: @unchecked Sendable { return connection } - func startDaemon(accessURL: URL, token: String, tun: FileHandle, headers: Data?) async throws { + func startDaemon(accessURL: URL, token: String, tun: FileHandle, headers: Data?, dangerousDisableSignatureValidation: Bool) async throws { let conn = connect() return try await withCheckedThrowingContinuation { continuation in guard let proxy = conn.remoteObjectProxyWithErrorHandler({ err in @@ -46,7 +46,7 @@ final class HelperXPCClient: @unchecked Sendable { continuation.resume(throwing: XPCError.wrongProxyType) return } - proxy.startDaemon(accessURL: accessURL, token: token, tun: tun, headers: headers) { err in + proxy.startDaemon(accessURL: accessURL, token: token, tun: tun, headers: headers, dangerousDisableSignatureValidation: dangerousDisableSignatureValidation) { err in if let error = err { self.logger.error("Failed to start daemon: \(error.localizedDescription, privacy: .public)") continuation.resume(throwing: error) diff --git a/Coder-Desktop/VPN/PacketTunnelProvider.swift b/Coder-Desktop/VPN/PacketTunnelProvider.swift index a2d35597..e8304289 100644 --- a/Coder-Desktop/VPN/PacketTunnelProvider.swift +++ b/Coder-Desktop/VPN/PacketTunnelProvider.swift @@ -59,6 +59,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { throw makeNSError(suffix: "PTP", desc: "Missing Token") } let headers = proto.providerConfiguration?["literalHeaders"] as? Data + let dangerousDisableSigValidation = proto.providerConfiguration?["dangerousDisableCoderSignatureValidation"] as? Bool ?? false logger.debug("retrieved token & access URL") guard let tunFd = tunnelFileDescriptor else { logger.error("startTunnel called with nil tunnelFileDescriptor") @@ -68,7 +69,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { accessURL: .init(string: baseAccessURL)!, token: token, tun: FileHandle(fileDescriptor: tunFd), - headers: headers + headers: headers, + dangerousDisableSignatureValidation: dangerousDisableSigValidation ) } diff --git a/Coder-Desktop/VPNLib/XPC.swift b/Coder-Desktop/VPNLib/XPC.swift index daf902f2..ca57f38f 100644 --- a/Coder-Desktop/VPNLib/XPC.swift +++ b/Coder-Desktop/VPNLib/XPC.swift @@ -26,7 +26,7 @@ public let helperNEMachServiceName = "4399GN35BJ.com.coder.Coder-Desktop.HelperN @preconcurrency @objc public protocol HelperNEXPCInterface { // headers is a JSON `[HTTPHeader]` - func startDaemon(accessURL: URL, token: String, tun: FileHandle, headers: Data?, reply: @escaping (Error?) -> Void) + func startDaemon(accessURL: URL, token: String, tun: FileHandle, headers: Data?, dangerousDisableSignatureValidation: Bool, reply: @escaping (Error?) -> Void) func stopDaemon(reply: @escaping (Error?) -> Void) } From 092a53992a4be5e057432ae174f5a8c9d8364e09 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Fri, 6 Mar 2026 15:59:10 +1100 Subject: [PATCH 2/4] fix fmt --- Coder-Desktop/Coder-Desktop/State.swift | 1 + Coder-Desktop/Coder-Desktop/Views/Util.swift | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Coder-Desktop/Coder-Desktop/State.swift b/Coder-Desktop/Coder-Desktop/State.swift index 954dfa43..3807273d 100644 --- a/Coder-Desktop/Coder-Desktop/State.swift +++ b/Coder-Desktop/Coder-Desktop/State.swift @@ -89,6 +89,7 @@ class AppState: ObservableObject { if useLiteralHeaders, let headers = try? JSONEncoder().encode(literalHeaders) { proto.providerConfiguration?["literalHeaders"] = headers } + // swiftlint:disable:next line_length proto.providerConfiguration?["dangerousDisableCoderSignatureValidation"] = dangerousDisableCoderSignatureValidation proto.serverAddress = baseAccessURL!.absoluteString return proto diff --git a/Coder-Desktop/Coder-Desktop/Views/Util.swift b/Coder-Desktop/Coder-Desktop/Views/Util.swift index 10d07479..8987f54c 100644 --- a/Coder-Desktop/Coder-Desktop/Views/Util.swift +++ b/Coder-Desktop/Coder-Desktop/Views/Util.swift @@ -55,7 +55,7 @@ private struct ActivationPolicyModifier: ViewModifier { NSApp.setActivationPolicy(.regular) } .onDisappear { - if NSApp.windows.filter { $0.level != .statusBar && $0.isVisible }.count <= 1 { + if NSApp.windows.filter({ $0.level != .statusBar && $0.isVisible }).count <= 1 { NSApp.setActivationPolicy(.accessory) } } From a4055e02d2bbb4e97cdaa89d87119be9c7617d70 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Fri, 6 Mar 2026 16:17:34 +1100 Subject: [PATCH 3/4] Fix 4 swiftlint lint issues - Shorten local variable name in PacketTunnelProvider.swift to keep line under 120 chars - Add function_parameter_count disable comments for startDaemon in XPC.swift and HelperXPCListeners.swift - Add cyclomatic_complexity to existing disable comment in Manager.swift --- .../Coder-DesktopHelper/HelperXPCListeners.swift | 8 +++++++- Coder-Desktop/Coder-DesktopHelper/Manager.swift | 2 +- Coder-Desktop/VPN/NEHelperXPCClient.swift | 16 ++++++++++++++-- Coder-Desktop/VPN/PacketTunnelProvider.swift | 6 +++--- Coder-Desktop/VPNLib/XPC.swift | 10 +++++++++- 5 files changed, 34 insertions(+), 8 deletions(-) diff --git a/Coder-Desktop/Coder-DesktopHelper/HelperXPCListeners.swift b/Coder-Desktop/Coder-DesktopHelper/HelperXPCListeners.swift index e7cb04b9..02375ef1 100644 --- a/Coder-Desktop/Coder-DesktopHelper/HelperXPCListeners.swift +++ b/Coder-Desktop/Coder-DesktopHelper/HelperXPCListeners.swift @@ -75,6 +75,7 @@ class HelperNEXPCServer: NSObject, NSXPCListenerDelegate, @unchecked Sendable { } extension HelperNEXPCServer: HelperNEXPCInterface { + // swiftlint:disable:next function_parameter_count func startDaemon( accessURL: URL, token: String, @@ -93,7 +94,12 @@ extension HelperNEXPCServer: HelperNEXPCInterface { apiToken: token, serverUrl: accessURL, tunFd: tun.fileDescriptor, - literalHeaders: headers.flatMap { try? JSONDecoder().decode([HTTPHeader].self, from: $0) } ?? [], + literalHeaders: headers.flatMap { + try? JSONDecoder().decode( + [HTTPHeader].self, + from: $0 + ) + } ?? [], dangerousDisableSignatureValidation: dangerousDisableSignatureValidation ) ) diff --git a/Coder-Desktop/Coder-DesktopHelper/Manager.swift b/Coder-Desktop/Coder-DesktopHelper/Manager.swift index cafeef58..778088d6 100644 --- a/Coder-Desktop/Coder-DesktopHelper/Manager.swift +++ b/Coder-Desktop/Coder-DesktopHelper/Manager.swift @@ -25,7 +25,7 @@ actor Manager { private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "manager") - // swiftlint:disable:next function_body_length + // swiftlint:disable:next function_body_length cyclomatic_complexity init(cfg: ManagerConfig) async throws(ManagerError) { self.cfg = cfg telemetryEnricher = TelemetryEnricher() diff --git a/Coder-Desktop/VPN/NEHelperXPCClient.swift b/Coder-Desktop/VPN/NEHelperXPCClient.swift index a6fac0a3..2d1d77a6 100644 --- a/Coder-Desktop/VPN/NEHelperXPCClient.swift +++ b/Coder-Desktop/VPN/NEHelperXPCClient.swift @@ -35,7 +35,13 @@ final class HelperXPCClient: @unchecked Sendable { return connection } - func startDaemon(accessURL: URL, token: String, tun: FileHandle, headers: Data?, dangerousDisableSignatureValidation: Bool) async throws { + func startDaemon( + accessURL: URL, + token: String, + tun: FileHandle, + headers: Data?, + dangerousDisableSignatureValidation: Bool + ) async throws { let conn = connect() return try await withCheckedThrowingContinuation { continuation in guard let proxy = conn.remoteObjectProxyWithErrorHandler({ err in @@ -46,7 +52,13 @@ final class HelperXPCClient: @unchecked Sendable { continuation.resume(throwing: XPCError.wrongProxyType) return } - proxy.startDaemon(accessURL: accessURL, token: token, tun: tun, headers: headers, dangerousDisableSignatureValidation: dangerousDisableSignatureValidation) { err in + proxy.startDaemon( + accessURL: accessURL, + token: token, + tun: tun, + headers: headers, + dangerousDisableSignatureValidation: dangerousDisableSignatureValidation + ) { err in if let error = err { self.logger.error("Failed to start daemon: \(error.localizedDescription, privacy: .public)") continuation.resume(throwing: error) diff --git a/Coder-Desktop/VPN/PacketTunnelProvider.swift b/Coder-Desktop/VPN/PacketTunnelProvider.swift index e8304289..7bd492eb 100644 --- a/Coder-Desktop/VPN/PacketTunnelProvider.swift +++ b/Coder-Desktop/VPN/PacketTunnelProvider.swift @@ -43,7 +43,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { return nil } - override func startTunnel( + nonisolated(nonsending) override func startTunnel( options _: [String: NSObject]? ) async throws { globalHelperXPCClient.ptp = self @@ -59,7 +59,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { throw makeNSError(suffix: "PTP", desc: "Missing Token") } let headers = proto.providerConfiguration?["literalHeaders"] as? Data - let dangerousDisableSigValidation = proto.providerConfiguration?["dangerousDisableCoderSignatureValidation"] as? Bool ?? false + let disableSigVal = proto.providerConfiguration?["dangerousDisableCoderSignatureValidation"] as? Bool ?? false logger.debug("retrieved token & access URL") guard let tunFd = tunnelFileDescriptor else { logger.error("startTunnel called with nil tunnelFileDescriptor") @@ -70,7 +70,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { token: token, tun: FileHandle(fileDescriptor: tunFd), headers: headers, - dangerousDisableSignatureValidation: dangerousDisableSigValidation + dangerousDisableSignatureValidation: disableSigVal ) } diff --git a/Coder-Desktop/VPNLib/XPC.swift b/Coder-Desktop/VPNLib/XPC.swift index ca57f38f..75d3cd2f 100644 --- a/Coder-Desktop/VPNLib/XPC.swift +++ b/Coder-Desktop/VPNLib/XPC.swift @@ -26,7 +26,15 @@ public let helperNEMachServiceName = "4399GN35BJ.com.coder.Coder-Desktop.HelperN @preconcurrency @objc public protocol HelperNEXPCInterface { // headers is a JSON `[HTTPHeader]` - func startDaemon(accessURL: URL, token: String, tun: FileHandle, headers: Data?, dangerousDisableSignatureValidation: Bool, reply: @escaping (Error?) -> Void) + // swiftlint:disable:next function_parameter_count + func startDaemon( + accessURL: URL, + token: String, + tun: FileHandle, + headers: Data?, + dangerousDisableSignatureValidation: Bool, + reply: @escaping (Error?) -> Void + ) func stopDaemon(reply: @escaping (Error?) -> Void) } From 8e18bbf863e18dcb63ce1179c15660df92080266 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Fri, 6 Mar 2026 17:34:25 +1100 Subject: [PATCH 4/4] Remove nonisolated(nonsending) for CI Xcode compat --- Coder-Desktop/VPN/PacketTunnelProvider.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Coder-Desktop/VPN/PacketTunnelProvider.swift b/Coder-Desktop/VPN/PacketTunnelProvider.swift index 7bd492eb..70b50e64 100644 --- a/Coder-Desktop/VPN/PacketTunnelProvider.swift +++ b/Coder-Desktop/VPN/PacketTunnelProvider.swift @@ -43,7 +43,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { return nil } - nonisolated(nonsending) override func startTunnel( + override func startTunnel( options _: [String: NSObject]? ) async throws { globalHelperXPCClient.ptp = self