diff --git a/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj b/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj
index e68a8c44..a7b35920 100644
--- a/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj	
+++ b/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj	
@@ -17,7 +17,6 @@
 		AA3B3DCE2D2D249F0099996A /* VPNLib.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = AA3B3DA12D2D23860099996A /* VPNLib.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		AA3B3E8E2D2E0FF40099996A /* Mocker in Frameworks */ = {isa = PBXBuildFile; productRef = AA3B3E8D2D2E0FF40099996A /* Mocker */; };
 		AA3B40992D2FC8560099996A /* CoderSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA3B40912D2FC8560099996A /* CoderSDK.framework */; };
-		AA3B40A42D2FC8560099996A /* CoderSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA3B40912D2FC8560099996A /* CoderSDK.framework */; };
 		AA3B40B62D2FD9DD0099996A /* Mocker in Frameworks */ = {isa = PBXBuildFile; productRef = AA3B40B52D2FD9DD0099996A /* Mocker */; };
 		AA3B40B72D2FDA5C0099996A /* CoderSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA3B40912D2FC8560099996A /* CoderSDK.framework */; };
 		AA3B40BD2D2FDFBA0099996A /* Mocker in Frameworks */ = {isa = PBXBuildFile; productRef = AA3B40BC2D2FDFBA0099996A /* Mocker */; };
@@ -25,6 +24,10 @@
 		AA8BC3392D0060A900E1ABAA /* ViewInspector in Frameworks */ = {isa = PBXBuildFile; productRef = AA8BC3382D0060A900E1ABAA /* ViewInspector */; };
 		AA8BC33F2D0061F200E1ABAA /* FluidMenuBarExtra in Frameworks */ = {isa = PBXBuildFile; productRef = AA8BC33E2D0061F200E1ABAA /* FluidMenuBarExtra */; };
 		AA8BC4CF2D00A4B700E1ABAA /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = AA8BC4CE2D00A4B700E1ABAA /* KeychainAccess */; };
+		AAC382352D427B7600F6DFB4 /* CoderSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA3B40912D2FC8560099996A /* CoderSDK.framework */; };
+		AAC382362D427B7600F6DFB4 /* CoderSDK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = AA3B40912D2FC8560099996A /* CoderSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+		AAC382392D427B8300F6DFB4 /* CoderSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA3B40912D2FC8560099996A /* CoderSDK.framework */; };
+		AAC3823A2D427B8300F6DFB4 /* CoderSDK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = AA3B40912D2FC8560099996A /* CoderSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -105,6 +108,13 @@
 			remoteGlobalIDString = AA3B40902D2FC8560099996A;
 			remoteInfo = CoderSDK;
 		};
+		AAC382372D427B7600F6DFB4 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 961678F42CFF100D00B2B6DF /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = AA3B40902D2FC8560099996A;
+			remoteInfo = CoderSDK;
+		};
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXCopyFilesBuildPhase section */
@@ -126,6 +136,18 @@
 			dstSubfolderSpec = 10;
 			files = (
 				AA3B3DCE2D2D249F0099996A /* VPNLib.framework in Embed Frameworks */,
+				AAC382362D427B7600F6DFB4 /* CoderSDK.framework in Embed Frameworks */,
+			);
+			name = "Embed Frameworks";
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		AAC3823B2D427B8300F6DFB4 /* Embed Frameworks */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 10;
+			files = (
+				AAC3823A2D427B8300F6DFB4 /* CoderSDK.framework in Embed Frameworks */,
 			);
 			name = "Embed Frameworks";
 			runOnlyForDeploymentPostprocessing = 0;
@@ -228,8 +250,8 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				AA3B40A42D2FC8560099996A /* CoderSDK.framework in Frameworks */,
 				AA8BC4CF2D00A4B700E1ABAA /* KeychainAccess in Frameworks */,
+				AAC382392D427B8300F6DFB4 /* CoderSDK.framework in Frameworks */,
 				AA2C690F2D34F6920059AFAF /* LaunchAtLogin in Frameworks */,
 				AA8BC33F2D0061F200E1ABAA /* FluidMenuBarExtra in Frameworks */,
 			);
