mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 18:50:42 +00:00
fix(clawsweeper): address review for automerge-openclaw-openclaw-77010 (1)
This commit is contained in:
@@ -166,7 +166,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- CLI/plugins: explain when a missing plugin command alias belongs to a bundled plugin that is disabled by default, including the `openclaw plugins enable <plugin>` repair command. (#76835)
|
- CLI/plugins: explain when a missing plugin command alias belongs to a bundled plugin that is disabled by default, including the `openclaw plugins enable <plugin>` repair command. (#76835)
|
||||||
- Gateway/Bonjour: auto-start LAN multicast discovery only on macOS hosts while preserving explicit `openclaw plugins enable bonjour` startup elsewhere, so Linux servers and containers that do not need LAN discovery avoid default mDNS probing and watchdog churn. Refs #74209.
|
- Gateway/Bonjour: auto-start LAN multicast discovery only on macOS hosts while preserving explicit `openclaw plugins enable bonjour` startup elsewhere, so Linux servers and containers that do not need LAN discovery avoid default mDNS probing and watchdog churn. Refs #74209.
|
||||||
- Gateway/macOS: stop `doctor` and LaunchAgent recovery from running `launchctl kickstart -k` after a fresh bootstrap, avoiding an immediate SIGTERM of the just-started gateway while still nudging already-loaded launchd jobs. Fixes #76261. Thanks @solosage1.
|
- Gateway/macOS: stop `doctor` and LaunchAgent recovery from running `launchctl kickstart -k` after a fresh bootstrap, avoiding an immediate SIGTERM of the just-started gateway while still nudging already-loaded launchd jobs. Fixes #76261. Thanks @solosage1.
|
||||||
- Proxy/debugging: disable debug proxy CONNECT upstream forwarding while managed proxy mode is active unless `OPENCLAW_DEBUG_PROXY_ALLOW_DIRECT_CONNECT_WITH_MANAGED_PROXY=1` is explicitly set for approved local diagnostics. Thanks @jesse-merhi and @mjamiv.
|
- Proxy/debugging: disable debug proxy direct upstream forwarding for proxy requests and CONNECT tunnels while managed proxy mode is active unless `OPENCLAW_DEBUG_PROXY_ALLOW_DIRECT_CONNECT_WITH_MANAGED_PROXY=1` is explicitly set for approved local diagnostics. Thanks @jesse-merhi and @mjamiv.
|
||||||
- Google Meet: route stateful CLI session commands through the gateway-owned runtime so joined realtime sessions survive after the starting CLI process exits. Fixes #76344. Thanks @coltonharris-wq.
|
- Google Meet: route stateful CLI session commands through the gateway-owned runtime so joined realtime sessions survive after the starting CLI process exits. Fixes #76344. Thanks @coltonharris-wq.
|
||||||
- Memory/status: split builtin sqlite-vec store readiness from embedding-provider readiness in `memory status --deep` and `openclaw status`, so local vector-store failures no longer look like provider failures and provider failures no longer hide a healthy local vector store.
|
- Memory/status: split builtin sqlite-vec store readiness from embedding-provider readiness in `memory status --deep` and `openclaw status`, so local vector-store failures no longer look like provider failures and provider failures no longer hide a healthy local vector store.
|
||||||
- CLI/doctor: trust a ready gateway memory probe when CLI-side active memory backend resolution is unavailable, preventing false "No active memory plugin is registered" warnings for healthy runtime setups. Fixes #76792. Thanks @som-686.
|
- CLI/doctor: trust a ready gateway memory probe when CLI-side active memory backend resolution is unavailable, preventing false "No active memory plugin is registered" warnings for healthy runtime setups. Fixes #76792. Thanks @som-686.
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ semantics.
|
|||||||
|
|
||||||
- `start` defaults to `127.0.0.1` unless `--host` is set.
|
- `start` defaults to `127.0.0.1` unless `--host` is set.
|
||||||
- `run` starts a local debug proxy and then runs the command after `--`.
|
- `run` starts a local debug proxy and then runs the command after `--`.
|
||||||
- The debug proxy's CONNECT forwarding opens upstream TCP sockets for diagnostics. When OpenClaw managed proxy mode is active, CONNECT forwarding is disabled by default; set `OPENCLAW_DEBUG_PROXY_ALLOW_DIRECT_CONNECT_WITH_MANAGED_PROXY=1` only for approved local diagnostics.
|
- The debug proxy's direct upstream forwarding opens upstream sockets for diagnostics. When OpenClaw managed proxy mode is active, direct forwarding for proxy requests and CONNECT tunnels is disabled by default; set `OPENCLAW_DEBUG_PROXY_ALLOW_DIRECT_CONNECT_WITH_MANAGED_PROXY=1` only for approved local diagnostics.
|
||||||
- `validate` exits with code 1 when proxy config or destination checks fail.
|
- `validate` exits with code 1 when proxy config or destination checks fail.
|
||||||
- Captures are local debugging data; use `openclaw proxy purge` when finished.
|
- Captures are local debugging data; use `openclaw proxy purge` when finished.
|
||||||
|
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ proxy:
|
|||||||
- The proxy improves coverage for process-local JavaScript HTTP and WebSocket clients, but it is not an OS-level network sandbox.
|
- The proxy improves coverage for process-local JavaScript HTTP and WebSocket clients, but it is not an OS-level network sandbox.
|
||||||
- Raw `net`, `tls`, and `http2` sockets, native addons, and child processes may bypass Node-level proxy routing unless they inherit and respect proxy environment variables.
|
- Raw `net`, `tls`, and `http2` sockets, native addons, and child processes may bypass Node-level proxy routing unless they inherit and respect proxy environment variables.
|
||||||
- IRC is a raw TCP/TLS channel outside operator-managed forward proxy routing. In deployments that require all egress through that forward proxy, set `channels.irc.enabled=false` unless direct IRC egress is explicitly approved.
|
- IRC is a raw TCP/TLS channel outside operator-managed forward proxy routing. In deployments that require all egress through that forward proxy, set `channels.irc.enabled=false` unless direct IRC egress is explicitly approved.
|
||||||
- The local debug proxy is diagnostic tooling and its CONNECT upstream forwarding is disabled by default while managed proxy mode is active; enable direct CONNECT forwarding only for approved local diagnostics.
|
- The local debug proxy is diagnostic tooling and its direct upstream forwarding for proxy requests and CONNECT tunnels is disabled by default while managed proxy mode is active; enable direct forwarding only for approved local diagnostics.
|
||||||
- User local WebUIs and local model servers should be allowlisted in the operator proxy policy when needed; OpenClaw does not expose a general local-network bypass for them.
|
- User local WebUIs and local model servers should be allowlisted in the operator proxy policy when needed; OpenClaw does not expose a general local-network bypass for them.
|
||||||
- Gateway control-plane proxy bypass is intentionally limited to `localhost` and literal loopback IP URLs. Use `ws://127.0.0.1:18789`, `ws://[::1]:18789`, or `ws://localhost:18789` for local direct Gateway control-plane connections; other hostnames route like ordinary hostname-based traffic.
|
- Gateway control-plane proxy bypass is intentionally limited to `localhost` and literal loopback IP URLs. Use `ws://127.0.0.1:18789`, `ws://[::1]:18789`, or `ws://localhost:18789` for local direct Gateway control-plane connections; other hostnames route like ordinary hostname-based traffic.
|
||||||
- OpenClaw does not inspect, test, or certify your proxy policy.
|
- OpenClaw does not inspect, test, or certify your proxy policy.
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { mkdtemp, rm } from "node:fs/promises";
|
import { mkdtemp, rm } from "node:fs/promises";
|
||||||
import { Socket } from "node:net";
|
import { createServer as createHttpServer } from "node:http";
|
||||||
|
import { Socket, type AddressInfo } from "node:net";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
import { assertDebugProxyDirectConnectAllowed, startDebugProxyServer } from "./proxy-server.js";
|
import { assertDebugProxyDirectUpstreamAllowed, startDebugProxyServer } from "./proxy-server.js";
|
||||||
|
|
||||||
let testRoot: string | undefined;
|
let testRoot: string | undefined;
|
||||||
|
|
||||||
@@ -47,7 +48,60 @@ async function connectThroughProxy(proxyUrl: string): Promise<string> {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("debug proxy managed-proxy CONNECT policy", () => {
|
async function requestThroughProxy(proxyUrl: string, targetUrl: string): Promise<string> {
|
||||||
|
const proxy = new URL(proxyUrl);
|
||||||
|
const target = new URL(targetUrl);
|
||||||
|
const socket = new Socket();
|
||||||
|
let data = "";
|
||||||
|
socket.setEncoding("utf8");
|
||||||
|
socket.on("data", (chunk) => {
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
socket.once("error", reject);
|
||||||
|
socket.connect(Number(proxy.port), proxy.hostname, resolve);
|
||||||
|
});
|
||||||
|
socket.write(`GET ${target.href} HTTP/1.1\r\nHost: ${target.host}\r\nConnection: close\r\n\r\n`);
|
||||||
|
await new Promise<void>((resolve) => socket.once("end", resolve));
|
||||||
|
socket.destroy();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startCanaryOrigin(): Promise<{
|
||||||
|
requestCount: () => number;
|
||||||
|
stop: () => Promise<void>;
|
||||||
|
url: string;
|
||||||
|
}> {
|
||||||
|
let requests = 0;
|
||||||
|
const server = createHttpServer((_req, res) => {
|
||||||
|
requests += 1;
|
||||||
|
res.end("ok");
|
||||||
|
});
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
server.once("error", reject);
|
||||||
|
server.listen(0, "127.0.0.1", () => {
|
||||||
|
server.off("error", reject);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const address = server.address() as AddressInfo;
|
||||||
|
return {
|
||||||
|
requestCount: () => requests,
|
||||||
|
stop: async () =>
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
server.close((error) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
url: `http://127.0.0.1:${address.port}/metadata`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("debug proxy managed-proxy direct upstream policy", () => {
|
||||||
const originalProxyActive = process.env["OPENCLAW_PROXY_ACTIVE"];
|
const originalProxyActive = process.env["OPENCLAW_PROXY_ACTIVE"];
|
||||||
const originalAllowDirect =
|
const originalAllowDirect =
|
||||||
process.env["OPENCLAW_DEBUG_PROXY_ALLOW_DIRECT_CONNECT_WITH_MANAGED_PROXY"];
|
process.env["OPENCLAW_DEBUG_PROXY_ALLOW_DIRECT_CONNECT_WITH_MANAGED_PROXY"];
|
||||||
@@ -73,31 +127,31 @@ describe("debug proxy managed-proxy CONNECT policy", () => {
|
|||||||
await cleanupTestDirs();
|
await cleanupTestDirs();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows direct CONNECT upstreams when managed proxy mode is inactive", () => {
|
it("allows direct upstreams when managed proxy mode is inactive", () => {
|
||||||
expect(() => assertDebugProxyDirectConnectAllowed()).not.toThrow();
|
expect(() => assertDebugProxyDirectUpstreamAllowed()).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects direct CONNECT upstreams while managed proxy mode is active", () => {
|
it("rejects direct upstreams while managed proxy mode is active", () => {
|
||||||
process.env["OPENCLAW_PROXY_ACTIVE"] = "1";
|
process.env["OPENCLAW_PROXY_ACTIVE"] = "1";
|
||||||
|
|
||||||
expect(() => assertDebugProxyDirectConnectAllowed()).toThrow(
|
expect(() => assertDebugProxyDirectUpstreamAllowed()).toThrow(
|
||||||
/Debug proxy CONNECT upstream forwarding is disabled/,
|
/Debug proxy direct upstream forwarding is disabled/,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses shared truthy parsing for managed proxy mode", () => {
|
it("uses shared truthy parsing for managed proxy mode", () => {
|
||||||
process.env["OPENCLAW_PROXY_ACTIVE"] = "true";
|
process.env["OPENCLAW_PROXY_ACTIVE"] = "true";
|
||||||
|
|
||||||
expect(() => assertDebugProxyDirectConnectAllowed()).toThrow(
|
expect(() => assertDebugProxyDirectUpstreamAllowed()).toThrow(
|
||||||
/Debug proxy CONNECT upstream forwarding is disabled/,
|
/Debug proxy direct upstream forwarding is disabled/,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows direct CONNECT upstreams with explicit diagnostic override", () => {
|
it("allows direct upstreams with explicit diagnostic override", () => {
|
||||||
process.env["OPENCLAW_PROXY_ACTIVE"] = "1";
|
process.env["OPENCLAW_PROXY_ACTIVE"] = "1";
|
||||||
process.env["OPENCLAW_DEBUG_PROXY_ALLOW_DIRECT_CONNECT_WITH_MANAGED_PROXY"] = "1";
|
process.env["OPENCLAW_DEBUG_PROXY_ALLOW_DIRECT_CONNECT_WITH_MANAGED_PROXY"] = "1";
|
||||||
|
|
||||||
expect(() => assertDebugProxyDirectConnectAllowed()).not.toThrow();
|
expect(() => assertDebugProxyDirectUpstreamAllowed()).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects CONNECT upstreams before opening direct sockets while managed proxy mode is active", async () => {
|
it("rejects CONNECT upstreams before opening direct sockets while managed proxy mode is active", async () => {
|
||||||
@@ -108,9 +162,26 @@ describe("debug proxy managed-proxy CONNECT policy", () => {
|
|||||||
|
|
||||||
expect(response).toContain("403 Forbidden");
|
expect(response).toContain("403 Forbidden");
|
||||||
expect(response).toContain("Connection: close");
|
expect(response).toContain("Connection: close");
|
||||||
expect(response).toContain("Debug proxy CONNECT upstream forwarding is disabled");
|
expect(response).toContain("Debug proxy direct upstream forwarding is disabled");
|
||||||
} finally {
|
} finally {
|
||||||
await server.stop();
|
await server.stop();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("rejects absolute-form HTTP proxy requests before opening direct upstreams while managed proxy mode is active", async () => {
|
||||||
|
process.env["OPENCLAW_PROXY_ACTIVE"] = "1";
|
||||||
|
const origin = await startCanaryOrigin();
|
||||||
|
const server = await startDebugProxyServer({ settings: await makeSettings() });
|
||||||
|
try {
|
||||||
|
const response = await requestThroughProxy(server.proxyUrl, origin.url);
|
||||||
|
|
||||||
|
expect(response).toContain("403 Forbidden");
|
||||||
|
expect(response).toContain("Connection: close");
|
||||||
|
expect(response).toContain("Debug proxy direct upstream forwarding is disabled");
|
||||||
|
expect(origin.requestCount()).toBe(0);
|
||||||
|
} finally {
|
||||||
|
await server.stop();
|
||||||
|
await origin.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ function allowsDirectConnectWithManagedProxy(env: NodeJS.ProcessEnv = process.en
|
|||||||
return isTruthyEnvValue(env[DEBUG_PROXY_DIRECT_CONNECT_OVERRIDE]);
|
return isTruthyEnvValue(env[DEBUG_PROXY_DIRECT_CONNECT_OVERRIDE]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assertDebugProxyDirectConnectAllowed(env: NodeJS.ProcessEnv = process.env): void {
|
export function assertDebugProxyDirectUpstreamAllowed(env: NodeJS.ProcessEnv = process.env): void {
|
||||||
if (!isManagedProxyActive(env) || allowsDirectConnectWithManagedProxy(env)) {
|
if (!isManagedProxyActive(env) || allowsDirectConnectWithManagedProxy(env)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Debug proxy CONNECT upstream forwarding is disabled while managed proxy mode is active. " +
|
"Debug proxy direct upstream forwarding is disabled while managed proxy mode is active. " +
|
||||||
`Set ${DEBUG_PROXY_DIRECT_CONNECT_OVERRIDE}=1 only for approved local diagnostics.`,
|
`Set ${DEBUG_PROXY_DIRECT_CONNECT_OVERRIDE}=1 only for approved local diagnostics.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -99,6 +99,33 @@ export async function startDebugProxyServer(params: {
|
|||||||
const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
|
const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
|
||||||
const flowId = randomUUID();
|
const flowId = randomUUID();
|
||||||
const target = normalizeTargetUrl(req);
|
const target = normalizeTargetUrl(req);
|
||||||
|
try {
|
||||||
|
assertDebugProxyDirectUpstreamAllowed();
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
store.recordEvent({
|
||||||
|
sessionId: params.settings.sessionId,
|
||||||
|
ts: Date.now(),
|
||||||
|
sourceScope: "openclaw",
|
||||||
|
sourceProcess: params.settings.sourceProcess,
|
||||||
|
protocol: target.protocol === "https:" ? "https" : "http",
|
||||||
|
direction: "local",
|
||||||
|
kind: "error",
|
||||||
|
flowId,
|
||||||
|
method: req.method,
|
||||||
|
host: target.host,
|
||||||
|
path: `${target.pathname}${target.search}`,
|
||||||
|
errorText: message,
|
||||||
|
});
|
||||||
|
const responseBody = `${message}\n`;
|
||||||
|
res.writeHead(403, {
|
||||||
|
Connection: "close",
|
||||||
|
"Content-Type": "text/plain; charset=utf-8",
|
||||||
|
"Content-Length": Buffer.byteLength(responseBody),
|
||||||
|
});
|
||||||
|
res.end(responseBody);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const body = await readBody(req);
|
const body = await readBody(req);
|
||||||
store.recordEvent({
|
store.recordEvent({
|
||||||
sessionId: params.settings.sessionId,
|
sessionId: params.settings.sessionId,
|
||||||
@@ -214,7 +241,7 @@ export async function startDebugProxyServer(params: {
|
|||||||
headersJson: JSON.stringify(req.headers),
|
headersJson: JSON.stringify(req.headers),
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
assertDebugProxyDirectConnectAllowed();
|
assertDebugProxyDirectUpstreamAllowed();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
store.recordEvent({
|
store.recordEvent({
|
||||||
|
|||||||
Reference in New Issue
Block a user