@@ -46,6 +46,11 @@ actor Manager {
46
46
} catch {
47
47
throw . validation( error)
48
48
}
49
+
50
+ // HACK: The downloaded dylib may be quarantined, but we've validated it's signature
51
+ // so it's safe to execute. However, this SE must be sandboxed, so we defer to the app.
52
+ try await removeQuarantine ( dest)
53
+
49
54
do {
50
55
try tunnelHandle = TunnelHandle ( dylibPath: dest)
51
56
} catch {
@@ -85,7 +90,9 @@ actor Manager {
85
90
} catch {
86
91
logger. error ( " tunnel read loop failed: \( error. localizedDescription, privacy: . public) " )
87
92
try await tunnelHandle. close ( )
88
- ptp. cancelTunnelWithError ( error)
93
+ ptp. cancelTunnelWithError (
94
+ makeNSError ( suffix: " Manager " , desc: " Tunnel read loop failed: \( error. localizedDescription) " )
95
+ )
89
96
return
90
97
}
91
98
logger. info ( " tunnel read loop exited " )
@@ -227,6 +234,9 @@ enum ManagerError: Error {
227
234
case serverInfo( String )
228
235
case errorResponse( msg: String )
229
236
case noTunnelFileDescriptor
237
+ case noApp
238
+ case permissionDenied
239
+ case tunnelFail( any Error )
230
240
231
241
var description : String {
232
242
switch self {
@@ -248,6 +258,12 @@ enum ManagerError: Error {
248
258
msg
249
259
case . noTunnelFileDescriptor:
250
260
" Could not find a tunnel file descriptor "
261
+ case . noApp:
262
+ " The VPN must be started with the app open during first-time setup. "
263
+ case . permissionDenied:
264
+ " Permission was not granted to execute the CoderVPN dylib "
265
+ case let . tunnelFail( err) :
266
+ " Failed to communicate with dylib over tunnel: \( err) "
251
267
}
252
268
}
253
269
}
@@ -272,3 +288,23 @@ func writeVpnLog(_ log: Vpn_Log) {
272
288
let fields = log. fields. map { " \( $0. name) : \( $0. value) " } . joined ( separator: " , " )
273
289
logger. log ( level: level, " \( log. message, privacy: . public) : \( fields, privacy: . public) " )
274
290
}
291
+
292
+ private func removeQuarantine( _ dest: URL ) async throws ( ManagerError) {
293
+ var flag : AnyObject ?
294
+ let file = NSURL ( fileURLWithPath: dest. path)
295
+ try ? file. getResourceValue ( & flag, forKey: kCFURLQuarantinePropertiesKey as URLResourceKey )
296
+ if flag != nil {
297
+ guard let conn = globalXPCListenerDelegate. conn else {
298
+ throw . noApp
299
+ }
300
+ // Wait for unsandboxed app to accept our file
301
+ let success = await withCheckedContinuation { [ dest] continuation in
302
+ conn. removeQuarantine ( path: dest. path) { success in
303
+ continuation. resume ( returning: success)
304
+ }
305
+ }
306
+ if !success {
307
+ throw . permissionDenied
308
+ }
309
+ }
310
+ }
0 commit comments