TLS Inspection with MITM Proxy setup for System Extension app in macOS

Hi All,

I am working on a macOS System Extension using Apple’s Network Extension Framework, designed to observe and log network activity at multiple layers. The system extension is currently stable and working as expected for HTTP and DNS traffic with 3 providers, getting Socket, HTTP, and DNS logs.


Current Architecture Overview

The project consists of two Xcode targets:

1. Main App Process

  • Responsible for:

    • Managing system extension lifecycle (activation, configuration)
    • Establishing IPC (XPC) communication with extensions
    • Receiving structured logs from extensions
    • Writing logs efficiently to disk using a persistent file handle
  • Uses:

    • OSSystemExtensionManager
    • NEFilterManager, NETransparentProxyManager, NEDNSProxyManager
    • NWPathMonitor for network availability handling
    • Persistent logging mechanism (FileHandle)

2. System Extension Process

Contains three providers, all running within a single system extension process:

a) Content Filter (NEFilterDataProvider)

  • Captures socket-level metadata

  • Extracts:

    • PID via audit token
    • Local/remote endpoints
    • Protocol (TCP/UDP, IPv4/IPv6)
    • Direction (inbound/outbound)
  • Sends structured JSON logs via shared IPC


b) Transparent Proxy (NETransparentProxyProvider)

  • Intercepts TCP flows
  • Creates a corresponding NWConnection to the destination
  • Captures both HTTP and HTTPS traffic, sends it to HTTPFlowLogger file which bypasses if it's not HTTP traffic.
  • Uses a custom HTTPFlowLogger:
    • Built using SwiftNIO library (NIO HTTP1)
    • Parses up to HTTP/1.1 traffic
    • Handles streaming, headers, and partial body capture (with size limits)
  • Maintains per-flow state and lifecycle management
  • Logs structured HTTP data via shared IPC

c) DNS Proxy (NEDNSProxyProvider)

  • Intercepts UDP DNS traffic
  • Forwards queries to upstream resolver (system DNS or fallback)
  • Maintains shared UDP connection
  • Tracks pending requests using DNS IDs
  • Parses DNS packets (queries + responses) using a custom parser
  • Logs structured DNS metadata via shared IPC

Shared Component: IPCConnection

  • Single bidirectional XPC channel used by all providers

  • Handles:

    • App → Extension registration
    • Extension → App logging
  • Uses Mach service defined in system extension entitlements


Project Structure

NetworkExtension (Project)
│
├── NetworkExtension (Target 1: Main App)
│   ├── AppDelegate.swift
│   ├── Assets.xcassets
│   ├── Info.plist
│   ├── NetworkExtension.entitlements
│   ├── Main.storyboard
│   └──ViewController.swift
│
├── SystemExtensions (Target 2: Extension Process)
│  ├──  common/
│   │    ├──  IPCConnection.swift
│   │    └──  main.swift
│   │
│   ├── DNSProxyProvider/
│   │   ├──DNSDataParser.swift
│   │   └──DNSProxyProvider.swift (DNS Proxy)
│   │
│   ├──  FilterDataProvider/
│   │   └── FilterDataProvider.swift
│   │
│   ├──  TransparentProxyProvider/
│   │   ├── HTTPLogParser.swift
│   │   ├── LogDataModel.swift
│   │   └──TransparentProxyProvider.swift
│   │
│   ├── Info.plist
│   └── SystemExtensions.entitlements
│

Current Capabilities

  • Unified logging pipeline across:

    • Socket-level metadata
    • HTTP traffic (HTTP/1.1)
    • DNS queries/responses
  • Efficient log handling using persistent file descriptors

  • Stable IPC communication between app and extensions

  • Flow-level tracking and lifecycle management

  • Selective filtering (e.g., bypass rules for specific IPs)


What's the best approach to add TLS Inspection with MITM proxy setup?

Some context and constraints:

  • Existing implementation handles HTTP parsing and should remain unchanged (Swift-based).
  • I’m okay with bypassing apps/sites that use certificate pinning (e.g., banking apps) and legitimate sites.
  • Performance is important — I want to avoid high CPU utilization.
  • I’m relatively new to TLS inspection and MITM proxy design.

