Skip to content

Commit f4251ab

Browse files
feat: add DangerousDisableCoderSignatureValidation UserDefault to skip tunnel binary codesign check (#241)
Adds a `DangerousDisableCoderSignatureValidation` UserDefaults key (MDM-settable) that disables `SecStaticCode` signature validation of the downloaded tunnel binary, mirroring the Windows app's `Manager:TunnelBinarySignatureSigner` registry key. The setting flows through the existing pipeline: ``` Main App (UserDefaults) → providerConfiguration → Network Extension → XPC → Helper → Manager ``` When enabled, signature validation is skipped with a warning log. Version validation is always performed. Closes #240, where a user would like to user Coder Desktop alongside a Coder deployment built from source. ### Usage ```bash defaults write com.coder.Coder-Desktop DangerousDisableCoderSignatureValidation -bool true ``` or, like all UserDefaults, can be configured via your MDM platform to apply this rule to an entire org. Then restart the app, and log out and back in. ### Files changed - `State.swift` — new UserDefaults key + providerConfiguration plumbing - `PacketTunnelProvider.swift` — reads flag, forwards to XPC - `VPNLib/XPC.swift` — updated `HelperNEXPCInterface.startDaemon` signature - `NEHelperXPCClient.swift` — updated XPC client wrapper - `HelperXPCListeners.swift` — updated XPC server implementation - `Manager.swift` — new `ManagerConfig` field + conditional skip in `Manager.init`
1 parent 81d03d7 commit f4251ab

7 files changed

Lines changed: 54 additions & 10 deletions

File tree

Coder-Desktop/Coder-Desktop/State.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ class AppState: ObservableObject {
1010
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "AppState")
1111
let appId = Bundle.main.bundleIdentifier!
1212

13+
let dangerousDisableCoderSignatureValidation: Bool
14+
1315
// Stored in UserDefaults
1416
@Published private(set) var hasSession: Bool {
1517
didSet {
@@ -87,6 +89,8 @@ class AppState: ObservableObject {
8789
if useLiteralHeaders, let headers = try? JSONEncoder().encode(literalHeaders) {
8890
proto.providerConfiguration?["literalHeaders"] = headers
8991
}
92+
// swiftlint:disable:next line_length
93+
proto.providerConfiguration?["dangerousDisableCoderSignatureValidation"] = dangerousDisableCoderSignatureValidation
9094
proto.serverAddress = baseAccessURL!.absoluteString
9195
return proto
9296
}
@@ -106,6 +110,9 @@ class AppState: ObservableObject {
106110
{
107111
self.persistent = persistent
108112
self.onChange = onChange
113+
dangerousDisableCoderSignatureValidation = persistent
114+
? UserDefaults.standard.bool(forKey: Keys.dangerousDisableCoderSignatureValidation)
115+
: false
109116
keychain = Keychain(service: Bundle.main.bundleIdentifier!)
110117
_hasSession = Published(initialValue: persistent ? UserDefaults.standard.bool(forKey: Keys.hasSession) : false)
111118
_baseAccessURL = Published(
@@ -219,6 +226,7 @@ class AppState: ObservableObject {
219226
static let stopVPNOnQuit = "StopVPNOnQuit"
220227
static let startVPNOnLaunch = "StartVPNOnLaunch"
221228

229+
static let dangerousDisableCoderSignatureValidation = "DangerousDisableCoderSignatureValidation"
222230
static let skipHiddenIconAlert = "SkipHiddenIconAlert"
223231
}
224232
}

Coder-Desktop/Coder-Desktop/Views/Util.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ private struct ActivationPolicyModifier: ViewModifier {
5555
NSApp.setActivationPolicy(.regular)
5656
}
5757
.onDisappear {
58-
if NSApp.windows.filter { $0.level != .statusBar && $0.isVisible }.count <= 1 {
58+
if NSApp.windows.filter({ $0.level != .statusBar && $0.isVisible }).count <= 1 {
5959
NSApp.setActivationPolicy(.accessory)
6060
}
6161
}

Coder-Desktop/Coder-DesktopHelper/HelperXPCListeners.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,13 @@ class HelperNEXPCServer: NSObject, NSXPCListenerDelegate, @unchecked Sendable {
7575
}
7676

7777
extension HelperNEXPCServer: HelperNEXPCInterface {
78+
// swiftlint:disable:next function_parameter_count
7879
func startDaemon(
7980
accessURL: URL,
8081
token: String,
8182
tun: FileHandle,
8283
headers: Data?,
84+
dangerousDisableSignatureValidation: Bool,
8385
reply: @escaping (Error?) -> Void
8486
) {
8587
logger.info("startDaemon called")
@@ -92,7 +94,13 @@ extension HelperNEXPCServer: HelperNEXPCInterface {
9294
apiToken: token,
9395
serverUrl: accessURL,
9496
tunFd: tun.fileDescriptor,
95-
literalHeaders: headers.flatMap { try? JSONDecoder().decode([HTTPHeader].self, from: $0) } ?? []
97+
literalHeaders: headers.flatMap {
98+
try? JSONDecoder().decode(
99+
[HTTPHeader].self,
100+
from: $0
101+
)
102+
} ?? [],
103+
dangerousDisableSignatureValidation: dangerousDisableSignatureValidation
96104
)
97105
)
98106
try await manager.startVPN()

Coder-Desktop/Coder-DesktopHelper/Manager.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ actor Manager {
2525

2626
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "manager")
2727

28-
// swiftlint:disable:next function_body_length
28+
// swiftlint:disable:next function_body_length cyclomatic_complexity
2929
init(cfg: ManagerConfig) async throws(ManagerError) {
3030
self.cfg = cfg
3131
telemetryEnricher = TelemetryEnricher()
@@ -74,12 +74,17 @@ actor Manager {
7474
} catch {
7575
throw .download(error)
7676
}
77-
pushProgress(stage: .validating)
77+
7878
do {
79-
try Validator.validateSignature(binaryPath: dest)
79+
if cfg.dangerousDisableSignatureValidation {
80+
logger.warning("Skipping code signature validation of downloaded binary (disabled by configuration)")
81+
} else {
82+
pushProgress(stage: .validating)
83+
try Validator.validateSignature(binaryPath: dest)
84+
}
8085
try await Validator.validateVersion(binaryPath: dest, serverVersion: buildInfo.version)
8186
} catch {
82-
// Cleanup unvalid binary
87+
// Cleanup invalid binary
8388
try? FileManager.default.removeItem(at: dest)
8489
throw .validation(error)
8590
}
@@ -270,6 +275,7 @@ struct ManagerConfig {
270275
let serverUrl: URL
271276
let tunFd: Int32
272277
let literalHeaders: [HTTPHeader]
278+
let dangerousDisableSignatureValidation: Bool
273279
}
274280

275281
enum ManagerError: Error {

Coder-Desktop/VPN/NEHelperXPCClient.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,13 @@ final class HelperXPCClient: @unchecked Sendable {
3535
return connection
3636
}
3737

38-
func startDaemon(accessURL: URL, token: String, tun: FileHandle, headers: Data?) async throws {
38+
func startDaemon(
39+
accessURL: URL,
40+
token: String,
41+
tun: FileHandle,
42+
headers: Data?,
43+
dangerousDisableSignatureValidation: Bool
44+
) async throws {
3945
let conn = connect()
4046
return try await withCheckedThrowingContinuation { continuation in
4147
guard let proxy = conn.remoteObjectProxyWithErrorHandler({ err in
@@ -46,7 +52,13 @@ final class HelperXPCClient: @unchecked Sendable {
4652
continuation.resume(throwing: XPCError.wrongProxyType)
4753
return
4854
}
49-
proxy.startDaemon(accessURL: accessURL, token: token, tun: tun, headers: headers) { err in
55+
proxy.startDaemon(
56+
accessURL: accessURL,
57+
token: token,
58+
tun: tun,
59+
headers: headers,
60+
dangerousDisableSignatureValidation: dangerousDisableSignatureValidation
61+
) { err in
5062
if let error = err {
5163
self.logger.error("Failed to start daemon: \(error.localizedDescription, privacy: .public)")
5264
continuation.resume(throwing: error)

Coder-Desktop/VPN/PacketTunnelProvider.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
5959
throw makeNSError(suffix: "PTP", desc: "Missing Token")
6060
}
6161
let headers = proto.providerConfiguration?["literalHeaders"] as? Data
62+
let disableSigVal = proto.providerConfiguration?["dangerousDisableCoderSignatureValidation"] as? Bool ?? false
6263
logger.debug("retrieved token & access URL")
6364
guard let tunFd = tunnelFileDescriptor else {
6465
logger.error("startTunnel called with nil tunnelFileDescriptor")
@@ -68,7 +69,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
6869
accessURL: .init(string: baseAccessURL)!,
6970
token: token,
7071
tun: FileHandle(fileDescriptor: tunFd),
71-
headers: headers
72+
headers: headers,
73+
dangerousDisableSignatureValidation: disableSigVal
7274
)
7375
}
7476

Coder-Desktop/VPNLib/XPC.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,15 @@ public let helperNEMachServiceName = "4399GN35BJ.com.coder.Coder-Desktop.HelperN
2626
@preconcurrency
2727
@objc public protocol HelperNEXPCInterface {
2828
// headers is a JSON `[HTTPHeader]`
29-
func startDaemon(accessURL: URL, token: String, tun: FileHandle, headers: Data?, reply: @escaping (Error?) -> Void)
29+
// swiftlint:disable:next function_parameter_count
30+
func startDaemon(
31+
accessURL: URL,
32+
token: String,
33+
tun: FileHandle,
34+
headers: Data?,
35+
dangerousDisableSignatureValidation: Bool,
36+
reply: @escaping (Error?) -> Void
37+
)
3038
func stopDaemon(reply: @escaping (Error?) -> Void)
3139
}
3240

0 commit comments

Comments
 (0)