Network Extension

RSS for tag

Customize and extend the core networking features of iOS, iPad OS, and macOS using Network Extension.

Posts under Network Extension tag

200 Posts

Post

Replies

Boosts

Views

Activity

Accepted Use Case of the Network Extension Entitlement?
Hi! I recently had an idea to build an iOS app that allows users to create a system-level block of specified web domains by curating a "blacklist" on their device. If the user, for instance, inputs "*example.com" to their list, their iPhone would be blocked from relaying that network traffic to their ISP/DNS, and hence return an error message ("iPhone can't open the page because the address is invalid") instead of successfully fetching the response from example.com's servers. The overarching goal of this app would be to allow users to time-block their use of specified websites/apps and grant them greater agency over their technology consumption, and I thought that an app that blocks traffic at the network level, combined with the ability to control when to/not to allow access, would be a powerful alternative to the existing implementations out there that work more on the browser-level (eg. via Safari extension, which is isolated to the scope of user's Safari browser) or via Screen Time (which can be easy to bypass by inputting one's passcode). Another thing to mention is that since the app would serve as a local DNS proxy (instead of relying on a third party DNS resolver), none of their internet activity will be collected/transmitted off-device and be used for commercial purposes. I feel particularly driven to create a privacy-centered app in this way, since no user data needs to be harvested to implement this kind of filtering. I'd also love to get suggestions for a transparent privacy policy that respects users control over their device. With all this said, I found that the Network Extension APIs may be the only way that an app like this could be built on iOS and, I wanted to ask if the above-mentioned use case of Network Extension would be eligible to be granted access to its entitlement before I go ahead and purchase the $99/year Apple Developer Program membership. Happy to provide further information, and I'd also particularly be open to any mentions of existing solutions out there (since I might have missed some in my search). Maybe something like this already exists, in which case it'd be great to know in any case! :). Thank you so much in advance!
4
0
262
Feb ’26
How to get url-filter-provider entitlement approved for App Store distribution?
I'm building a content filtering app using NEURLFilterManager and NEURLFilterControlProvider (introduced in iOS 26). The app uses a PIR server for privacy-preserving URL filtering. Everything works with development-signed builds, but App Store export validation rejects: Entitlement value "url-filter-provider" for com.apple.developer.networking.networkextension — "not supported on iOS" I have "Network Extensions" enabled on my App IDs in the developer portal, but the provisioning profiles don't seem to include url-filter-provider, and I don't see a URL filter option in the Capability Requests tab. What I've tried: Entitlement values: url-filter-provider, url-filter — both rejected at export Extension points: com.apple.networkextension.url-filter, com.apple.networkextension.url-filter-control — both rejected Regenerating provisioning profiles after enabling Network Extensions capability My setup: iOS 26, Xcode 26 Main app bundle: com.pledgelock.app URL filter extension bundle: com.pledgelock.app.url-filter PIR server deployed and functional Is there a specific request or approval process needed for the url-filter-provider entitlement? The WWDC25 session "Filter and tunnel network traffic with NetworkExtension" mentions this entitlement but I can't find documentation on how to get it approved for distribution. Any guidance appreciated. Thanks!
1
0
306
Feb ’26
NETransparentProxyProvider – Support for Port Ranges in NENetworkRule
Hello, We are implementing a Transparent Proxy using NETransparentProxyProvider and configuring NETransparentProxyNetworkSettings with NENetworkRule. Currently, NENetworkRule requires: NENetworkRule( destinationHost: NWHostEndpoint(hostname: String, port: String), protocol: .TCP / .UDP / .any ) NWHostEndpoint.port accepts only a single port value (as a String) or an empty string for all ports. At present, we are creating a separate NENetworkRule for each port in the range (ex for range 49152–65535 approximately 16,384 rules). After deploying this configuration, we observe the following behavior: nesessionmanager starts consuming very high CPU (near 100%) The system becomes unresponsive The device eventually hangs and restarts automatically The behavior resembles a kernel panic scenario This strongly suggests that creating thousands of NENetworkRule entries may not be a supported or scalable approach. Questions: Is there any officially supported way to specify a port range in NENetworkRule? Is creating thousands of rules (one per port) considered acceptable or supported? Is the recommended design to intercept broadly (e.g., port = "") and filter port ranges inside handleNewTCPFlow / handleNewUDPFlow instead? Are there documented system limits for the number of NENetworkRule entries allowed in NETransparentProxyNetworkSettings?
2
0
142
Feb ’26
NEFilterManager fails with NEFilterErrorDomain Code=1 (“Configuration invalid or read/write failed”) on iOS — is NEFilter supported on non-supervised devices?
Hi, I’m implementing a NetworkExtension content filter provider on iOS and I can’t get it to activate on device. I have an iOS app (App Store distribution) with a content filter provider extension (NEFilterDataProvider). The app builds, installs, and runs fine, and the extension is embedded correctly. Entitlements appear to be set for both the app and the extension, and the extension’s Info.plist is configured as expected. However, when I try to enable the filter via NEFilterManager (loadFromPreferences → set configuration → isEnabled = true → saveToPreferences), saveToPreferences fails with NEFilterErrorDomain code 1 and the message “Configuration invalid or read/write failed.” The extension never starts and startFilter() is never called. Main app bundle ID: uk.co.getnovi.student Extension bundle ID: uk.co.getnovi.student.NoviContentFilter Extension type: NEFilterDataProvider We are testing on an iPhone 15 running iOS 18.6.2 (22G100). This app is intended for education use on student-owned personal iPhones installed from the App Store. The devices we are testing on are not supervised and not enrolled in MDM. We already use the Family Controls framework (ManagedSettings) for app restrictions and have the com.apple.developer.family-controls entitlement enabled for App Store distribution. I’ve read TN3134 and noticed content filter providers on iOS are described as “supervised devices only” in general, with additional notes around iOS 15.0 for “apps using Screen Time APIs” and iOS 16.0 for “per-app on managed devices,” plus a note that in the Screen Time case content filters are only supported on child devices. My question is whether this error is what you’d expect when attempting to enable a content filter provider on a non-supervised, non-managed device, or whether this should still work if the entitlement and configuration are correct. If non-supervised devices are not supported, is there any supported path for enabling NEFilter on iOS without supervision/MDM (for example via the Screen Time / Family Controls child authorization pathway), or will the system always refuse to enable the filter on standard devices? TLDR: is NEFilterDataProvider supported on non-supervised devices for consumer App Store apps, or is this a platform restriction that cannot be worked around? Thanks, Matt
2
0
116
Feb ’26
Connection drops while using Content Filter & App Proxy Provider
I have a network extension that hosts a NEFilterDataProvider & NETransparentProxyProvider. One of the use case that this caters to is : Proxy some flows (depending on originating app) while Content filter is also filtering flows based on business logic. The issue I am running into happens when FilterDataProvider sees a flow & responds with filterDataVerdict(withFilterInbound: false, peekInboundBytes: 0, filterOutbound: true, peekOutboundBytes:1024 to handleNewFlow(_ flow: NEFilterFlow) [wants to peek more bytes on outbound connection before making a decision] TransparentProxyProvider sees the flow & responds with NO to handleNewFlow(_ flow: NEAppProxyFlow) as it is not interested in in proxying that flow. When this occurs, we see connection being dropped by kernel. I wanted to know if this is expected behavior. Logs when this occurs: 2026-02-06 14:57:09.725854-0600 0x17c918f Default 0x0 569 0 com.test.networkextension: (NetworkExtension) [com.apple.networkextension:] [Extension com.test.network]: provider rejected new flow TCP headless_shell[{length = 20, bytes = 0xe69023e655b6065e1a2f94fa508807fa43f6ac8a}] remote: 100.72.0.3:443 interface utun9 2026-02-06 14:57:09.725874-0600 0x17ca166 Debug 0x0 569 0 com.test.networkextension: (NetworkExtension) [com.apple.networkextension:] New flow verdict for D89B5B5D-793C-4940-D955-37BE33F18005: drop = NO remediate = NO needRules = NO shouldReport = YES pause = NO urlAppendString = NO filterInbound = NO peekInboundBytes = 0 filterOutbound = YES peekOutboundBytes = 1024 statisticsReportFrequency = low 2026-02-06 14:57:09.726009-0600 0x17ca24a Default 0x0 569 0 com.test.networkextension: (libnetworkextension.dylib) [com.apple.networkextension:] (410011084): Closing reads (sending SHUT_WR), closed by plugin (flow error: 0) 2026-02-06 14:57:09.726028-0600 0x17ca24a Default 0x0 569 0 com.test.networkextension: (libnetworkextension.dylib) [com.apple.networkextension:] (410011084): Closing writes, sending SHUT_RD 2026-02-06 14:57:09.726040-0600 0x17ca24a Debug 0x0 569 0 com.test.networkextension: (libnetworkextension.dylib) [com.apple.networkextension:] (410011084): Dropping the director 2026-02-06 14:57:09.726047-0600 0x17ca24a Default 0x0 569 0 com.test.networkextension: (libnetworkextension.dylib) [com.apple.networkextension:] (410011084): Destroying, client tx 0, client rx 0, kernel rx 0, kernel tx 0 I wanted to know how neagent is handling this when for a flow, filterDataProvider wants to look at the traffic while transparentProxy is not interested in handling that flow
3
0
129
Feb ’26
Collecting OSLog logs from network extensions
I have an iOS app with a network extension that's using OSLog to log various bits of information that are useful for debugging. I'm currently trying to add a simple button that bundles up those logs with some other information and presents the user with a Share sheet so they can send it to support teams. I looked at OSLogStore but it only collects logs for the current process so the user clicking a button in my app wouldn't collect logs from my network extension. I would really like to avoid having to guide users through the process of creating and sharing a sysdiagnose but it seems like this might be the only option. How do other folks do this kind of thing? Is there a recommended way to do it?
1
0
91
Feb ’26
NEFilterManager fails with NEFilterErrorDomain Code=1 (“Configuration invalid or read/write failed”) on iOS — is NEFilter supported on non-supervised devices?
Hi, I’m implementing a NetworkExtension content filter provider on iOS and I can’t get it to activate on device. I have an iOS app (App Store distribution) with a content filter provider extension (NEFilterDataProvider). The app builds, installs, and runs fine, and the extension is embedded correctly. Entitlements appear to be set for both the app and the extension, and the extension’s Info.plist is configured as expected. However, when I try to enable the filter via NEFilterManager (loadFromPreferences → set configuration → isEnabled = true → saveToPreferences), saveToPreferences fails with NEFilterErrorDomain code 1 and the message “Configuration invalid or read/write failed.” The extension never starts and startFilter() is never called. Main app bundle ID: uk.co.getnovi.student Extension bundle ID: uk.co.getnovi.student.NoviContentFilter Extension type: NEFilterDataProvider We are testing on an iPhone 15 running iOS 18.6.2 (22G100), the app is designed to run on iPhone. This app is intended for education use on student-owned personal iPhones installed from the App Store. The devices we are testing on are not supervised and not enrolled in MDM. We already use the Family Controls framework (ManagedSettings) for app restrictions and have the com.apple.developer.family-controls entitlement enabled for App Store distribution. I’ve read TN3134 and noticed content filter providers on iOS are described as “supervised devices only” in general, with additional notes around iOS 15.0 for “apps using Screen Time APIs” and iOS 16.0 for “per-app on managed devices,” plus a note that in the Screen Time case content filters are only supported on child devices. My question is whether this error is what you’d expect when attempting to enable a content filter provider on a non-supervised, non-managed device, or whether this should still work if the entitlement and configuration are correct. If non-supervised devices are not supported, is there any supported path for enabling NEFilter on iOS without supervision/MDM (for example via the Screen Time / Family Controls child authorization pathway), or will the system always refuse to enable the filter on standard devices? In summary: is NEFilterDataProvider supported on non-supervised devices for consumer App Store apps, or is this a platform restriction that cannot be worked around? Thanks, Matt
1
0
76
Feb ’26
Reproducible EXC_BAD_ACCESS in NEDNSProxyProvider when using async/await variants of NEAppProxyUDPFlow
Description I am seeing a consistent crash in a NEDNSProxyProvider on iOS when migrating from completion handlers to the new Swift Concurrency async/await variants of readDatagrams() and writeDatagrams() on NEAppProxyUDPFlow. The crash occurs inside the Swift Concurrency runtime during task resumption. Specifically, it seems the Task attempts to return to the flow’s internal serial executor (NEFlow queue) after a suspension point, but fails if the flow was invalidated or deallocated by the kernel while the task was suspended. Error Signature Thread 4: EXC_BAD_ACCESS (code=1, address=0x28) Thread 4 Queue : NEFlow queue (serial) #0 0x000000018fe919cc in swift::AsyncTask::flagAsAndEnqueueOnExecutor () #9 0x00000001ee25c3b8 in _pthread_wqthread () Steps The crash is highly timing-dependent. To reproduce it reliably: Use an iOS device with Developer Settings enabled. Go to Developer > Network Link Conditioner -> High Latency DNS. Intercept a DNS query and perform a DoH (DNS-over-HTTPS) request using URLSession. The first few network requests should trigger the crash Minimum Working Example (MWE) class DNSProxyProvider: NEDNSProxyProvider { override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool { guard let udpFlow = flow as? NEAppProxyUDPFlow else { return false } Task(priority: .userInitiated) { await handleUDPFlow(udpFlow) } return true } func handleUDPFlow(_ flow: NEAppProxyUDPFlow) async { do { try await flow.open(withLocalFlowEndpoint: nil) while !Task.isCancelled { // Suspension point 1: Waiting for datagrams let (flowData, error) = await flow.readDatagrams() if let error { throw error } guard let flowData, !flowData.isEmpty else { return } var responses: [(Data, Network.NWEndpoint)] = [] for (data, endpoint) in flowData { // Suspension point 2: External DoH resolution let response = try await resolveViaDoH(data) responses.append((response, endpoint)) } // Suspension point 3: Writing back to the flow // Extension will crash here on task resumption try await flow.writeDatagrams(responses) } } catch { flow.closeReadWithError(error) flow.closeWriteWithError(error) } } private func handleFlowData(_ packet: Data, endpoint: Network.NWEndpoint, using parameters: NWParameters) async throws -> Data { let url = URL(string: "https://dns.google/dns-query")! var request = URLRequest(url: url) request.httpMethod = "POST" request.httpBody = packet request.setValue("application/dns-message", forHTTPHeaderField: "Content-Type") let (data, _) = try await URLSession.shared.data(for: request) return data } } Crash Details & Analysis The disassembly at the crash point indicates a null dereference of an internal executor pointer (Voucher context): ldr x20, [TPIDRRO_EL0 + 0x340] ldr x0, [x20, #0x28] // x20 is NULL/0x0 here, resulting in address 0x28 It appears that NEAppProxyUDPFlow’s async methods bind the Task to a specific internal executor. When the kernel reclaims the flow memory, the pointer in x20 becomes invalid. Because the Swift runtime is unaware that the NEFlow queue executor has vanished, it attempts to resume on non-existing flow and then crashes. Checking !Task.isCancelled does not prevent this, as the crash happens during the transition into the task body before the cancellation check can even run. Questions Is this a known issue of the NetworkExtension async bridge? Why does Task.isCancelled not reflect the deallocation of the underlying NEAppProxyFlow? Is the only safe workaround? Please feel free to correct me if I misunderstood anything here. I'll be happy to hear any insights or suggestions :) Thank you!
4
0
357
Feb ’26
iOS 26: Unable to Transition from CallKit Screen to App when remoteHandle is nil or empty string
Hello, I am developing an internal phone application using CallKit. I am experiencing an issue with the behavior of remoteHandle settings in iOS 26 and would appreciate any insights you can provide towards a solution. 1. Problem Description When an iPhone running iOS 26 is in a sleep state and receives a VoIP incoming call where remoteHandle is set to nil or an empty string (@""), we are unable to transition to our application (the UIExtension provided by the provider) from the CallKit UI's "More" (…) button after answering the call. 2. Conditions and Symptoms OS Version: iOS 26 Initial State: iPhone is in a sleep state Call Type: An unsolicited(unknown number) VoIP incoming call where the CXCallUpdate's remoteHandle is set to either nil or [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:@""] Symptoms: After answering the VoIP call by sliding the button, selecting the "More" (…) button displayed on the CallKit screen does not launch our application's UIExtension (custom UI), and the iPhone instead stay to the CallKit screen. 3. Previous Behavior (Up to iOS 18) Up to iOS 18, even when remoteHandle was set to an empty string using the following code, the application would transition normally from "More" after answering an incoming call from a sleep state. CXCallUpdate *update = [[CXCallUpdate alloc] init]; update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:@""]; [provider reportNewIncomingCallWithUUID:uuid update:update completion:completion]; 4. Unsuccessful Attempts to Resolve The issue remained unresolved after changing the handling for unsolicited(unknown number) incoming calls as follows: CXCallUpdate *update = [[CXCallUpdate alloc] init]; update.remoteHandle = nil; // Set remoteHandle to nil [provider reportNewIncomingCallWithUUID:uuid update:update completion:completion]; 5. Workaround (Temporary) The problem can be resolved, and the application can transition successfully, by setting a dummy numerical value (e.g., "0") for the value in remoteHandle using the following code: CXCallUpdate *update = [[CXCallUpdate alloc] init]; update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:@"0"]; // Set a dummy numerical value [provider reportNewIncomingCallWithUUID:uuid update:update completion:completion]; 6. Additional Information If remoteHandle is correctly set with the caller's number (i.e., not an unsolicited(unknown number) call; e.g., value:@"1234567890"), the application transitions normally from the "More" button after answering an incoming call from a sleep state, even in iOS 26. The above issue does not occur when answering incoming calls while the iPhone is in an active state (not sleeping). 7. Questions Have there been any other reports of similar behavior? Should this be considered a bug in CallKit for iOS 26? Should I make file a new Feedback report? Is there a suitable method to resolve this issue when the caller ID is unsolicited (nil or an empty string)? This problem significantly impacts user operations as end-users are unable to perform essential in-app actions such as hold or transfer after answering an unsolicited(unknown number) call from a sleep state. We are eager to find an urgent solution and would appreciate any information or advice you can provide. Thank you for your assistance.
5
0
485
Feb ’26
NETransparentProxyProvider frequent tunnel churn during Dark Wake cycles on macOS.
Description Our NETransparentProxyProvider system extension maintains a persistent TLS/DTLS control channel to a security gateway. To maintain this stateful connection the extension sends application-level "Keep Alive" packets every few seconds (example : 20 seconds). The Issue: When the macOS device enters a sleep state, the Network Extension process is suspended, causing our application-level heartbeat to cease. Consequently, our backend gateway—detecting no activity—terminates the session via Dead Peer Detection (DPD). The problem is exacerbated by macOS Dark Wake cycles. We observe the extension's wake() callback being triggered periodically (approx. every 15 minutes) while the device remains in a sleep state (lid closed). During these brief windows: The extension attempts to use the existing socket, finds it terminated by the backend, and initiates a full re-handshake. Shortly after the connection is re-established, the OS triggers the sleep() callback and suspends the process again. This creates a "connection churn" cycle that generates excessive telemetry noise and misleading "Session Disconnected" alerts for our enterprise customers. Steps to Reproduce Activate Proxy: Start the NETransparentProxyProvider and establish a TLS session to a gateway. Apply Settings: Configure NETransparentProxyNetworkSettings to intercept outbound TCP/UDP traffic. Initialize Heartbeat: Start a 20-second timer (DispatchSourceTimer) to log and send keep-alive packets. Induce Sleep: Put the Mac to sleep (Apple Menu > Sleep). Observe Logs: Monitor the system via sysdiagnose or the macOS Console. Observation: Logs stop entirely during sleep, indicating process suspension. Observation: wake() and sleep() callbacks are triggered repeatedly during Dark Wake intervals, causing a cycle of re-connections. Expected Behavior We seek to minimize connection turnover during maintenance wakes and maintain session stability while the device is technically in a sleep state. Questions for Apple Is it possible to suppress the sleep and wake callback methods of NETransparentProxyProvider when the device is performing a maintenance/Dark Wake, only triggering them for a full user-initiated wake? Is it possible to prevent the NETransparentProxyProvider process from being suspended during sleep, or at least grant it a high-priority background execution slot to maintain the heartbeat? If suspension is mandatory, is there a recommended way to utilize TCP_KEEPALIVE socket options that the kernel can handle on behalf of the suspended extension? How can the extension programmatically identify if a wake() call is a "Dark Wake" versus a "Full User Wake" to avoid unnecessary re-connection logic?
3
0
166
Feb ’26
TransparentProxy extension is not enabled when user performs logout and login with the extension deployed using MDM
We have an application which is written in Swift, which activates Transparent Proxy network extension. Our Transparent Proxy module is a system extension, which is exposing an app proxy provider interface (We are using NETransparentProxyProvider class and in extension’s Info.plist we use com.apple.networkextension.app-proxy key.) We are using JamF MDM profile with VPN payload for deployment. With this MDM profile, we are observing an issue, ie TransparentProxy extension is not enabled when user performs logout and login and only in Sonoma. By analyzing it further we are noticing that in Sonoma some times, the system invokes NETransparentProxyProvider's stopProxy delegate once or twice with NEProviderStopReason as 12 ie userLogout. Due to this after login the system extension is not activated.
2
0
97
Feb ’26
Title: Accessing Wi-Fi SSID for custom On-Demand logic in PacketTunnelProvider on macOS
We are developing a macOS VPN application using NEPacketTunnelProvider with a custom encryption protocol. We are using standard On-Demand VPN rules with Wi-Fi SSID matching but we want to add some additional feature to the native behaviour.  We want to control the 'conenect/disconnect' button status and allow the user to interact with the tunnel even when the on demand rule conditions are satisfied, is there a native way to do it? In case we need to implement our custom on-demand behaviour we need to access to this information: connected interface type ssid name and being informed when it changes so to trigger our logic, how to do it from the app side? we try to use CWWiFiClient along with ssidDidChangeForWiFiInterface monitoring, it returns just the interface name en0 and not the wifi ssid name. Is location access mandatory to access wifi SSID on macOS even if we have a NEPacketTunnelProvider? Please note that we bundle our Network Extension as an App Extension (not SystemExtension).
9
2
380
Jan ’26
Does NETunnelProvider (Packet Tunnel) require a special entitlement for App Store VPN apps?
I’m working on an iOS VPN app and looking into using NETunnelProvider (Packet Tunnel) for the VPN implementation. From the documentation it seems that Packet Tunnel is required for VPN protocols like OpenVPN, but the Packet Tunnel capability doesn’t appear to be available by default. Does using NETunnelProvider / Packet Tunnel require a special entitlement to be enabled by Apple for App Store apps? If so, what is the general process for requesting or enabling that entitlement?
1
0
691
Jan ’26
NetworkExtension framework problems
Case-ID: 17935956 In the NetworkExtension framework, for the NETransparentProxyProvider and NEDNSProxyProvider classes: when calling the open func writeDatagrams(_ datagrams: [Data], sentBy remoteEndpoints: [NWEndpoint]) async throwsin the NEDNSProxyProvider class, and the open func write(_ data: Data, withCompletionHandler completionHandler: @escaping @Sendable ((any Error)?) -> Void)in the NETransparentProxyProvider class, errors such as "The operation could not be completed because the flow is not connected" and "Error Domain=NEAppProxyFlowErrorDomain Code=1 "The operation could not be completed because the flow is not connected"" occur. Once this issue arises, if it occurs in the NEDNSProxyProvider, the entire system's DNS will fail to function properly; if it occurs in the NETransparentProxyProvider, the entire network will become unavailable.
7
0
250
Jan ’26
What is the recommended way to programmatically apply proxy to WKWebView
Hi Apple engineers! We are making an iOS browser and are planing to deliver a feature that allows enterprise customers to use a MAM key to set a PAC file for proxy. It's designed to support unmanaged device so the MDM based solutions like 'Global HTTP Proxy MDM payload' or 'Per-App VPN' simply don't work. After doing some research we found that with WKWebView, the only framework allowed on iOS for web browsing, there's no API for programmatically setting proxy. The closes API is the WKURLSchemeHandler, but it's for data management not network request interception, in other word it can not be used to handle HTTP/HTTPS request well. When we go from the web-view level to the app level, it seems there's no API to let an app set proxy for itself at an app-level, the closest API is Per-App VPN but as mentioned above, Per-App VPN is only available for managed device so we can't use that as well. Eventually we go to the system level, and try to use Network Extension, but there's still obstacles. It seems Network Extension doesn't directly provide a way to write system proxy. In order to archive that, we may have to use Packet Tunnel Provider in destination IP mode and create a local VPN server to loop back the network traffic and do the proxy stuff in that server. In other word, the custom VPN protocol is 'forward directly without encryption'. This approach looks viable as we see some of the network analysis tools use this approach, but still I'd like to ask is this against App Store Review Guidelines? If the above approach with Network Extension is not against App Store Review Guidelines, I have a further question that, what is the NEProxySettings of NETunnelNetworkSettings for? Is it the proxy which proxies the VPN traffic (in order to hide source IP from VPN provider) or it is the proxy to use after network traffic goes into the virtual private network? If none of the above is considered recommended, what is the recommended way to programmatically set proxy on WKWebView on an unmanaged device (regardless of where the proxy runs, web-view/app/system)?
4
0
1.8k
Jan ’26
[URGENT] NEFilterManager Error Code 5 "Permission Denied" in TestFlight - Works in Debug Mode
Tags NetworkExtension, NEFilterManager, Content-Filter, TestFlight, iOS, Swift, Entitlements, App-Groups Problem Summary I'm experiencing a critical issue with a Network Extension Content Filter that works perfectly in debug mode but fails in TestFlight with: ``` -[NEFilterManager saveToPreferencesWithCompletionHandler:]_block_invoke_3: failed to save the new configuration: Error Domain=NEFilterErrorDomain Code=5 "permission denied" UserInfo={NSLocalizedDescription=permission denied} ``` This is blocking completion of a client project and requires urgent assistance. Environment • Platform: iOS • Minimum Deployment: iOS 16.0 • Development: Xcode with Flutter integration • Testing Method: TestFlight (production build) • Works in: Debug mode (direct device deployment) • Fails in: TestFlight builds What Works vs. What Fails WORKS IN DEBUG MODE (✓): • Network extension installs successfully • System permission dialog appears correctly • Filter starts and blocks content as expected • All domain management functions work FAILS IN TESTFLIGHT (✗): • System permission dialog never appears • NEFilterManager.saveToPreferences fails immediately • Error Code 5: "permission denied" • Cannot set up the filter at all Implementation Details ARCHITECTURE: The implementation consists of: Main App (Flutter) - handles UI and configuration Network Extension Plugin (Swift) - bridges Flutter to NetworkExtension framework FilterDataProvider (Swift) - implements content filtering logic App Group - shared storage for configuration (group.app.v1.dev0) PERMISSION REQUEST CODE: ```swift func requestPermissions(completion: @escaping (Result<Bool, Error>) -> Void) { NEFilterManager.shared().loadFromPreferences { error in if let error = error { DispatchQueue.main.async { completion(.failure(error)) } return } let config = NEFilterProviderConfiguration() config.organization = "Testing config.filterBrowsers = true config.filterSockets = true let manager = NEFilterManager.shared() manager.providerConfiguration = config manager.localizedDescription = " Screen Shield" manager.isEnabled = true manager.saveToPreferences { saveError in DispatchQueue.main.async { completion(saveError == nil ? .success(true) : .failure(saveError!)) } } } } ``` EXTENSION INFO.PLIST: ```xml ENTITLEMENTS: ```xml What I've Already Tried VERIFIED ENTITLEMENTS (✓) • Both main app and extension have matching entitlements • App Group identifier is identical in both targets • content-filter-provider capability is set CHECKED PROVISIONING PROFILES (✓) • Created distribution provisioning profiles with Network Extension capability • App Group is included in all profiles • All capabilities are enabled in App Store Connect VERIFIED APP GROUP CONFIGURATION (✓) • App Group exists in Apple Developer portal • Added to both App ID and Extension App ID • Regenerated provisioning profiles after adding CODE SIGNING (✓) • Both targets build and sign successfully • No code signing errors during archive • Extension is embedded in main app bundle TESTFLIGHT REQUIREMENTS (✓) • Using distribution certificate for archive • Archive validation passes without warnings • Upload to TestFlight successful BUILD CONFIGURATION (✓) • Minimum deployment target is iOS 16.0 for both targets • Extension deployment target matches main app • All required frameworks are properly linked Specific Questions Permission Dialog: In debug mode, the system permission dialog appears. In TestFlight, it never shows. Is there a TestFlight-specific permission issue with Network Extensions? Entitlements Propagation: Are there known issues with entitlements not being properly included in TestFlight builds despite being present in the archive? Distribution vs Development: Are there any differences in how Network Extensions are authorized between development builds and distribution builds? Additional Context • The extension works flawlessly when deployed directly from Xcode • No console errors or warnings in TestFlight build • UserDefaults(suiteName:) successfully accesses the App Group in both modes • Filter logic itself is tested and working (confirmed in debug mode) • This is urgent as it's blocking client project completion I tested this with both adult acc and also with child app What I Need Specific steps to diagnose why NEFilterManager.saveToPreferences returns Code 5 in TestFlight Confirmation of whether Network Extension entitlements require special handling for TestFlight Any known issues or workarounds for this specific error in production builds Debugging techniques that work in TestFlight environment (since console logs are limited) System Information • Xcode Version: Latest stable • iOS Target: 16.0+ • Swift Version: 5.0 • Framework: Flutter with native iOS plugin • Build Type: Distribution (Ad Hoc via TestFlight) Thank you for any assistance. This is blocking critical client work and I need to resolve it urgently.
1
0
193
Jan ’26
How to know when `NEPacketTunnelProvider` has been cleaned up?
I have noticed race conditions on macOS when tearing down and re-configuring an NEPacketTunnelProvider. My goal is to handle switching out one VPN profile for another identical/near identical one (I'll add some context for this below). The flow that I have tested was to wait for the NEVPNStatusDidChange notification to report a NEVPNStatus.disconnected state, and then start the process of re-configuring the VPN with a new profile. In practice however, I have noticed that I must wait a couple of seconds between NEVPNStatus.disconnected state being reported and setting up a new tunnel. Otherwise, the system routing table gets messed up but the VPN reports being in NEVPNStatus.connected state, resulting in a tunnel that appears healthy but can't be accessed. With this, I wanted to ask if you have any suggestions on any OS items I can observer, in order to deterministically know that the system has fully cleaned up my packet tunnel, and that I am safe to configure another? This would be much more optimal than a hard-coded delay. Additional context: Jamf is a common solution for deploying MDM configuration profiles. However, in my tests, it doesn't support Apple's recommended approach of using the PayloadIdentifier to mark profiles for replacement, as PayloadIdentifiers are automatically updated to match the PayloadUUID of that same profile on upload. Although given what I've observed, I'm not sure the Apple recommended approach would work here in any case. Additionally, it would be nice to transition from non-MDM to MDM cleanly, however, this also requires an indeterminate wait time between the non-MDM configuration being disconnected and subsequently removed, and the MDM one being configured. With these scenarios, we need to be able to add a second configuration, with possibly identical VPN settings, then remove the old one, allowing the system to transition to the new configuration. For the MDM case, the pattern I've noticed on the system is that when the current profile is suddenly deleted, the connection will go into disconnected state, then NEVPNConfigurationChange will fire. The new profile can be configured from NEVPNConfigurationChange, however some time is needed to avoid races. For non-MDM, I had experimented with an approach of polling for MDM configurations appearing. When they do, I'd remove my previous notification observers, and set up a new NEVPNStatusDidChange notification observer, to remove the non-MDM VPN configuration after. it enters a disconnected state. Following the removal, I would call a function to reconfigure the VPN with new configuration. When this logic is in place, the call to stopVPNTunnel() is made. Again, a hardcoded delay is required between stopping and removing the old configuration and setting up a new one. Thanks!
3
0
137
Jan ’26
nesessionmanager “Resetting VPN On Demand” after sleep/wake
We’re developing an enterprise VPN client for macOS using NetworkExtension (PacketTunnelProvider) with Always-On / On-Demand VPN, deployed via MDM. On macOS 14.x and 15.x we observe the following log message from nesessionmanager: nesessionmanager: NESMVPNSession[...] Resetting VPN On Demand This most commonly occurs after sleep → wake. After this happens, the VPN no longer reconnects automatically, even though isOnDemandEnabled remains true and On-Demand rules are still present. Then a manual user action is required to reconnect. Questions: Is the “Resetting VPN On Demand” log message expected during sleep/wake transitions? Under what conditions does macOS reset On-Demand VPN state? Is there a supported way to detect or recover from this state programmatically? Any guidance on expected behavior or best practices would be appreciated.
1
0
139
Jan ’26
DNS Proxy system extension – OSSystemExtensionErrorDomain error 9 “validationFailed” on clean macOS machine
Hi, I’m implementing a macOS DNS Proxy as a system extension and running into a persistent activation error: OSSystemExtensionErrorDomain error 9 (validationFailed) with the message: extension category returned error This happens both on an MDM‑managed Mac and on a completely clean Mac (no MDM, fresh install). Setup macOS: 15.x (clean machine, no MDM) Xcode: 16.x Team ID: AAAAAAA111 (test) Host app bundle ID: com.example.agent.NetShieldProxy DNS Proxy system extension bundle ID: com.example.agent.NetShieldProxy.dnsProxy The DNS Proxy is implemented as a NetworkExtension system extension, not an app extension. Host app entitlements From codesign -d --entitlements :- /Applications/NetShieldProxy.app: xml com.apple.application-identifier AAAAAAA111.com.example.agent.NetShieldProxy <key>com.apple.developer.system-extension.install</key> <true/> <key>com.apple.developer.team-identifier</key> <string>AAAAAAA111</string> <key>com.apple.security.app-sandbox</key> <true/> <key>com.apple.security.application-groups</key> <array> <string>group.com.example.NetShieldmac</string> </array> <key>com.apple.security.files.user-selected.read-only</key> <true/> xml com.apple.application-identifier AAAAAAA111.com.example.agent.NetShieldProxy.dnsProxy <key>com.apple.developer.networking.networkextension</key> <array> <string>dns-proxy-systemextension</string> </array> <key>com.apple.developer.team-identifier</key> <string>AAAAAAA111</string> <key>com.apple.security.application-groups</key> <array> <string>group.com.example.NetShieldmac</string> <string>group.example.NetShieldmac</string> <string>group.example.agent.enterprise.macos</string> <string>group.example.com.NetShieldmac</string> </array> DNS Proxy system extension Info.plist On the clean Mac, from: bash plutil -p "/Applications/NetShieldProxy.app/Contents/Library/SystemExtensions/com.example.agent.NetShieldProxy.dnsProxy.systemextension/Contents/Info.plist" I get: json { "CFBundleExecutable" => "com.example.agent.NetShieldProxy.dnsProxy", "CFBundleIdentifier" => "com.example.agent.NetShieldProxy.dnsProxy", "CFBundleName" => "com.example.agent.NetShieldProxy.dnsProxy", "CFBundlePackageType" => "SYSX", "CFBundleShortVersionString" => "1.0.1.8", "CFBundleSupportedPlatforms" => [ "MacOSX" ], "CFBundleVersion" => "0.1.1", "LSMinimumSystemVersion" => "13.5", "NSExtension" => { "NSExtensionPointIdentifier" => "com.apple.dns-proxy", "NSExtensionPrincipalClass" => "com_example_agent_NetShieldProxy_dnsProxy.DNSProxyProvider" }, "NSSystemExtensionUsageDescription" => "SYSTEM_EXTENSION_USAGE_DESCRIPTION" } The DNSProxyProvider class inherits from NEDNSProxyProvider and is built in the system extension target. Activation code In the host app, I use: swift import SystemExtensions final class SystemExtensionActivator: NSObject, OSSystemExtensionRequestDelegate { private let extensionIdentifier = "com.example.agent.NetShieldProxy.dnsProxy" func activate(completion: @escaping (Bool) -> Void) { let request = OSSystemExtensionRequest.activationRequest( forExtensionWithIdentifier: extensionIdentifier, queue: .main ) request.delegate = self OSSystemExtensionManager.shared.submitRequest(request) } func request(_ request: OSSystemExtensionRequest, didFailWithError error: Error) { let nsError = error as NSError print("Activation failed:", nsError) } func request(_ request: OSSystemExtensionRequest, didFinishWithResult result: OSSystemExtensionRequest.Result) { print("Result:", result.rawValue) } } Runtime behavior on a clean Mac (no MDM) config.plist is created under /Library/Application Support/NetShield (via a root shell script). A daemon runs, contacts our backend, and writes /Library/Application Support/NetShield/state.plist with a valid dnsToken and other fields. The app NetShieldProxy.app is installed via a notarized, stapled Developer ID .pkg. The extension bundle is present at: /Applications/NetShieldProxy.app/Contents/Library/SystemExtensions/com.example.agent.NetShieldProxy.dnsProxy.systemextension. When I press Activate DNS Proxy in the UI, I see in the unified log: text NetShieldProxy: [com.example.agent:SystemExtensionActivator] Requesting activation for system extension: com.example.agent.NetShieldProxy.dnsProxy NetShieldProxy: [com.example.agent:SystemExtensionActivator] SystemExtensionActivator - activation failed: extension category returned error (domain=OSSystemExtensionErrorDomain code=9) NetShieldProxy: [com.example.agent:SystemExtensionActivator] SystemExtensionActivator - OSSystemExtensionError code enum: 9 NetShieldProxy: [com.example.agent:SystemExtensionActivator] SystemExtensionActivator - validationFailed And: bash systemextensionsctl list -> 0 extension(s) There is no prompt in Privacy & Security on this clean Mac. Question Given: The extension is packaged as a system extension (CFBundlePackageType = SYSX) with NSExtensionPointIdentifier = "com.apple.dns-proxy". Host and extension share the same Team ID and Developer ID Application cert. Entitlements on the target machine match the provisioning profile and Apple’s docs for DNS Proxy system extensions (dns-proxy-systemextension). This is happening on a clean Mac with no MDM profiles at all. What are the likely reasons for OSSystemExtensionErrorDomain error 9 (validationFailed) with "extension category returned error" in this DNS Proxy system extension scenario? Is there any additional configuration required for DNS Proxy system extensions (beyond entitlements and Info.plist) that could trigger this category-level validation failure? Any guidance or examples of a working DNS Proxy system extension configuration (host entitlements + extension Info.plist + entitlements) would be greatly appreciated. Thanks!
9
0
420
Jan ’26
When updating a VPN app with `includeAllNetworks`, the newer instance of the packet tunnel is not started via on-demand rules
When installing a new version the app while a tunnel is connected, seemingly the old packet tunnel process gets stopped but the new one does not come back up. Reportedly, a path monitor is reporting that the device has no connectivity. Is this the expected behavior? When installing an update from TestFlight or the App store, the packet tunnel instance from the old tunnel is stopped, but, due to the profile being on-demand and incldueAllNetworks, the path monitoring believes the device has no connectivity - so the new app is never downloaded. Is this the expected behavior? During development, the old packet tunnel gets stopped, the new app is installed, but the new packet tunnel is never started. To start it, the user has to toggle the VPN twice from the Settings app. The tunnel could be started from the VPN app too, if we chose to not take the path monitor into account, but then the user still needs to attempt to start the tunnel twice - it only works on the second try. As far as we can tell, the first time around, the packet tunnel never gets started, the app receives an update about NEVPNStatus being set to disconnecting yet NEVPNConnection does not throw. The behavior I was naively expecting was that the packet tunnel process would be stopped only when the new app is fully downloaded and when the update is installed, Are we doing something horribly wrong here?
7
3
650
Jan ’26
Accepted Use Case of the Network Extension Entitlement?
Hi! I recently had an idea to build an iOS app that allows users to create a system-level block of specified web domains by curating a "blacklist" on their device. If the user, for instance, inputs "*example.com" to their list, their iPhone would be blocked from relaying that network traffic to their ISP/DNS, and hence return an error message ("iPhone can't open the page because the address is invalid") instead of successfully fetching the response from example.com's servers. The overarching goal of this app would be to allow users to time-block their use of specified websites/apps and grant them greater agency over their technology consumption, and I thought that an app that blocks traffic at the network level, combined with the ability to control when to/not to allow access, would be a powerful alternative to the existing implementations out there that work more on the browser-level (eg. via Safari extension, which is isolated to the scope of user's Safari browser) or via Screen Time (which can be easy to bypass by inputting one's passcode). Another thing to mention is that since the app would serve as a local DNS proxy (instead of relying on a third party DNS resolver), none of their internet activity will be collected/transmitted off-device and be used for commercial purposes. I feel particularly driven to create a privacy-centered app in this way, since no user data needs to be harvested to implement this kind of filtering. I'd also love to get suggestions for a transparent privacy policy that respects users control over their device. With all this said, I found that the Network Extension APIs may be the only way that an app like this could be built on iOS and, I wanted to ask if the above-mentioned use case of Network Extension would be eligible to be granted access to its entitlement before I go ahead and purchase the $99/year Apple Developer Program membership. Happy to provide further information, and I'd also particularly be open to any mentions of existing solutions out there (since I might have missed some in my search). Maybe something like this already exists, in which case it'd be great to know in any case! :). Thank you so much in advance!
Replies
4
Boosts
0
Views
262
Activity
Feb ’26
How to get url-filter-provider entitlement approved for App Store distribution?
I'm building a content filtering app using NEURLFilterManager and NEURLFilterControlProvider (introduced in iOS 26). The app uses a PIR server for privacy-preserving URL filtering. Everything works with development-signed builds, but App Store export validation rejects: Entitlement value "url-filter-provider" for com.apple.developer.networking.networkextension — "not supported on iOS" I have "Network Extensions" enabled on my App IDs in the developer portal, but the provisioning profiles don't seem to include url-filter-provider, and I don't see a URL filter option in the Capability Requests tab. What I've tried: Entitlement values: url-filter-provider, url-filter — both rejected at export Extension points: com.apple.networkextension.url-filter, com.apple.networkextension.url-filter-control — both rejected Regenerating provisioning profiles after enabling Network Extensions capability My setup: iOS 26, Xcode 26 Main app bundle: com.pledgelock.app URL filter extension bundle: com.pledgelock.app.url-filter PIR server deployed and functional Is there a specific request or approval process needed for the url-filter-provider entitlement? The WWDC25 session "Filter and tunnel network traffic with NetworkExtension" mentions this entitlement but I can't find documentation on how to get it approved for distribution. Any guidance appreciated. Thanks!
Replies
1
Boosts
0
Views
306
Activity
Feb ’26
NETransparentProxyProvider – Support for Port Ranges in NENetworkRule
Hello, We are implementing a Transparent Proxy using NETransparentProxyProvider and configuring NETransparentProxyNetworkSettings with NENetworkRule. Currently, NENetworkRule requires: NENetworkRule( destinationHost: NWHostEndpoint(hostname: String, port: String), protocol: .TCP / .UDP / .any ) NWHostEndpoint.port accepts only a single port value (as a String) or an empty string for all ports. At present, we are creating a separate NENetworkRule for each port in the range (ex for range 49152–65535 approximately 16,384 rules). After deploying this configuration, we observe the following behavior: nesessionmanager starts consuming very high CPU (near 100%) The system becomes unresponsive The device eventually hangs and restarts automatically The behavior resembles a kernel panic scenario This strongly suggests that creating thousands of NENetworkRule entries may not be a supported or scalable approach. Questions: Is there any officially supported way to specify a port range in NENetworkRule? Is creating thousands of rules (one per port) considered acceptable or supported? Is the recommended design to intercept broadly (e.g., port = "") and filter port ranges inside handleNewTCPFlow / handleNewUDPFlow instead? Are there documented system limits for the number of NENetworkRule entries allowed in NETransparentProxyNetworkSettings?
Replies
2
Boosts
0
Views
142
Activity
Feb ’26
NEFilterManager fails with NEFilterErrorDomain Code=1 (“Configuration invalid or read/write failed”) on iOS — is NEFilter supported on non-supervised devices?
Hi, I’m implementing a NetworkExtension content filter provider on iOS and I can’t get it to activate on device. I have an iOS app (App Store distribution) with a content filter provider extension (NEFilterDataProvider). The app builds, installs, and runs fine, and the extension is embedded correctly. Entitlements appear to be set for both the app and the extension, and the extension’s Info.plist is configured as expected. However, when I try to enable the filter via NEFilterManager (loadFromPreferences → set configuration → isEnabled = true → saveToPreferences), saveToPreferences fails with NEFilterErrorDomain code 1 and the message “Configuration invalid or read/write failed.” The extension never starts and startFilter() is never called. Main app bundle ID: uk.co.getnovi.student Extension bundle ID: uk.co.getnovi.student.NoviContentFilter Extension type: NEFilterDataProvider We are testing on an iPhone 15 running iOS 18.6.2 (22G100). This app is intended for education use on student-owned personal iPhones installed from the App Store. The devices we are testing on are not supervised and not enrolled in MDM. We already use the Family Controls framework (ManagedSettings) for app restrictions and have the com.apple.developer.family-controls entitlement enabled for App Store distribution. I’ve read TN3134 and noticed content filter providers on iOS are described as “supervised devices only” in general, with additional notes around iOS 15.0 for “apps using Screen Time APIs” and iOS 16.0 for “per-app on managed devices,” plus a note that in the Screen Time case content filters are only supported on child devices. My question is whether this error is what you’d expect when attempting to enable a content filter provider on a non-supervised, non-managed device, or whether this should still work if the entitlement and configuration are correct. If non-supervised devices are not supported, is there any supported path for enabling NEFilter on iOS without supervision/MDM (for example via the Screen Time / Family Controls child authorization pathway), or will the system always refuse to enable the filter on standard devices? TLDR: is NEFilterDataProvider supported on non-supervised devices for consumer App Store apps, or is this a platform restriction that cannot be worked around? Thanks, Matt
Replies
2
Boosts
0
Views
116
Activity
Feb ’26
Connection drops while using Content Filter & App Proxy Provider
I have a network extension that hosts a NEFilterDataProvider & NETransparentProxyProvider. One of the use case that this caters to is : Proxy some flows (depending on originating app) while Content filter is also filtering flows based on business logic. The issue I am running into happens when FilterDataProvider sees a flow & responds with filterDataVerdict(withFilterInbound: false, peekInboundBytes: 0, filterOutbound: true, peekOutboundBytes:1024 to handleNewFlow(_ flow: NEFilterFlow) [wants to peek more bytes on outbound connection before making a decision] TransparentProxyProvider sees the flow & responds with NO to handleNewFlow(_ flow: NEAppProxyFlow) as it is not interested in in proxying that flow. When this occurs, we see connection being dropped by kernel. I wanted to know if this is expected behavior. Logs when this occurs: 2026-02-06 14:57:09.725854-0600 0x17c918f Default 0x0 569 0 com.test.networkextension: (NetworkExtension) [com.apple.networkextension:] [Extension com.test.network]: provider rejected new flow TCP headless_shell[{length = 20, bytes = 0xe69023e655b6065e1a2f94fa508807fa43f6ac8a}] remote: 100.72.0.3:443 interface utun9 2026-02-06 14:57:09.725874-0600 0x17ca166 Debug 0x0 569 0 com.test.networkextension: (NetworkExtension) [com.apple.networkextension:] New flow verdict for D89B5B5D-793C-4940-D955-37BE33F18005: drop = NO remediate = NO needRules = NO shouldReport = YES pause = NO urlAppendString = NO filterInbound = NO peekInboundBytes = 0 filterOutbound = YES peekOutboundBytes = 1024 statisticsReportFrequency = low 2026-02-06 14:57:09.726009-0600 0x17ca24a Default 0x0 569 0 com.test.networkextension: (libnetworkextension.dylib) [com.apple.networkextension:] (410011084): Closing reads (sending SHUT_WR), closed by plugin (flow error: 0) 2026-02-06 14:57:09.726028-0600 0x17ca24a Default 0x0 569 0 com.test.networkextension: (libnetworkextension.dylib) [com.apple.networkextension:] (410011084): Closing writes, sending SHUT_RD 2026-02-06 14:57:09.726040-0600 0x17ca24a Debug 0x0 569 0 com.test.networkextension: (libnetworkextension.dylib) [com.apple.networkextension:] (410011084): Dropping the director 2026-02-06 14:57:09.726047-0600 0x17ca24a Default 0x0 569 0 com.test.networkextension: (libnetworkextension.dylib) [com.apple.networkextension:] (410011084): Destroying, client tx 0, client rx 0, kernel rx 0, kernel tx 0 I wanted to know how neagent is handling this when for a flow, filterDataProvider wants to look at the traffic while transparentProxy is not interested in handling that flow
Replies
3
Boosts
0
Views
129
Activity
Feb ’26
Collecting OSLog logs from network extensions
I have an iOS app with a network extension that's using OSLog to log various bits of information that are useful for debugging. I'm currently trying to add a simple button that bundles up those logs with some other information and presents the user with a Share sheet so they can send it to support teams. I looked at OSLogStore but it only collects logs for the current process so the user clicking a button in my app wouldn't collect logs from my network extension. I would really like to avoid having to guide users through the process of creating and sharing a sysdiagnose but it seems like this might be the only option. How do other folks do this kind of thing? Is there a recommended way to do it?
Replies
1
Boosts
0
Views
91
Activity
Feb ’26
NEFilterManager fails with NEFilterErrorDomain Code=1 (“Configuration invalid or read/write failed”) on iOS — is NEFilter supported on non-supervised devices?
Hi, I’m implementing a NetworkExtension content filter provider on iOS and I can’t get it to activate on device. I have an iOS app (App Store distribution) with a content filter provider extension (NEFilterDataProvider). The app builds, installs, and runs fine, and the extension is embedded correctly. Entitlements appear to be set for both the app and the extension, and the extension’s Info.plist is configured as expected. However, when I try to enable the filter via NEFilterManager (loadFromPreferences → set configuration → isEnabled = true → saveToPreferences), saveToPreferences fails with NEFilterErrorDomain code 1 and the message “Configuration invalid or read/write failed.” The extension never starts and startFilter() is never called. Main app bundle ID: uk.co.getnovi.student Extension bundle ID: uk.co.getnovi.student.NoviContentFilter Extension type: NEFilterDataProvider We are testing on an iPhone 15 running iOS 18.6.2 (22G100), the app is designed to run on iPhone. This app is intended for education use on student-owned personal iPhones installed from the App Store. The devices we are testing on are not supervised and not enrolled in MDM. We already use the Family Controls framework (ManagedSettings) for app restrictions and have the com.apple.developer.family-controls entitlement enabled for App Store distribution. I’ve read TN3134 and noticed content filter providers on iOS are described as “supervised devices only” in general, with additional notes around iOS 15.0 for “apps using Screen Time APIs” and iOS 16.0 for “per-app on managed devices,” plus a note that in the Screen Time case content filters are only supported on child devices. My question is whether this error is what you’d expect when attempting to enable a content filter provider on a non-supervised, non-managed device, or whether this should still work if the entitlement and configuration are correct. If non-supervised devices are not supported, is there any supported path for enabling NEFilter on iOS without supervision/MDM (for example via the Screen Time / Family Controls child authorization pathway), or will the system always refuse to enable the filter on standard devices? In summary: is NEFilterDataProvider supported on non-supervised devices for consumer App Store apps, or is this a platform restriction that cannot be worked around? Thanks, Matt
Replies
1
Boosts
0
Views
76
Activity
Feb ’26
Reproducible EXC_BAD_ACCESS in NEDNSProxyProvider when using async/await variants of NEAppProxyUDPFlow
Description I am seeing a consistent crash in a NEDNSProxyProvider on iOS when migrating from completion handlers to the new Swift Concurrency async/await variants of readDatagrams() and writeDatagrams() on NEAppProxyUDPFlow. The crash occurs inside the Swift Concurrency runtime during task resumption. Specifically, it seems the Task attempts to return to the flow’s internal serial executor (NEFlow queue) after a suspension point, but fails if the flow was invalidated or deallocated by the kernel while the task was suspended. Error Signature Thread 4: EXC_BAD_ACCESS (code=1, address=0x28) Thread 4 Queue : NEFlow queue (serial) #0 0x000000018fe919cc in swift::AsyncTask::flagAsAndEnqueueOnExecutor () #9 0x00000001ee25c3b8 in _pthread_wqthread () Steps The crash is highly timing-dependent. To reproduce it reliably: Use an iOS device with Developer Settings enabled. Go to Developer > Network Link Conditioner -> High Latency DNS. Intercept a DNS query and perform a DoH (DNS-over-HTTPS) request using URLSession. The first few network requests should trigger the crash Minimum Working Example (MWE) class DNSProxyProvider: NEDNSProxyProvider { override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool { guard let udpFlow = flow as? NEAppProxyUDPFlow else { return false } Task(priority: .userInitiated) { await handleUDPFlow(udpFlow) } return true } func handleUDPFlow(_ flow: NEAppProxyUDPFlow) async { do { try await flow.open(withLocalFlowEndpoint: nil) while !Task.isCancelled { // Suspension point 1: Waiting for datagrams let (flowData, error) = await flow.readDatagrams() if let error { throw error } guard let flowData, !flowData.isEmpty else { return } var responses: [(Data, Network.NWEndpoint)] = [] for (data, endpoint) in flowData { // Suspension point 2: External DoH resolution let response = try await resolveViaDoH(data) responses.append((response, endpoint)) } // Suspension point 3: Writing back to the flow // Extension will crash here on task resumption try await flow.writeDatagrams(responses) } } catch { flow.closeReadWithError(error) flow.closeWriteWithError(error) } } private func handleFlowData(_ packet: Data, endpoint: Network.NWEndpoint, using parameters: NWParameters) async throws -> Data { let url = URL(string: "https://dns.google/dns-query")! var request = URLRequest(url: url) request.httpMethod = "POST" request.httpBody = packet request.setValue("application/dns-message", forHTTPHeaderField: "Content-Type") let (data, _) = try await URLSession.shared.data(for: request) return data } } Crash Details & Analysis The disassembly at the crash point indicates a null dereference of an internal executor pointer (Voucher context): ldr x20, [TPIDRRO_EL0 + 0x340] ldr x0, [x20, #0x28] // x20 is NULL/0x0 here, resulting in address 0x28 It appears that NEAppProxyUDPFlow’s async methods bind the Task to a specific internal executor. When the kernel reclaims the flow memory, the pointer in x20 becomes invalid. Because the Swift runtime is unaware that the NEFlow queue executor has vanished, it attempts to resume on non-existing flow and then crashes. Checking !Task.isCancelled does not prevent this, as the crash happens during the transition into the task body before the cancellation check can even run. Questions Is this a known issue of the NetworkExtension async bridge? Why does Task.isCancelled not reflect the deallocation of the underlying NEAppProxyFlow? Is the only safe workaround? Please feel free to correct me if I misunderstood anything here. I'll be happy to hear any insights or suggestions :) Thank you!
Replies
4
Boosts
0
Views
357
Activity
Feb ’26
iOS 26: Unable to Transition from CallKit Screen to App when remoteHandle is nil or empty string
Hello, I am developing an internal phone application using CallKit. I am experiencing an issue with the behavior of remoteHandle settings in iOS 26 and would appreciate any insights you can provide towards a solution. 1. Problem Description When an iPhone running iOS 26 is in a sleep state and receives a VoIP incoming call where remoteHandle is set to nil or an empty string (@""), we are unable to transition to our application (the UIExtension provided by the provider) from the CallKit UI's "More" (…) button after answering the call. 2. Conditions and Symptoms OS Version: iOS 26 Initial State: iPhone is in a sleep state Call Type: An unsolicited(unknown number) VoIP incoming call where the CXCallUpdate's remoteHandle is set to either nil or [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:@""] Symptoms: After answering the VoIP call by sliding the button, selecting the "More" (…) button displayed on the CallKit screen does not launch our application's UIExtension (custom UI), and the iPhone instead stay to the CallKit screen. 3. Previous Behavior (Up to iOS 18) Up to iOS 18, even when remoteHandle was set to an empty string using the following code, the application would transition normally from "More" after answering an incoming call from a sleep state. CXCallUpdate *update = [[CXCallUpdate alloc] init]; update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:@""]; [provider reportNewIncomingCallWithUUID:uuid update:update completion:completion]; 4. Unsuccessful Attempts to Resolve The issue remained unresolved after changing the handling for unsolicited(unknown number) incoming calls as follows: CXCallUpdate *update = [[CXCallUpdate alloc] init]; update.remoteHandle = nil; // Set remoteHandle to nil [provider reportNewIncomingCallWithUUID:uuid update:update completion:completion]; 5. Workaround (Temporary) The problem can be resolved, and the application can transition successfully, by setting a dummy numerical value (e.g., "0") for the value in remoteHandle using the following code: CXCallUpdate *update = [[CXCallUpdate alloc] init]; update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:@"0"]; // Set a dummy numerical value [provider reportNewIncomingCallWithUUID:uuid update:update completion:completion]; 6. Additional Information If remoteHandle is correctly set with the caller's number (i.e., not an unsolicited(unknown number) call; e.g., value:@"1234567890"), the application transitions normally from the "More" button after answering an incoming call from a sleep state, even in iOS 26. The above issue does not occur when answering incoming calls while the iPhone is in an active state (not sleeping). 7. Questions Have there been any other reports of similar behavior? Should this be considered a bug in CallKit for iOS 26? Should I make file a new Feedback report? Is there a suitable method to resolve this issue when the caller ID is unsolicited (nil or an empty string)? This problem significantly impacts user operations as end-users are unable to perform essential in-app actions such as hold or transfer after answering an unsolicited(unknown number) call from a sleep state. We are eager to find an urgent solution and would appreciate any information or advice you can provide. Thank you for your assistance.
Replies
5
Boosts
0
Views
485
Activity
Feb ’26
NETransparentProxyProvider frequent tunnel churn during Dark Wake cycles on macOS.
Description Our NETransparentProxyProvider system extension maintains a persistent TLS/DTLS control channel to a security gateway. To maintain this stateful connection the extension sends application-level "Keep Alive" packets every few seconds (example : 20 seconds). The Issue: When the macOS device enters a sleep state, the Network Extension process is suspended, causing our application-level heartbeat to cease. Consequently, our backend gateway—detecting no activity—terminates the session via Dead Peer Detection (DPD). The problem is exacerbated by macOS Dark Wake cycles. We observe the extension's wake() callback being triggered periodically (approx. every 15 minutes) while the device remains in a sleep state (lid closed). During these brief windows: The extension attempts to use the existing socket, finds it terminated by the backend, and initiates a full re-handshake. Shortly after the connection is re-established, the OS triggers the sleep() callback and suspends the process again. This creates a "connection churn" cycle that generates excessive telemetry noise and misleading "Session Disconnected" alerts for our enterprise customers. Steps to Reproduce Activate Proxy: Start the NETransparentProxyProvider and establish a TLS session to a gateway. Apply Settings: Configure NETransparentProxyNetworkSettings to intercept outbound TCP/UDP traffic. Initialize Heartbeat: Start a 20-second timer (DispatchSourceTimer) to log and send keep-alive packets. Induce Sleep: Put the Mac to sleep (Apple Menu > Sleep). Observe Logs: Monitor the system via sysdiagnose or the macOS Console. Observation: Logs stop entirely during sleep, indicating process suspension. Observation: wake() and sleep() callbacks are triggered repeatedly during Dark Wake intervals, causing a cycle of re-connections. Expected Behavior We seek to minimize connection turnover during maintenance wakes and maintain session stability while the device is technically in a sleep state. Questions for Apple Is it possible to suppress the sleep and wake callback methods of NETransparentProxyProvider when the device is performing a maintenance/Dark Wake, only triggering them for a full user-initiated wake? Is it possible to prevent the NETransparentProxyProvider process from being suspended during sleep, or at least grant it a high-priority background execution slot to maintain the heartbeat? If suspension is mandatory, is there a recommended way to utilize TCP_KEEPALIVE socket options that the kernel can handle on behalf of the suspended extension? How can the extension programmatically identify if a wake() call is a "Dark Wake" versus a "Full User Wake" to avoid unnecessary re-connection logic?
Replies
3
Boosts
0
Views
166
Activity
Feb ’26
TransparentProxy extension is not enabled when user performs logout and login with the extension deployed using MDM
We have an application which is written in Swift, which activates Transparent Proxy network extension. Our Transparent Proxy module is a system extension, which is exposing an app proxy provider interface (We are using NETransparentProxyProvider class and in extension’s Info.plist we use com.apple.networkextension.app-proxy key.) We are using JamF MDM profile with VPN payload for deployment. With this MDM profile, we are observing an issue, ie TransparentProxy extension is not enabled when user performs logout and login and only in Sonoma. By analyzing it further we are noticing that in Sonoma some times, the system invokes NETransparentProxyProvider's stopProxy delegate once or twice with NEProviderStopReason as 12 ie userLogout. Due to this after login the system extension is not activated.
Replies
2
Boosts
0
Views
97
Activity
Feb ’26
Title: Accessing Wi-Fi SSID for custom On-Demand logic in PacketTunnelProvider on macOS
We are developing a macOS VPN application using NEPacketTunnelProvider with a custom encryption protocol. We are using standard On-Demand VPN rules with Wi-Fi SSID matching but we want to add some additional feature to the native behaviour.  We want to control the 'conenect/disconnect' button status and allow the user to interact with the tunnel even when the on demand rule conditions are satisfied, is there a native way to do it? In case we need to implement our custom on-demand behaviour we need to access to this information: connected interface type ssid name and being informed when it changes so to trigger our logic, how to do it from the app side? we try to use CWWiFiClient along with ssidDidChangeForWiFiInterface monitoring, it returns just the interface name en0 and not the wifi ssid name. Is location access mandatory to access wifi SSID on macOS even if we have a NEPacketTunnelProvider? Please note that we bundle our Network Extension as an App Extension (not SystemExtension).
Replies
9
Boosts
2
Views
380
Activity
Jan ’26
Does NETunnelProvider (Packet Tunnel) require a special entitlement for App Store VPN apps?
I’m working on an iOS VPN app and looking into using NETunnelProvider (Packet Tunnel) for the VPN implementation. From the documentation it seems that Packet Tunnel is required for VPN protocols like OpenVPN, but the Packet Tunnel capability doesn’t appear to be available by default. Does using NETunnelProvider / Packet Tunnel require a special entitlement to be enabled by Apple for App Store apps? If so, what is the general process for requesting or enabling that entitlement?
Replies
1
Boosts
0
Views
691
Activity
Jan ’26
NetworkExtension framework problems
Case-ID: 17935956 In the NetworkExtension framework, for the NETransparentProxyProvider and NEDNSProxyProvider classes: when calling the open func writeDatagrams(_ datagrams: [Data], sentBy remoteEndpoints: [NWEndpoint]) async throwsin the NEDNSProxyProvider class, and the open func write(_ data: Data, withCompletionHandler completionHandler: @escaping @Sendable ((any Error)?) -> Void)in the NETransparentProxyProvider class, errors such as "The operation could not be completed because the flow is not connected" and "Error Domain=NEAppProxyFlowErrorDomain Code=1 "The operation could not be completed because the flow is not connected"" occur. Once this issue arises, if it occurs in the NEDNSProxyProvider, the entire system's DNS will fail to function properly; if it occurs in the NETransparentProxyProvider, the entire network will become unavailable.
Replies
7
Boosts
0
Views
250
Activity
Jan ’26
What is the recommended way to programmatically apply proxy to WKWebView
Hi Apple engineers! We are making an iOS browser and are planing to deliver a feature that allows enterprise customers to use a MAM key to set a PAC file for proxy. It's designed to support unmanaged device so the MDM based solutions like 'Global HTTP Proxy MDM payload' or 'Per-App VPN' simply don't work. After doing some research we found that with WKWebView, the only framework allowed on iOS for web browsing, there's no API for programmatically setting proxy. The closes API is the WKURLSchemeHandler, but it's for data management not network request interception, in other word it can not be used to handle HTTP/HTTPS request well. When we go from the web-view level to the app level, it seems there's no API to let an app set proxy for itself at an app-level, the closest API is Per-App VPN but as mentioned above, Per-App VPN is only available for managed device so we can't use that as well. Eventually we go to the system level, and try to use Network Extension, but there's still obstacles. It seems Network Extension doesn't directly provide a way to write system proxy. In order to archive that, we may have to use Packet Tunnel Provider in destination IP mode and create a local VPN server to loop back the network traffic and do the proxy stuff in that server. In other word, the custom VPN protocol is 'forward directly without encryption'. This approach looks viable as we see some of the network analysis tools use this approach, but still I'd like to ask is this against App Store Review Guidelines? If the above approach with Network Extension is not against App Store Review Guidelines, I have a further question that, what is the NEProxySettings of NETunnelNetworkSettings for? Is it the proxy which proxies the VPN traffic (in order to hide source IP from VPN provider) or it is the proxy to use after network traffic goes into the virtual private network? If none of the above is considered recommended, what is the recommended way to programmatically set proxy on WKWebView on an unmanaged device (regardless of where the proxy runs, web-view/app/system)?
Replies
4
Boosts
0
Views
1.8k
Activity
Jan ’26
[URGENT] NEFilterManager Error Code 5 "Permission Denied" in TestFlight - Works in Debug Mode
Tags NetworkExtension, NEFilterManager, Content-Filter, TestFlight, iOS, Swift, Entitlements, App-Groups Problem Summary I'm experiencing a critical issue with a Network Extension Content Filter that works perfectly in debug mode but fails in TestFlight with: ``` -[NEFilterManager saveToPreferencesWithCompletionHandler:]_block_invoke_3: failed to save the new configuration: Error Domain=NEFilterErrorDomain Code=5 "permission denied" UserInfo={NSLocalizedDescription=permission denied} ``` This is blocking completion of a client project and requires urgent assistance. Environment • Platform: iOS • Minimum Deployment: iOS 16.0 • Development: Xcode with Flutter integration • Testing Method: TestFlight (production build) • Works in: Debug mode (direct device deployment) • Fails in: TestFlight builds What Works vs. What Fails WORKS IN DEBUG MODE (✓): • Network extension installs successfully • System permission dialog appears correctly • Filter starts and blocks content as expected • All domain management functions work FAILS IN TESTFLIGHT (✗): • System permission dialog never appears • NEFilterManager.saveToPreferences fails immediately • Error Code 5: "permission denied" • Cannot set up the filter at all Implementation Details ARCHITECTURE: The implementation consists of: Main App (Flutter) - handles UI and configuration Network Extension Plugin (Swift) - bridges Flutter to NetworkExtension framework FilterDataProvider (Swift) - implements content filtering logic App Group - shared storage for configuration (group.app.v1.dev0) PERMISSION REQUEST CODE: ```swift func requestPermissions(completion: @escaping (Result<Bool, Error>) -> Void) { NEFilterManager.shared().loadFromPreferences { error in if let error = error { DispatchQueue.main.async { completion(.failure(error)) } return } let config = NEFilterProviderConfiguration() config.organization = "Testing config.filterBrowsers = true config.filterSockets = true let manager = NEFilterManager.shared() manager.providerConfiguration = config manager.localizedDescription = " Screen Shield" manager.isEnabled = true manager.saveToPreferences { saveError in DispatchQueue.main.async { completion(saveError == nil ? .success(true) : .failure(saveError!)) } } } } ``` EXTENSION INFO.PLIST: ```xml ENTITLEMENTS: ```xml What I've Already Tried VERIFIED ENTITLEMENTS (✓) • Both main app and extension have matching entitlements • App Group identifier is identical in both targets • content-filter-provider capability is set CHECKED PROVISIONING PROFILES (✓) • Created distribution provisioning profiles with Network Extension capability • App Group is included in all profiles • All capabilities are enabled in App Store Connect VERIFIED APP GROUP CONFIGURATION (✓) • App Group exists in Apple Developer portal • Added to both App ID and Extension App ID • Regenerated provisioning profiles after adding CODE SIGNING (✓) • Both targets build and sign successfully • No code signing errors during archive • Extension is embedded in main app bundle TESTFLIGHT REQUIREMENTS (✓) • Using distribution certificate for archive • Archive validation passes without warnings • Upload to TestFlight successful BUILD CONFIGURATION (✓) • Minimum deployment target is iOS 16.0 for both targets • Extension deployment target matches main app • All required frameworks are properly linked Specific Questions Permission Dialog: In debug mode, the system permission dialog appears. In TestFlight, it never shows. Is there a TestFlight-specific permission issue with Network Extensions? Entitlements Propagation: Are there known issues with entitlements not being properly included in TestFlight builds despite being present in the archive? Distribution vs Development: Are there any differences in how Network Extensions are authorized between development builds and distribution builds? Additional Context • The extension works flawlessly when deployed directly from Xcode • No console errors or warnings in TestFlight build • UserDefaults(suiteName:) successfully accesses the App Group in both modes • Filter logic itself is tested and working (confirmed in debug mode) • This is urgent as it's blocking client project completion I tested this with both adult acc and also with child app What I Need Specific steps to diagnose why NEFilterManager.saveToPreferences returns Code 5 in TestFlight Confirmation of whether Network Extension entitlements require special handling for TestFlight Any known issues or workarounds for this specific error in production builds Debugging techniques that work in TestFlight environment (since console logs are limited) System Information • Xcode Version: Latest stable • iOS Target: 16.0+ • Swift Version: 5.0 • Framework: Flutter with native iOS plugin • Build Type: Distribution (Ad Hoc via TestFlight) Thank you for any assistance. This is blocking critical client work and I need to resolve it urgently.
Replies
1
Boosts
0
Views
193
Activity
Jan ’26
How to know when `NEPacketTunnelProvider` has been cleaned up?
I have noticed race conditions on macOS when tearing down and re-configuring an NEPacketTunnelProvider. My goal is to handle switching out one VPN profile for another identical/near identical one (I'll add some context for this below). The flow that I have tested was to wait for the NEVPNStatusDidChange notification to report a NEVPNStatus.disconnected state, and then start the process of re-configuring the VPN with a new profile. In practice however, I have noticed that I must wait a couple of seconds between NEVPNStatus.disconnected state being reported and setting up a new tunnel. Otherwise, the system routing table gets messed up but the VPN reports being in NEVPNStatus.connected state, resulting in a tunnel that appears healthy but can't be accessed. With this, I wanted to ask if you have any suggestions on any OS items I can observer, in order to deterministically know that the system has fully cleaned up my packet tunnel, and that I am safe to configure another? This would be much more optimal than a hard-coded delay. Additional context: Jamf is a common solution for deploying MDM configuration profiles. However, in my tests, it doesn't support Apple's recommended approach of using the PayloadIdentifier to mark profiles for replacement, as PayloadIdentifiers are automatically updated to match the PayloadUUID of that same profile on upload. Although given what I've observed, I'm not sure the Apple recommended approach would work here in any case. Additionally, it would be nice to transition from non-MDM to MDM cleanly, however, this also requires an indeterminate wait time between the non-MDM configuration being disconnected and subsequently removed, and the MDM one being configured. With these scenarios, we need to be able to add a second configuration, with possibly identical VPN settings, then remove the old one, allowing the system to transition to the new configuration. For the MDM case, the pattern I've noticed on the system is that when the current profile is suddenly deleted, the connection will go into disconnected state, then NEVPNConfigurationChange will fire. The new profile can be configured from NEVPNConfigurationChange, however some time is needed to avoid races. For non-MDM, I had experimented with an approach of polling for MDM configurations appearing. When they do, I'd remove my previous notification observers, and set up a new NEVPNStatusDidChange notification observer, to remove the non-MDM VPN configuration after. it enters a disconnected state. Following the removal, I would call a function to reconfigure the VPN with new configuration. When this logic is in place, the call to stopVPNTunnel() is made. Again, a hardcoded delay is required between stopping and removing the old configuration and setting up a new one. Thanks!
Replies
3
Boosts
0
Views
137
Activity
Jan ’26
nesessionmanager “Resetting VPN On Demand” after sleep/wake
We’re developing an enterprise VPN client for macOS using NetworkExtension (PacketTunnelProvider) with Always-On / On-Demand VPN, deployed via MDM. On macOS 14.x and 15.x we observe the following log message from nesessionmanager: nesessionmanager: NESMVPNSession[...] Resetting VPN On Demand This most commonly occurs after sleep → wake. After this happens, the VPN no longer reconnects automatically, even though isOnDemandEnabled remains true and On-Demand rules are still present. Then a manual user action is required to reconnect. Questions: Is the “Resetting VPN On Demand” log message expected during sleep/wake transitions? Under what conditions does macOS reset On-Demand VPN state? Is there a supported way to detect or recover from this state programmatically? Any guidance on expected behavior or best practices would be appreciated.
Replies
1
Boosts
0
Views
139
Activity
Jan ’26
DNS Proxy system extension – OSSystemExtensionErrorDomain error 9 “validationFailed” on clean macOS machine
Hi, I’m implementing a macOS DNS Proxy as a system extension and running into a persistent activation error: OSSystemExtensionErrorDomain error 9 (validationFailed) with the message: extension category returned error This happens both on an MDM‑managed Mac and on a completely clean Mac (no MDM, fresh install). Setup macOS: 15.x (clean machine, no MDM) Xcode: 16.x Team ID: AAAAAAA111 (test) Host app bundle ID: com.example.agent.NetShieldProxy DNS Proxy system extension bundle ID: com.example.agent.NetShieldProxy.dnsProxy The DNS Proxy is implemented as a NetworkExtension system extension, not an app extension. Host app entitlements From codesign -d --entitlements :- /Applications/NetShieldProxy.app: xml com.apple.application-identifier AAAAAAA111.com.example.agent.NetShieldProxy <key>com.apple.developer.system-extension.install</key> <true/> <key>com.apple.developer.team-identifier</key> <string>AAAAAAA111</string> <key>com.apple.security.app-sandbox</key> <true/> <key>com.apple.security.application-groups</key> <array> <string>group.com.example.NetShieldmac</string> </array> <key>com.apple.security.files.user-selected.read-only</key> <true/> xml com.apple.application-identifier AAAAAAA111.com.example.agent.NetShieldProxy.dnsProxy <key>com.apple.developer.networking.networkextension</key> <array> <string>dns-proxy-systemextension</string> </array> <key>com.apple.developer.team-identifier</key> <string>AAAAAAA111</string> <key>com.apple.security.application-groups</key> <array> <string>group.com.example.NetShieldmac</string> <string>group.example.NetShieldmac</string> <string>group.example.agent.enterprise.macos</string> <string>group.example.com.NetShieldmac</string> </array> DNS Proxy system extension Info.plist On the clean Mac, from: bash plutil -p "/Applications/NetShieldProxy.app/Contents/Library/SystemExtensions/com.example.agent.NetShieldProxy.dnsProxy.systemextension/Contents/Info.plist" I get: json { "CFBundleExecutable" => "com.example.agent.NetShieldProxy.dnsProxy", "CFBundleIdentifier" => "com.example.agent.NetShieldProxy.dnsProxy", "CFBundleName" => "com.example.agent.NetShieldProxy.dnsProxy", "CFBundlePackageType" => "SYSX", "CFBundleShortVersionString" => "1.0.1.8", "CFBundleSupportedPlatforms" => [ "MacOSX" ], "CFBundleVersion" => "0.1.1", "LSMinimumSystemVersion" => "13.5", "NSExtension" => { "NSExtensionPointIdentifier" => "com.apple.dns-proxy", "NSExtensionPrincipalClass" => "com_example_agent_NetShieldProxy_dnsProxy.DNSProxyProvider" }, "NSSystemExtensionUsageDescription" => "SYSTEM_EXTENSION_USAGE_DESCRIPTION" } The DNSProxyProvider class inherits from NEDNSProxyProvider and is built in the system extension target. Activation code In the host app, I use: swift import SystemExtensions final class SystemExtensionActivator: NSObject, OSSystemExtensionRequestDelegate { private let extensionIdentifier = "com.example.agent.NetShieldProxy.dnsProxy" func activate(completion: @escaping (Bool) -> Void) { let request = OSSystemExtensionRequest.activationRequest( forExtensionWithIdentifier: extensionIdentifier, queue: .main ) request.delegate = self OSSystemExtensionManager.shared.submitRequest(request) } func request(_ request: OSSystemExtensionRequest, didFailWithError error: Error) { let nsError = error as NSError print("Activation failed:", nsError) } func request(_ request: OSSystemExtensionRequest, didFinishWithResult result: OSSystemExtensionRequest.Result) { print("Result:", result.rawValue) } } Runtime behavior on a clean Mac (no MDM) config.plist is created under /Library/Application Support/NetShield (via a root shell script). A daemon runs, contacts our backend, and writes /Library/Application Support/NetShield/state.plist with a valid dnsToken and other fields. The app NetShieldProxy.app is installed via a notarized, stapled Developer ID .pkg. The extension bundle is present at: /Applications/NetShieldProxy.app/Contents/Library/SystemExtensions/com.example.agent.NetShieldProxy.dnsProxy.systemextension. When I press Activate DNS Proxy in the UI, I see in the unified log: text NetShieldProxy: [com.example.agent:SystemExtensionActivator] Requesting activation for system extension: com.example.agent.NetShieldProxy.dnsProxy NetShieldProxy: [com.example.agent:SystemExtensionActivator] SystemExtensionActivator - activation failed: extension category returned error (domain=OSSystemExtensionErrorDomain code=9) NetShieldProxy: [com.example.agent:SystemExtensionActivator] SystemExtensionActivator - OSSystemExtensionError code enum: 9 NetShieldProxy: [com.example.agent:SystemExtensionActivator] SystemExtensionActivator - validationFailed And: bash systemextensionsctl list -> 0 extension(s) There is no prompt in Privacy & Security on this clean Mac. Question Given: The extension is packaged as a system extension (CFBundlePackageType = SYSX) with NSExtensionPointIdentifier = "com.apple.dns-proxy". Host and extension share the same Team ID and Developer ID Application cert. Entitlements on the target machine match the provisioning profile and Apple’s docs for DNS Proxy system extensions (dns-proxy-systemextension). This is happening on a clean Mac with no MDM profiles at all. What are the likely reasons for OSSystemExtensionErrorDomain error 9 (validationFailed) with "extension category returned error" in this DNS Proxy system extension scenario? Is there any additional configuration required for DNS Proxy system extensions (beyond entitlements and Info.plist) that could trigger this category-level validation failure? Any guidance or examples of a working DNS Proxy system extension configuration (host entitlements + extension Info.plist + entitlements) would be greatly appreciated. Thanks!
Replies
9
Boosts
0
Views
420
Activity
Jan ’26
When updating a VPN app with `includeAllNetworks`, the newer instance of the packet tunnel is not started via on-demand rules
When installing a new version the app while a tunnel is connected, seemingly the old packet tunnel process gets stopped but the new one does not come back up. Reportedly, a path monitor is reporting that the device has no connectivity. Is this the expected behavior? When installing an update from TestFlight or the App store, the packet tunnel instance from the old tunnel is stopped, but, due to the profile being on-demand and incldueAllNetworks, the path monitoring believes the device has no connectivity - so the new app is never downloaded. Is this the expected behavior? During development, the old packet tunnel gets stopped, the new app is installed, but the new packet tunnel is never started. To start it, the user has to toggle the VPN twice from the Settings app. The tunnel could be started from the VPN app too, if we chose to not take the path monitor into account, but then the user still needs to attempt to start the tunnel twice - it only works on the second try. As far as we can tell, the first time around, the packet tunnel never gets started, the app receives an update about NEVPNStatus being set to disconnecting yet NEVPNConnection does not throw. The behavior I was naively expecting was that the packet tunnel process would be stopped only when the new app is fully downloaded and when the update is installed, Are we doing something horribly wrong here?
Replies
7
Boosts
3
Views
650
Activity
Jan ’26