mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 05:01:15 +00:00
Browser: normalize localhost absolute-form CDP hosts (#59236)
* Browser: normalize localhost absolute-form CDP hosts * CHANGELOG: note localhost absolute-form CDP fix --------- Co-authored-by: Jacob Tomlinson <jtomlinson@nvidia.com>
This commit is contained in:
@@ -53,6 +53,7 @@ Docs: https://docs.openclaw.ai
|
||||
- ACP/gateway reconnects: reject stale pre-ack ACP prompts after reconnect grace expiry so callers fail cleanly instead of hanging indefinitely when the gateway never confirms the run.
|
||||
- Providers/Copilot: classify native GitHub Copilot API hosts in the shared provider endpoint resolver and harden token-derived proxy endpoint parsing so Copilot base URL routing stays centralized and fails closed on malformed hints. Thanks @vincentkoc.
|
||||
- Gateway: prune empty `node-pending-work` state entries after explicit acknowledgments and natural expiry so the per-node state map no longer grows indefinitely. (#58179) Thanks @gavyngong.
|
||||
- Browser/CDP: normalize trailing-dot localhost absolute-form hosts before loopback checks so remote CDP websocket URLs like `ws://localhost.:...` rewrite back to the configured remote host. (#59236) Thanks @mappel-nv.
|
||||
|
||||
## 2026.4.1-beta.1
|
||||
|
||||
|
||||
@@ -320,6 +320,14 @@ describe("cdp", () => {
|
||||
expect(normalized).toBe("wss://user:pass@example.com/devtools/browser/ABC?token=abc");
|
||||
});
|
||||
|
||||
it("rewrites localhost absolute-form websocket URLs for remote CDP hosts", () => {
|
||||
const normalized = normalizeCdpWsUrl(
|
||||
"ws://localhost.:9222/devtools/browser/ABC",
|
||||
"https://user:pass@example.com?token=abc",
|
||||
);
|
||||
expect(normalized).toBe("wss://user:pass@example.com/devtools/browser/ABC?token=abc");
|
||||
});
|
||||
|
||||
it("rewrites 0.0.0.0 wildcard bind address to remote CDP host", () => {
|
||||
const normalized = normalizeCdpWsUrl(
|
||||
"ws://0.0.0.0:3000/devtools/browser/ABC",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { makeNetworkInterfacesSnapshot } from "../test-helpers/network-interfaces.js";
|
||||
import {
|
||||
isLocalishHost,
|
||||
isLoopbackHost,
|
||||
isPrivateOrLoopbackAddress,
|
||||
isPrivateOrLoopbackHost,
|
||||
isSecureWebSocketUrl,
|
||||
@@ -28,6 +29,7 @@ describe("isLocalishHost", () => {
|
||||
it("accepts loopback and tailscale serve/funnel host headers", () => {
|
||||
const accepted = [
|
||||
"localhost",
|
||||
"localhost.:18789",
|
||||
"127.0.0.1:18789",
|
||||
"[::1]:18789",
|
||||
"[::ffff:127.0.0.1]:18789",
|
||||
@@ -46,6 +48,13 @@ describe("isLocalishHost", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("isLoopbackHost", () => {
|
||||
it("accepts localhost absolute-form hostnames", () => {
|
||||
expect(isLoopbackHost("localhost.")).toBe(true);
|
||||
expect(isLoopbackHost("LOCALHOST...")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isTrustedProxyAddress", () => {
|
||||
it.each([
|
||||
{
|
||||
@@ -394,6 +403,7 @@ describe("isPrivateOrLoopbackAddress", () => {
|
||||
describe("isPrivateOrLoopbackHost", () => {
|
||||
it("accepts localhost", () => {
|
||||
expect(isPrivateOrLoopbackHost("localhost")).toBe(true);
|
||||
expect(isPrivateOrLoopbackHost("localhost.")).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts loopback addresses", () => {
|
||||
|
||||
@@ -400,8 +400,9 @@ function parseHostForAddressChecks(
|
||||
return null;
|
||||
}
|
||||
const normalizedHost = host.trim().toLowerCase();
|
||||
if (normalizedHost === "localhost") {
|
||||
return { isLocalhost: true, unbracketedHost: normalizedHost };
|
||||
const canonicalHost = normalizedHost.replace(/\.+$/, "");
|
||||
if (canonicalHost === "localhost") {
|
||||
return { isLocalhost: true, unbracketedHost: canonicalHost };
|
||||
}
|
||||
return {
|
||||
isLocalhost: false,
|
||||
|
||||
Reference in New Issue
Block a user