Questions

  1. Is it a good idea to implement TLS inspection within a system extension, or does that typically introduce significant complexity and performance overhead?
  2. As NETransparentProxyProvider already intercepting HTTPS traffic, can we redirect it to a separate processing pipeline (e.g., another file/module), while keeping the existing HTTP parser(HTTPFlowLogger - HTTP only parser) intact?
  3. What are the recommended architectural approaches for adding HTTPS parsing via MITM in a performant way?
  4. Are there best practices for selectively bypassing pinned or sensitive domains while still inspecting other traffic?
  5. Any guidance on avoiding common pitfalls (e.g., certificate handling, connection reuse, latency issues)?

I’m looking for a clean, maintainable approach to integrate HTTPS inspection into my existing system without unnecessary complexity or performance degradation.

Please let me know if any additional details from my side would help in suggesting the most appropriate approach.

Thanks in advance for your time and insights—I really appreciate it.

Answered by DTS Engineer in 885764022
I’m referring to a MITM proxy in the generic sense

OK, cool. mitmproxy is super cool, but running that code in a system extension would present some challenges.

With that out of the way, let’s return to your original questions:

1- Is it a good idea to implement TLS inspection within a system extension … ?

Yes and no.

TLS inspection itself has significant drawbacks, so it’s hard to say that it’s a good idea overall. But if you’re going to implement it then using an NE transparent proxy is a reasonable way to do it. Transparent proxies do have their sharp edges, but the only alternative is to use a traditional proxy and that requires cooperation from the apps involved [1].

2- As NETransparentProxyProvider already intercepting HTTPS traffic, can we redirect it to a separate processing pipeline … while keeping the existing HTTP parser … intact?

I’m not sure I understand this question, but it seems to be about the internal architecture of your transparent proxy, and NE puts very few constraints on that. If, for example, you want process HTTP flows using one module within your product and HTTPS flows using another, that’s really your business.

3- What are the recommended architectural approaches for adding HTTPS parsing via MITM in a performant way?

Apple doesn’t have APIs for that. NE gives your transparent proxy TCP and UDP flows. Parsing the content of those flows is up to you.

Keep in mind that the world has moved on from HTTP/1. A good TLS inspection product should support HTTP/2 + TLS + TCP and HTTP/3 + QUIC + DTLS + UDP. Apple has APIs for these if you’re building a client, namely URLSession, but no APIs for implement TLS inspection. You’ll have to write or acquire your own library for implementing them, or force all connections to downgrade to HTTP/1 + TLS, which brings its own compatibility risks.

4- Are there best practices for selectively bypassing pinned or sensitive domains while still inspecting other traffic?

Again, that’s up to you. NE provides you with various mechanics, but you then have to decide how to apply those mechanics to meet your own goals.

On the subject of mechanics, there’s trade-off between features and compatibility here:

  • The way to get the best compatibility and performance is not have a transparent proxy at all (-:
  • Presuming that you do decide to create one, then ideally you should use the tunnel settings (NETransparentProxyNetworkSettings) to minimise the number of flows that get routed to your proxy.
  • If the system sends a flow to your proxy, you should try to identify the flow in your handle-new-flow method and return false if you don’t want to handle it.
  • Once you return true, there’s no going back. You become responsible for that flow.

This is a challenge because:

  • The tunnel settings are very coarse-grained.
  • You might find that the metadata you need to decide whether you want to handle a flow is not present when the flow arrives at your handle-new-flow method.

Combined, it means you often end up handling flows that you don’t really want to handle, and that impacts both compatibility and performance.

5- Any guidance on avoiding common pitfalls … ?

TLS inspection requires that the user install a trusted anchor, that is, a root certificate that’s trusted system wide. You can’t fully automate that. The user has to be involved. (Unless your product is installed via MDM, in which case the device manager can install the trusted anchor for you.)

Debugging NE providers is tricky. I have advice on how to approach that in Debugging a Network Extension Provider.

And lots more NE advice linked to from Network Extension Resources.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Apps that use Apple’s recommended networking APIs, URLSession and Network framework, get proxy support for free, but not all apps use those APIs.

I want to start by clarifying your use of the term “MITM Proxy”. Are you using that generically? Or specifically referencing the mitmproxy [1] open source project?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] https://www.mitmproxy.org/

