From 35196f8f714d4ab07ebb211802693d03385486ad Mon Sep 17 00:00:00 2001 From: Nimrod Gutman Date: Thu, 30 Apr 2026 14:04:49 +0300 Subject: [PATCH] fix(macos): clear accepted tls failures --- .../MacNodeModeCoordinatorTests.swift | 10 ++++---- .../OpenClawKit/GatewayTLSPinning.swift | 24 ++++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/apps/macos/Tests/OpenClawIPCTests/MacNodeModeCoordinatorTests.swift b/apps/macos/Tests/OpenClawIPCTests/MacNodeModeCoordinatorTests.swift index 0459ac1d8c2..339a9844b2a 100644 --- a/apps/macos/Tests/OpenClawIPCTests/MacNodeModeCoordinatorTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/MacNodeModeCoordinatorTests.swift @@ -31,16 +31,16 @@ struct MacNodeModeCoordinatorTests { } @Test func `tls pin store key uses default wss port`() throws { - let url = try #require(URL(string: "wss://gutsy-home.tail06a72.ts.net")) - #expect(MacNodeModeCoordinator.tlsPinStoreKey(for: url) == "gutsy-home.tail06a72.ts.net:443") + let url = try #require(URL(string: "wss://gateway.example.ts.net")) + #expect(MacNodeModeCoordinator.tlsPinStoreKey(for: url) == "gateway.example.ts.net:443") } @Test func `auto repairs trusted tailscale serve pin mismatch`() throws { - let url = try #require(URL(string: "wss://gutsy-home.tail06a72.ts.net")) + let url = try #require(URL(string: "wss://gateway.example.ts.net")) let failure = GatewayTLSValidationFailure( kind: .pinMismatch, - host: "gutsy-home.tail06a72.ts.net", - storeKey: "gutsy-home.tail06a72.ts.net:443", + host: "gateway.example.ts.net", + storeKey: "gateway.example.ts.net:443", expectedFingerprint: "old", observedFingerprint: "new", systemTrustOk: true) diff --git a/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayTLSPinning.swift b/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayTLSPinning.swift index 342ab1935cb..8bbbe494f17 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayTLSPinning.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayTLSPinning.swift @@ -184,6 +184,12 @@ public final class GatewayTLSPinningSession: NSObject, WebSocketSessioning, URLS self.failureLock.unlock() } + private func clearTLSFailure() { + self.failureLock.lock() + self.lastTLSFailure = nil + self.failureLock.unlock() + } + public func makeWebSocketTask(url: URL) -> WebSocketTaskBox { let task = self.session.webSocketTask(with: url) task.maximumMessageSize = 16 * 1024 * 1024 @@ -205,9 +211,11 @@ public final class GatewayTLSPinningSession: NSObject, WebSocketSessioning, URLS let host = challenge.protectionSpace.host let systemTrustOk = SecTrustEvaluateWithError(trust, nil) let expected = self.params.expectedFingerprint.map(normalizeFingerprint) - if let fingerprint = certificateFingerprint(trust) { + let fingerprint = certificateFingerprint(trust) + if let fingerprint { if let expected { if fingerprint == expected { + self.clearTLSFailure() completionHandler(.useCredential, URLCredential(trust: trust)) } else { self.recordTLSFailure(GatewayTLSValidationFailure( @@ -225,28 +233,22 @@ public final class GatewayTLSPinningSession: NSObject, WebSocketSessioning, URLS if let storeKey = params.storeKey { GatewayTLSStore.saveFingerprint(fingerprint, stableID: storeKey) } + self.clearTLSFailure() completionHandler(.useCredential, URLCredential(trust: trust)) return } - } else { - self.recordTLSFailure(GatewayTLSValidationFailure( - kind: .certificateUnavailable, - host: host, - storeKey: self.params.storeKey, - expectedFingerprint: expected, - observedFingerprint: nil, - systemTrustOk: systemTrustOk)) } if systemTrustOk || !self.params.required { + self.clearTLSFailure() completionHandler(.useCredential, URLCredential(trust: trust)) } else { self.recordTLSFailure(GatewayTLSValidationFailure( - kind: .untrustedCertificate, + kind: fingerprint == nil ? .certificateUnavailable : .untrustedCertificate, host: host, storeKey: self.params.storeKey, expectedFingerprint: expected, - observedFingerprint: nil, + observedFingerprint: fingerprint, systemTrustOk: false)) completionHandler(.cancelAuthenticationChallenge, nil) }