@@ -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, the 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 {
@@ -228,6 +233,8 @@ enum ManagerError: Error {
228233 case serverInfo( String )
229234 case errorResponse( msg: String )
230235 case noTunnelFileDescriptor
236+ case noApp
237+ case permissionDenied
231238
232239 var description : String {
233240 switch self {
@@ -249,6 +256,10 @@ enum ManagerError: Error {
249256 msg
250257 case . noTunnelFileDescriptor:
251258 " Could not find a tunnel file descriptor "
259+ case . noApp:
260+ " The VPN must be started with the app open to perform first-time setup "
261+ case . permissionDenied:
262+ " Permission was not granted to run the CoderVPN dylib "
252263 }
253264 }
254265}
@@ -273,3 +284,24 @@ func writeVpnLog(_ log: Vpn_Log) {
273284 let fields = log. fields. map { " \( $0. name) : \( $0. value) " } . joined ( separator: " , " )
274285 logger. log ( level: level, " \( log. message, privacy: . public) : \( fields, privacy: . public) " )
275286}
287+
288+ private func removeQuarantine( _ dest: URL ) async throws ( ManagerError) {
289+ var flag : AnyObject ?
290+ let file = NSURL ( fileURLWithPath: dest. path)
291+ try ? file. getResourceValue ( & flag, forKey: kCFURLQuarantinePropertiesKey as URLResourceKey )
292+ if flag != nil {
293+ guard let conn = globalXPCListenerDelegate. conn else {
294+ throw . noApp
295+ }
296+ // Wait for unsandboxed app to accept our file
297+ do {
298+ try await withCheckedThrowingContinuation { [ dest] continuation in
299+ conn. removeQuarantine ( path: dest. path) { _ in
300+ continuation. resume ( )
301+ }
302+ }
303+ } catch {
304+ throw . permissionDenied
305+ }
306+ }
307+ }
0 commit comments