Add iPad support, auto-pinning, and comprehensive logging

- Adaptive iPhone/iPad layout with NavigationSplitView sidebar
- Auto-detect SSL-pinned domains, fall back to passthrough
- Certificate install via local HTTP server (Safari profile flow)
- App Group-backed CA, per-domain leaf cert LRU cache
- DB-backed config repository, Darwin notification throttling
- Rules engine, breakpoint rules, pinned domain tracking
- os.Logger instrumentation across tunnel/proxy/mitm/capture/cert/rules/db/ipc/ui
- Fix dyld framework embed, race conditions, thread safety
This commit is contained in:
Trey t
2026-04-11 12:52:18 -05:00
parent c77e506db5
commit 148bc3887c
77 changed files with 6710 additions and 847 deletions

View File

@@ -117,6 +117,26 @@ extension CapturedTraffic {
return dict
}
public func requestHeaderValue(named name: String) -> String? {
HTTPBodyDecoder.headerValue(named: name, in: decodedRequestHeaders)
}
public func responseHeaderValue(named name: String) -> String? {
HTTPBodyDecoder.headerValue(named: name, in: decodedResponseHeaders)
}
public var decodedResponseBodyData: Data? {
HTTPBodyDecoder.decodedBodyData(from: responseBody, headers: decodedResponseHeaders)
}
public var searchableResponseBodyText: String? {
HTTPBodyDecoder.searchableText(from: responseBody, headers: decodedResponseHeaders)
}
public var responseBodyDecodingHint: String {
HTTPBodyDecoder.decodingHint(for: responseBody, headers: decodedResponseHeaders)
}
public var decodedQueryParameters: [String: String] {
guard let data = queryParameters?.data(using: .utf8),
let dict = try? JSONDecoder().decode([String: String].self, from: data) else {

View File

@@ -0,0 +1,24 @@
import Foundation
import GRDB
/// A domain detected as using SSL pinning. MITM will automatically skip these
/// and fall back to passthrough mode.
public struct PinnedDomain: Codable, FetchableRecord, MutablePersistableRecord, Identifiable, Sendable {
public var id: Int64?
public var domain: String
public var reason: String?
public var detectedAt: Double
public static let databaseTableName = "pinned_domains"
public mutating func didInsert(_ inserted: InsertionSuccess) {
id = inserted.rowID
}
public init(id: Int64? = nil, domain: String, reason: String? = nil, detectedAt: Double = Date().timeIntervalSince1970) {
self.id = id
self.domain = domain
self.reason = reason
self.detectedAt = detectedAt
}
}

View File

@@ -0,0 +1,35 @@
import Foundation
import GRDB
public struct ProxyConfiguration: Codable, FetchableRecord, MutablePersistableRecord, Sendable {
public static let databaseTableName = "proxy_configuration"
public var id: Int64
public var sslProxyingEnabled: Bool
public var blockListEnabled: Bool
public var breakpointEnabled: Bool
public var noCachingEnabled: Bool
public var dnsSpoofingEnabled: Bool
public var hideSystemTraffic: Bool
public var updatedAt: Double
public init(
id: Int64 = 1,
sslProxyingEnabled: Bool = false,
blockListEnabled: Bool = false,
breakpointEnabled: Bool = false,
noCachingEnabled: Bool = false,
dnsSpoofingEnabled: Bool = false,
hideSystemTraffic: Bool = false,
updatedAt: Double = Date().timeIntervalSince1970
) {
self.id = id
self.sslProxyingEnabled = sslProxyingEnabled
self.blockListEnabled = blockListEnabled
self.breakpointEnabled = breakpointEnabled
self.noCachingEnabled = noCachingEnabled
self.dnsSpoofingEnabled = dnsSpoofingEnabled
self.hideSystemTraffic = hideSystemTraffic
self.updatedAt = updatedAt
}
}

View File

@@ -0,0 +1,61 @@
import Foundation
import GRDB
public enum ProxyRuntimeState: String, Codable, Sendable {
case stopped
case starting
case running
case failed
}
public struct ProxyRuntimeStatus: Codable, FetchableRecord, MutablePersistableRecord, Sendable {
public static let databaseTableName = "proxy_runtime_status"
public var id: Int64
public var tunnelState: String
public var proxyHost: String?
public var proxyPort: Int?
public var caFingerprint: String?
public var lastProxyError: String?
public var lastMITMError: String?
public var lastConnectError: String?
public var lastSuccessfulMITMDomain: String?
public var lastSuccessfulMITMAt: Double?
public var lastExtensionStartAt: Double?
public var lastExtensionStopAt: Double?
public var updatedAt: Double
public init(
id: Int64 = 1,
tunnelState: ProxyRuntimeState = .stopped,
proxyHost: String? = nil,
proxyPort: Int? = nil,
caFingerprint: String? = nil,
lastProxyError: String? = nil,
lastMITMError: String? = nil,
lastConnectError: String? = nil,
lastSuccessfulMITMDomain: String? = nil,
lastSuccessfulMITMAt: Double? = nil,
lastExtensionStartAt: Double? = nil,
lastExtensionStopAt: Double? = nil,
updatedAt: Double = Date().timeIntervalSince1970
) {
self.id = id
self.tunnelState = tunnelState.rawValue
self.proxyHost = proxyHost
self.proxyPort = proxyPort
self.caFingerprint = caFingerprint
self.lastProxyError = lastProxyError
self.lastMITMError = lastMITMError
self.lastConnectError = lastConnectError
self.lastSuccessfulMITMDomain = lastSuccessfulMITMDomain
self.lastSuccessfulMITMAt = lastSuccessfulMITMAt
self.lastExtensionStartAt = lastExtensionStartAt
self.lastExtensionStopAt = lastExtensionStopAt
self.updatedAt = updatedAt
}
public var state: ProxyRuntimeState {
ProxyRuntimeState(rawValue: tunnelState) ?? .stopped
}
}