@@ -46,6 +46,11 @@ actor Manager {
4646 } catch {
4747 throw . validation( error)
4848 }
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+
4954 do {
5055 try tunnelHandle = TunnelHandle ( dylibPath: dest)
5156 } catch {
@@ -85,7 +90,9 @@ actor Manager {
8590 } catch {
8691 logger. error ( " tunnel read loop failed: \( error. localizedDescription, privacy: . public) " )
8792 try await tunnelHandle. close ( )
88- ptp. cancelTunnelWithError ( error)
93+ ptp. cancelTunnelWithError (
94+ makeNSError ( suffix: " Manager " , desc: " Tunnel read loop failed: \( error. localizedDescription) " )
95+ )
8996 return
9097 }
9198 logger. info ( " tunnel read loop exited " )
@@ -227,6 +234,9 @@ enum ManagerError: Error {
227234 case serverInfo( String )
228235 case errorResponse( msg: String )
229236 case noTunnelFileDescriptor
237+ case noApp
238+ case permissionDenied
239+ case tunnelFail( any Error )
230240
231241 var description : String {
232242 switch self {
@@ -248,6 +258,12 @@ enum ManagerError: Error {
248258 msg
249259 case . noTunnelFileDescriptor:
250260 " 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) "
251267 }
252268 }
253269}
@@ -272,3 +288,23 @@ func writeVpnLog(_ log: Vpn_Log) {
272288 let fields = log. fields. map { " \( $0. name) : \( $0. value) " } . joined ( separator: " , " )
273289 logger. log ( level: level, " \( log. message, privacy: . public) : \( fields, privacy: . public) " )
274290}
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