@@ -257,6 +279,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				961679332CFF117300B2B6DF /* NetworkExtension.framework in Frameworks */,
+				AAC382352D427B7600F6DFB4 /* CoderSDK.framework in Frameworks */,
 				AA3B3DCD2D2D249F0099996A /* VPNLib.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -366,6 +389,7 @@
 				961678F92CFF100D00B2B6DF /* Frameworks */,
 				961678FA2CFF100D00B2B6DF /* Resources */,
 				961679422CFF117300B2B6DF /* Embed System Extensions */,
+				AAC3823B2D427B8300F6DFB4 /* Embed Frameworks */,
 			);
 			buildRules = (
 			);
@@ -452,6 +476,7 @@
 			dependencies = (
 				AA2C69922D354A8B0059AFAF /* PBXTargetDependency */,
 				AA3B3DD02D2D249F0099996A /* PBXTargetDependency */,
+				AAC382382D427B7600F6DFB4 /* PBXTargetDependency */,
 			);
 			fileSystemSynchronizedGroups = (
 				AA3C69AD2D2D143400A45481 /* VPN */,
@@ -847,6 +872,11 @@
 			target = AA3B40902D2FC8560099996A /* CoderSDK */;
 			targetProxy = AA3B40C22D2FE7760099996A /* PBXContainerItemProxy */;
 		};
+		AAC382382D427B7600F6DFB4 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = AA3B40902D2FC8560099996A /* CoderSDK */;
+			targetProxy = AAC382372D427B7600F6DFB4 /* PBXContainerItemProxy */;
+		};
 /* End PBXTargetDependency section */
 
 /* Begin XCBuildConfiguration section */
