mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:10:44 +00:00
fix(android): require private IP cleartext pairing
This commit is contained in:
@@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Pairing/security: require private-IP or loopback hosts for cleartext mobile pairing, and stop treating `.local` or dotless hostnames as safe cleartext endpoints. (#70721) Thanks @vincentkoc.
|
||||
- Approvals/security: require explicit chat exec-approval enablement instead of auto-enabling approval clients just because approvers resolve from config or owner allowlists. (#70715) Thanks @vincentkoc.
|
||||
- Discord/security: keep native slash-command channel policy from bypassing configured owner or member restrictions, while preserving channel-policy fallback when no stricter access rule exists. (#70711) Thanks @vincentkoc.
|
||||
- Android/security: stop `ASK_OPENCLAW` intents from auto-sending injected prompts, so external app actions only prefill the draft instead of dispatching it immediately. (#70714) Thanks @vincentkoc.
|
||||
|
||||
@@ -63,8 +63,6 @@ internal fun isPrivateLanGatewayHost(
|
||||
}
|
||||
if (host.isEmpty()) return false
|
||||
if (isLoopbackGatewayHost(host, allowEmulatorBridgeAlias = allowEmulatorBridgeAlias)) return true
|
||||
if (host.endsWith(".local")) return true
|
||||
if (!host.contains('.') && !host.contains(':')) return true
|
||||
|
||||
parseIpv4Address(host)?.let { ipv4 ->
|
||||
val first = ipv4[0].toInt() and 0xff
|
||||
|
||||
@@ -240,9 +240,9 @@ class ConnectionManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isPrivateLanGatewayHost_acceptsLanHostsButRejectsTailnetHosts() {
|
||||
fun isPrivateLanGatewayHost_acceptsLanIpsButRejectsMdnsAndTailnetHosts() {
|
||||
assertTrue(isPrivateLanGatewayHost("192.168.1.20"))
|
||||
assertTrue(isPrivateLanGatewayHost("gateway.local"))
|
||||
assertFalse(isPrivateLanGatewayHost("gateway.local"))
|
||||
assertFalse(isPrivateLanGatewayHost("100.64.0.9"))
|
||||
assertFalse(isPrivateLanGatewayHost("gateway.tailnet.ts.net"))
|
||||
}
|
||||
|
||||
@@ -114,18 +114,10 @@ class GatewayConfigResolverTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseGatewayEndpointAllowsMdnsCleartextWsUrls() {
|
||||
fun parseGatewayEndpointRejectsMdnsCleartextWsUrls() {
|
||||
val parsed = parseGatewayEndpoint("ws://gateway.local:18789")
|
||||
|
||||
assertEquals(
|
||||
GatewayEndpointConfig(
|
||||
host = "gateway.local",
|
||||
port = 18789,
|
||||
tls = false,
|
||||
displayUrl = "http://gateway.local:18789",
|
||||
),
|
||||
parsed,
|
||||
)
|
||||
assertNull(parsed)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -434,21 +434,6 @@ describe("pairing setup code", () => {
|
||||
urlSource: "gateway.bind=custom",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "allows mdns hostname cleartext setup urls",
|
||||
config: {
|
||||
gateway: {
|
||||
bind: "custom",
|
||||
customBindHost: "gateway.local",
|
||||
auth: { mode: "token", token: "tok_123" },
|
||||
},
|
||||
} satisfies ResolveSetupConfig,
|
||||
expected: {
|
||||
authLabel: "token",
|
||||
url: "ws://gateway.local:18789",
|
||||
urlSource: "gateway.bind=custom",
|
||||
},
|
||||
},
|
||||
] as const)("$name", async ({ config, options, expected }) => {
|
||||
await expectResolvedSetupSuccessCase({
|
||||
config,
|
||||
@@ -469,6 +454,17 @@ describe("pairing setup code", () => {
|
||||
} satisfies ResolveSetupConfig,
|
||||
expectedError: "Tailscale and public mobile pairing require a secure gateway URL",
|
||||
},
|
||||
{
|
||||
name: "rejects mdns hostname cleartext setup urls",
|
||||
config: {
|
||||
gateway: {
|
||||
bind: "custom",
|
||||
customBindHost: "gateway.local",
|
||||
auth: { mode: "token", token: "tok_123" },
|
||||
},
|
||||
} satisfies ResolveSetupConfig,
|
||||
expectedError: "private LAN IP address",
|
||||
},
|
||||
{
|
||||
name: "rejects tailnet bind remote ws setup urls for mobile pairing",
|
||||
config: {
|
||||
|
||||
@@ -6,7 +6,6 @@ import { materializeGatewayAuthSecretRefs } from "../gateway/auth-config-utils.j
|
||||
import { assertExplicitGatewayAuthModeWhenBothConfigured } from "../gateway/auth-mode-policy.js";
|
||||
import { isLoopbackHost, isSecureWebSocketUrl } from "../gateway/net.js";
|
||||
import { issueDeviceBootstrapToken } from "../infra/device-bootstrap.js";
|
||||
import { normalizeHostname } from "../infra/net/hostname.js";
|
||||
import {
|
||||
pickMatchingExternalInterfaceAddress,
|
||||
safeNetworkInterfaces,
|
||||
@@ -75,20 +74,12 @@ function describeSecureMobilePairingFix(source?: string): string {
|
||||
return (
|
||||
"Tailscale and public mobile pairing require a secure gateway URL (wss://) or Tailscale Serve/Funnel." +
|
||||
sourceNote +
|
||||
" Fix: use a private LAN host/address, prefer gateway.tailscale.mode=serve, or set " +
|
||||
" Fix: use a private LAN IP address, prefer gateway.tailscale.mode=serve, or set " +
|
||||
"gateway.remote.url / plugins.entries.device-pair.config.publicUrl to a wss:// URL. " +
|
||||
"ws:// is only valid for localhost, private LAN, or the Android emulator."
|
||||
"ws:// is only valid for localhost, private LAN IP addresses, or the Android emulator."
|
||||
);
|
||||
}
|
||||
|
||||
function isPrivateLanHostname(host: string): boolean {
|
||||
const normalized = normalizeHostname(host);
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
return normalized.endsWith(".local") || (!normalized.includes(".") && !normalized.includes(":"));
|
||||
}
|
||||
|
||||
function isPrivateLanIpHost(host: string): boolean {
|
||||
if (isRfc1918Ipv4Address(host)) {
|
||||
return true;
|
||||
@@ -111,12 +102,7 @@ function isPrivateLanIpHost(host: string): boolean {
|
||||
}
|
||||
|
||||
function isMobilePairingCleartextAllowedHost(host: string): boolean {
|
||||
return (
|
||||
isLoopbackHost(host) ||
|
||||
host === "10.0.2.2" ||
|
||||
isPrivateLanIpHost(host) ||
|
||||
isPrivateLanHostname(host)
|
||||
);
|
||||
return isLoopbackHost(host) || host === "10.0.2.2" || isPrivateLanIpHost(host);
|
||||
}
|
||||
|
||||
function validateMobilePairingUrl(url: string, source?: string): string | null {
|
||||
|
||||
Reference in New Issue
Block a user