Hi @DTS Engineer

I’m referring to a MITM proxy in the generic sense, not the mitmproxy project. I’m also unsure whether mitmproxy can be cleanly integrated into an existing all-Swift codebase. Additionally, would this approach require bringing in external libraries such as SwiftNIO SSL?

Overall, I’m looking for guidance on choosing the right approach to start with. And if possible, some more detailed steps and resources that'll help me here as it's my first time working with this kind of task.

Thanks a lot

I’m referring to a MITM proxy in the generic sense

OK, cool. mitmproxy is super cool, but running that code in a system extension would present some challenges.

With that out of the way, let’s return to your original questions:

1- Is it a good idea to implement TLS inspection within a system extension … ?

Yes and no.

TLS inspection itself has significant drawbacks, so it’s hard to say that it’s a good idea overall. But if you’re going to implement it then using an NE transparent proxy is a reasonable way to do it. Transparent proxies do have their sharp edges, but the only alternative is to use a traditional proxy and that requires cooperation from the apps involved [1].

2- As NETransparentProxyProvider already intercepting HTTPS traffic, can we redirect it to a separate processing pipeline … while keeping the existing HTTP parser … intact?

I’m not sure I understand this question, but it seems to be about the internal architecture of your transparent proxy, and NE puts very few constraints on that. If, for example, you want process HTTP flows using one module within your product and HTTPS flows using another, that’s really your business.

3- What are the recommended architectural approaches for adding HTTPS parsing via MITM in a performant way?

Apple doesn’t have APIs for that. NE gives your transparent proxy TCP and UDP flows. Parsing the content of those flows is up to you.

Keep in mind that the world has moved on from HTTP/1. A good TLS inspection product should support HTTP/2 + TLS + TCP and HTTP/3 + QUIC + DTLS + UDP. Apple has APIs for these if you’re building a client, namely URLSession, but no APIs for implement TLS inspection. You’ll have to write or acquire your own library for implementing them, or force all connections to downgrade to HTTP/1 + TLS, which brings its own compatibility risks.

4- Are there best practices for selectively bypassing pinned or sensitive domains while still inspecting other traffic?

Again, that’s up to you. NE provides you with various mechanics, but you then have to decide how to apply those mechanics to meet your own goals.

On the subject of mechanics, there’s trade-off between features and compatibility here:

  • The way to get the best compatibility and performance is not have a transparent proxy at all (-:
  • Presuming that you do decide to create one, then ideally you should use the tunnel settings (NETransparentProxyNetworkSettings) to minimise the number of flows that get routed to your proxy.
  • If the system sends a flow to your proxy, you should try to identify the flow in your handle-new-flow method and return false if you don’t want to handle it.
  • Once you return true, there’s no going back. You become responsible for that flow.

This is a challenge because:

  • The tunnel settings are very coarse-grained.
  • You might find that the metadata you need to decide whether you want to handle a flow is not present when the flow arrives at your handle-new-flow method.

Combined, it means you often end up handling flows that you don’t really want to handle, and that impacts both compatibility and performance.

5- Any guidance on avoiding common pitfalls … ?

TLS inspection requires that the user install a trusted anchor, that is, a root certificate that’s trusted system wide. You can’t fully automate that. The user has to be involved. (Unless your product is installed via MDM, in which case the device manager can install the trusted anchor for you.)

Debugging NE providers is tricky. I have advice on how to approach that in Debugging a Network Extension Provider.

And lots more NE advice linked to from Network Extension Resources.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Apps that use Apple’s recommended networking APIs, URLSession and Network framework, get proxy support for free, but not all apps use those APIs.

TLS Inspection with MITM Proxy setup for System Extension app in macOS
 
 
Q