@@ -1216,6 +1246,7 @@
 			buildSettings = {
 				BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
 				CODE_SIGN_IDENTITY = "";
+				"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
 				CURRENT_PROJECT_VERSION = 1;
@@ -1324,6 +1355,7 @@
 			buildSettings = {
 				BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
 				CODE_SIGN_IDENTITY = "";
+				"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
 				CURRENT_PROJECT_VERSION = 1;
diff --git a/Coder Desktop/Coder Desktop/Coder_DesktopApp.swift b/Coder Desktop/Coder Desktop/Coder_DesktopApp.swift
index c45e632a..bfb01ced 100644
--- a/Coder Desktop/Coder Desktop/Coder_DesktopApp.swift	
+++ b/Coder Desktop/Coder Desktop/Coder_DesktopApp.swift	
@@ -11,10 +11,12 @@ struct DesktopApp: App {
             EmptyView()
         }
         Window("Sign In", id: Windows.login.rawValue) {
-            LoginForm<PreviewSession>().environmentObject(appDelegate.session)
+            LoginForm<SecureSession>()
+                .environmentObject(appDelegate.session)
+                .environmentObject(appDelegate.settings)
         }
         .windowResizability(.contentSize)
-        SwiftUI.Settings { SettingsView<PreviewVPN>()
+        SwiftUI.Settings { SettingsView<CoderVPNService>()
             .environmentObject(appDelegate.vpn)
             .environmentObject(appDelegate.settings)
         }
@@ -25,20 +27,20 @@ struct DesktopApp: App {
 @MainActor
 class AppDelegate: NSObject, NSApplicationDelegate {
     private var menuBarExtra: FluidMenuBarExtra?
-    let vpn: PreviewVPN
-    let session: PreviewSession
+    let vpn: CoderVPNService
+    let session: SecureSession
     let settings: Settings
 
     override init() {
         // TODO: Replace with real implementation
-        vpn = PreviewVPN()
+        vpn = CoderVPNService()
         settings = Settings()
-        session = PreviewSession()
+        session = SecureSession(onChange: vpn.configureTunnelProviderProtocol)
     }
 
     func applicationDidFinishLaunching(_: Notification) {
         menuBarExtra = FluidMenuBarExtra(title: "Coder Desktop", image: "MenuBarIcon") {
-            VPNMenu<PreviewVPN, PreviewSession>().frame(width: 256)
+            VPNMenu<CoderVPNService, SecureSession>().frame(width: 256)
                 .environmentObject(self.vpn)
                 .environmentObject(self.session)
                 .environmentObject(self.settings)
diff --git a/Coder Desktop/Coder Desktop/NetworkExtension.swift b/Coder Desktop/Coder Desktop/NetworkExtension.swift
index 4c29256f..745579ea 100644
--- a/Coder Desktop/Coder Desktop/NetworkExtension.swift	
+++ b/Coder Desktop/Coder Desktop/NetworkExtension.swift	
@@ -3,17 +3,17 @@ import os
 
 enum NetworkExtensionState: Equatable {
     case unconfigured
-    case disbled
+    case disabled
     case enabled
     case failed(String)
 
     var description: String {
         switch self {
         case .unconfigured:
-            return "Not logged in to Coder"
+            return "NetworkExtension not configured, try logging in again"
         case .enabled:
             return "NetworkExtension tunnel enabled"
-        case .disbled:
+        case .disabled:
             return "NetworkExtension tunnel disabled"
         case let .failed(error):
             return "NetworkExtension config failed: \(error)"
@@ -24,6 +24,16 @@ enum NetworkExtensionState: Equatable {
 /// An actor that handles configuring, enabling, and disabling the VPN tunnel via the
 /// NetworkExtension APIs.
 extension CoderVPNService {
+    // Updates the UI if a previous configuration exists
+    func loadNetworkExtension() async {
+        do {
+            try await getTunnelManager()
+            neState = .disabled
+        } catch {
+            neState = .unconfigured
+        }
+    }
+
     func configureNetworkExtension(proto: NETunnelProviderProtocol) async {
         // removing the old tunnels, rather than reconfiguring ensures that configuration changes
         // are picked up.
@@ -47,6 +57,7 @@ extension CoderVPNService {
             logger.error("save tunnel failed: \(error)")
             neState = .failed(error.localizedDescription)
         }
+        neState = .disabled
     }
 
     func removeNetworkExtension() async throws(VPNServiceError) {
@@ -91,9 +102,10 @@ extension CoderVPNService {
             return
         }
         logger.debug("saved tunnel with enabled=false")
-        neState = .disbled
+        neState = .disabled
     }
 
+    @discardableResult
     private func getTunnelManager() async throws(VPNServiceError) -> NETunnelProviderManager {
         var tunnels: [NETunnelProviderManager] = []
         do {
diff --git a/Coder Desktop/Coder Desktop/VPNService.swift b/Coder Desktop/Coder Desktop/VPNService.swift
index cfc484b3..4510634f 100644
--- a/Coder Desktop/Coder Desktop/VPNService.swift	
+++ b/Coder Desktop/Coder Desktop/VPNService.swift	
@@ -50,7 +50,7 @@ final class CoderVPNService: NSObject, VPNService {
         guard sysExtnState == .installed else {
             return .failed(.systemExtensionError(sysExtnState))
         }
-        guard neState == .enabled || neState == .disbled else {
+        guard neState == .enabled || neState == .disabled else {
             return .failed(.networkExtensionError(neState))
         }
         return tunnelState
@@ -66,6 +66,9 @@ final class CoderVPNService: NSObject, VPNService {
     override init() {
         super.init()
         installSystemExtension()
+        Task {
+            await loadNetworkExtension()
+        }
     }
 
     var startTask: Task<Void, Never>?
diff --git a/Coder Desktop/VPN/Manager.swift b/Coder Desktop/VPN/Manager.swift
index bd598a0c..b441150f 100644
--- a/Coder Desktop/VPN/Manager.swift	
+++ b/Coder Desktop/VPN/Manager.swift	
@@ -16,13 +16,14 @@ actor Manager {
         .first!.appending(path: "coder-vpn.dylib")
     private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "manager")
 
+    // swiftlint:disable:next function_body_length
     init(with: PacketTunnelProvider, cfg: ManagerConfig) async throws(ManagerError) {
         ptp = with
         self.cfg = cfg
         #if arch(arm64)
-            let dylibPath = cfg.serverUrl.appending(path: "bin/coder-vpn-arm64.dylib")
+            let dylibPath = cfg.serverUrl.appending(path: "bin/coder-vpn-darwin-arm64.dylib")
         #elseif arch(x86_64)
-            let dylibPath = cfg.serverUrl.appending(path: "bin/coder-vpn-amd64.dylib")
+            let dylibPath = cfg.serverUrl.appending(path: "bin/coder-vpn-darwin-amd64.dylib")
         #else
             fatalError("unknown architecture")
         #endif
@@ -60,6 +61,14 @@ actor Manager {
         } catch {
             throw .handshake(error)
         }
+        do {
+            try await tunnelHandle.openTunnelTask?.value
+        } catch let error as TunnelHandleError {
+            logger.error("failed to wait for dylib to open tunnel: \(error, privacy: .public) ")
+            throw .tunnelSetup(error)
+        } catch {
+            fatalError("openTunnelTask must only throw TunnelHandleError")
+        }
         readLoop = Task { try await run() }
     }
 
@@ -180,7 +189,7 @@ actor Manager {
     }
 }
 
-public struct ManagerConfig {
+struct ManagerConfig {
     let apiToken: String
     let serverUrl: URL
 }
@@ -195,6 +204,29 @@ enum ManagerError: Error {
     case serverInfo(String)
     case errorResponse(msg: String)
     case noTunnelFileDescriptor
+
+    var description: String {
+        switch self {
+        case let .download(err):
+            return "Download error: \(err)"
+        case let .tunnelSetup(err):
+            return "Tunnel setup error: \(err)"
+        case let .handshake(err):
+            return "Handshake error: \(err)"
+        case let .validation(err):
+            return "Validation error: \(err)"
+        case .incorrectResponse:
+            return "Received unexpected response over tunnel"
+        case let .failedRPC(err):
+            return "Failed rpc: \(err)"
+        case let .serverInfo(msg):
+            return msg
+        case let .errorResponse(msg):
+            return msg
+        case .noTunnelFileDescriptor:
+            return "Could not find a tunnel file descriptor"
+        }
+    }
 }
 
 func writeVpnLog(_ log: Vpn_Log) {
diff --git a/Coder Desktop/VPN/PacketTunnelProvider.swift b/Coder Desktop/VPN/PacketTunnelProvider.swift
index 8f3e3cad..308882c1 100644
--- a/Coder Desktop/VPN/PacketTunnelProvider.swift	
+++ b/Coder Desktop/VPN/PacketTunnelProvider.swift	
@@ -1,5 +1,6 @@
 import NetworkExtension
 import os
+import VPNLib
 
 /* From <sys/kern_control.h> */
 let CTLIOCGINFO: UInt = 0xC064_4E03
@@ -8,7 +9,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
     private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "provider")
     private var manager: Manager?
 
-    public var tunnelFileDescriptor: Int32? {
+    var tunnelFileDescriptor: Int32? {
         var ctlInfo = ctl_info()
         withUnsafeMutablePointer(to: &ctlInfo.ctl_name) {
             $0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: $0.pointee)) {
@@ -47,19 +48,25 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
             completionHandler(nil)
             return
         }
+        let completionHandler = CallbackWrapper(completionHandler)
         Task {
             // TODO: Retrieve access URL & Token via Keychain
-            manager = try await Manager(
-                with: self,
-                cfg: .init(apiToken: "fake-token", serverUrl: .init(string: "https://dev.coder.com")!)
-            )
+            do throws(ManagerError) {
+                manager = try await Manager(
+                    with: self,
+                    cfg: .init(apiToken: "fake-token", serverUrl: .init(string: "https://dev.coder.com")!)
+                )
+                completionHandler(nil)
+            } catch {
+                completionHandler(error)
+                logger.error("error starting manager: \(error.description, privacy: .public)")
+            }
         }
-        completionHandler(nil)
     }
 
     override func stopTunnel(with _: NEProviderStopReason, completionHandler: @escaping () -> Void) {
         logger.debug("stopTunnel called")
-        guard manager == nil else {
+        guard manager != nil else {
             logger.error("stopTunnel called with nil Manager")
             completionHandler()
             return
diff --git a/Coder Desktop/VPN/TunnelHandle.swift b/Coder Desktop/VPN/TunnelHandle.swift
index 4258e1b3..ea800020 100644
--- a/Coder Desktop/VPN/TunnelHandle.swift	
+++ b/Coder Desktop/VPN/TunnelHandle.swift	
@@ -13,6 +13,9 @@ actor TunnelHandle {
     var writeHandle: FileHandle { tunnelReadPipe.fileHandleForWriting }
     var readHandle: FileHandle { tunnelWritePipe.fileHandleForReading }
 
+    // MUST only ever throw TunnelHandleError
+    var openTunnelTask: Task<Void, any Error>?
+
     init(dylibPath: URL) throws(TunnelHandleError) {
         guard let dylibHandle = dlopen(dylibPath.path, RTLD_NOW | RTLD_LOCAL) else {
             throw .dylib(dlerror().flatMap { String(cString: $0) } ?? "UNKNOWN")
@@ -22,13 +25,22 @@ actor TunnelHandle {
         guard let startSym = dlsym(dylibHandle, startSymbol) else {
             throw .symbol(startSymbol, dlerror().flatMap { String(cString: $0) } ?? "UNKNOWN")
         }
-        let openTunnelFn = unsafeBitCast(startSym, to: OpenTunnel.self)
+        let openTunnelFn = SendableOpenTunnel(unsafeBitCast(startSym, to: OpenTunnel.self))
         tunnelReadPipe = Pipe()
         tunnelWritePipe = Pipe()
-        let res = openTunnelFn(tunnelReadPipe.fileHandleForReading.fileDescriptor,
-                               tunnelWritePipe.fileHandleForWriting.fileDescriptor)
-        guard res == 0 else {
-            throw .openTunnel(OpenTunnelError(rawValue: res) ?? .unknown)
+        let rfd = tunnelReadPipe.fileHandleForReading.fileDescriptor
+        let wfd = tunnelWritePipe.fileHandleForWriting.fileDescriptor
+        openTunnelTask = Task { [openTunnelFn] in
+            try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, any Error>) in
+                DispatchQueue.global().async {
+                    let res = openTunnelFn(rfd, wfd)
+                    guard res == 0 else {
+                        cont.resume(throwing: TunnelHandleError.openTunnel(OpenTunnelError(rawValue: res) ?? .unknown))
+                        return
+                    }
+                    cont.resume()
+                }
+            }
         }
     }
 
@@ -89,3 +101,14 @@ enum OpenTunnelError: Int32 {
         }
     }
 }
+
+struct SendableOpenTunnel: @unchecked Sendable {
+    let fn: OpenTunnel
+    init(_ function: OpenTunnel) {
+        fn = function
+    }
+
+    func callAsFunction(_ lhs: Int32, _ rhs: Int32) -> Int32 {
+        fn(lhs, rhs)
+    }
+}
diff --git a/Coder Desktop/VPN/VPN.entitlements b/Coder Desktop/VPN/VPN.entitlements
index c5befc9a..a515bd39 100644
--- a/Coder Desktop/VPN/VPN.entitlements	
+++ b/Coder Desktop/VPN/VPN.entitlements	
@@ -12,5 +12,7 @@
 	<array>
 		<string>$(TeamIdentifierPrefix)com.coder.Coder-Desktop</string>
 	</array>
+	<key>com.apple.security.network.client</key>
+	<true/>
 </dict>
 </plist>
diff --git a/Coder Desktop/VPNLib/Util.swift b/Coder Desktop/VPNLib/Util.swift
new file mode 100644
index 00000000..9dbfbc79
--- /dev/null
+++ b/Coder Desktop/VPNLib/Util.swift	
@@ -0,0 +1,12 @@
+public final class CallbackWrapper<T, U>: @unchecked Sendable {
+    private let block: (T?) -> U
+
+    public init(_ block: @escaping (T?) -> U) {
+        self.block = block
+    }
+
+    public func callAsFunction(_ error: T?) -> U {
+        // Just forward to the original block
+        block(error)
+    }
+}