From 8484f0f04befbf11fa7cae21fdb992601ae8db49 Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Thu, 24 Sep 2026 11:03:24 -0700 Subject: [PATCH 1/2] fix(connection): recheck the wslrelay listener after the guest probe A cached wslrelay proof waited on the guest and then allowed a shared token without reading the Windows listeners again. Accept the credential only when that snapshot is still the same relay. - Call ListenerSnapshotStillCurrent after IsExpectedWslGatewayListening - Fail closed when the listener is replaced during the guest probe Signed-off-by: Sebastien Tardif --- ...anagedLocalGatewayPortProvenanceService.cs | 5 ++- ...dLocalGatewayPortProvenanceServiceTests.cs | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/OpenClaw.Connection/ManagedLocalGatewayPortProvenanceService.cs b/src/OpenClaw.Connection/ManagedLocalGatewayPortProvenanceService.cs index 5222c04bbb..25e7824f20 100644 --- a/src/OpenClaw.Connection/ManagedLocalGatewayPortProvenanceService.cs +++ b/src/OpenClaw.Connection/ManagedLocalGatewayPortProvenanceService.cs @@ -358,7 +358,10 @@ cached.ProcessStartTimeUtc is not DateTime expectedStart || return false; } - return _platform.IsExpectedWslGatewayListening(managedDistroName, uri.Port); + if (!_platform.IsExpectedWslGatewayListening(managedDistroName, uri.Port)) + return false; + + return ListenerSnapshotStillCurrent(current, uri); } private GatewayEndpointProvenance InspectCore(GatewayRecord record) diff --git a/tests/OpenClaw.Connection.Tests/ManagedLocalGatewayPortProvenanceServiceTests.cs b/tests/OpenClaw.Connection.Tests/ManagedLocalGatewayPortProvenanceServiceTests.cs index cccca65068..4dd85bfd2b 100644 --- a/tests/OpenClaw.Connection.Tests/ManagedLocalGatewayPortProvenanceServiceTests.cs +++ b/tests/OpenClaw.Connection.Tests/ManagedLocalGatewayPortProvenanceServiceTests.cs @@ -381,6 +381,27 @@ public void InteractiveCredentialGate_GuestSocketOwnershipChanges_FailsClosed() Assert.False(service.IsStrongCredentialAllowed(gateway, credential)); } + [Fact] + public void InteractiveCredentialGate_CachedWslRelayReplacedDuringGuestProbe_FailsClosed() + { + var platform = new FakePlatform(); + var start = new DateTime(2026, 7, 24, 1, 0, 0, DateTimeKind.Utc); + platform.Listeners.Add(new WindowsTcpListenerInfo( + IPAddress.Loopback, 18789, 102, "wslrelay", + @"C:\Program Files\WSL\wslrelay.exe", start)); + var service = new ManagedLocalGatewayPortProvenanceService(platform, NullLogger.Instance); + var gateway = ManagedRecord(); + var credential = new GatewayCredential( + "shared-token", false, CredentialResolver.SourceSharedGatewayToken); + Assert.Equal(GatewayEndpointProvenanceKind.ExpectedManagedGateway, service.Inspect(gateway).Kind); + + Assert.True(service.IsStrongCredentialAllowed(gateway, credential)); + + platform.ReplaceListenerDuringGuestProof = true; + + Assert.False(service.IsStrongCredentialAllowed(gateway, credential)); + } + [Fact] public void InteractiveCredentialGate_RelaylessProofRevalidatesEmptySnapshotAndGuestOwner() { @@ -561,6 +582,7 @@ private sealed class FakePlatform : IManagedLocalGatewayPortPlatform public bool ReplaceProcessIdentityOnEnd { get; set; } public bool ReplaceOwnerOnSecondCapture { get; set; } public bool IntroduceUnknownListenerDuringGuestProof { get; set; } + public bool ReplaceListenerDuringGuestProof { get; set; } public bool Ipv4Complete { get; set; } = true; public bool Ipv6Complete { get; set; } = true; public int TrustedWslRelayChecks { get; private set; } @@ -648,6 +670,19 @@ public bool IsExpectedWslGatewayListening(string distroName, int port) "unknown", @"C:\unknown.exe")); } + + if (ReplaceListenerDuringGuestProof && Listeners.Count > 0) + { + ReplaceListenerDuringGuestProof = false; + Listeners[0] = new WindowsTcpListenerInfo( + Listeners[0].Address, + port, + 999, + "unknown", + @"C:\unknown.exe", + Listeners[0].ProcessStartTimeUtc?.AddSeconds(1)); + } + return ExpectedDistroListening; } public string? ReadScheduledTaskXml(string taskName) => TaskXml; From e9da0e8102b27fd518b3cf0f7ec40e39d2d13cf2 Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Thu, 1 Oct 2026 09:32:48 -0700 Subject: [PATCH 2/2] ci: retrigger after revocation MCP startup timeout Signed-off-by: Sebastien Tardif