mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(proxy): reject malformed debug proxy targets
This commit is contained in:
@@ -35,6 +35,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Browser tool: treat malformed node proxy `payloadJSON` responses as browser proxy failures instead of leaking raw JSON parser errors.
|
||||
- Gateway HTTP: match models, session kill, and session history route paths without trusting malformed Host headers, avoiding pre-auth 500s on those endpoints.
|
||||
- Google Meet/Codex: report malformed node proxy `payloadJSON` responses with plugin-owned errors instead of leaking raw JSON parser failures.
|
||||
- Debug proxy: reject malformed relative-form proxy targets with a controlled 400 response instead of letting URL parsing escape the request handler.
|
||||
- Models config/auth: stop inferring provider env-var markers from broad `^[A-Z_][A-Z0-9_]*$` strings, and resolve config-backed provider `apiKey` values only through structured env SecretRefs (`secrets.providers[id]` / `secrets.defaults`), so unrelated env vars cannot accidentally become provider credentials. Thanks @sallyom.
|
||||
- Media fetch: skip allocating and buffering the response body for bodyless media responses (HEAD probes and 204-style empty bodies), avoiding wasted heap on streams that carry no payload. Thanks @shakkernerd.
|
||||
- CLI/onboarding: forward provider-specific auth flags (e.g. `--openai-api-key`) through the onboarding wizard so they reach provider auth methods via `ctx.opts`, letting `--openai-api-key "$OPENAI_API_KEY"` skip the redundant "use existing env var?" prompt in non-interactive harnesses. (#81669) Thanks @sjf.
|
||||
|
||||
@@ -71,6 +71,24 @@ async function requestThroughProxy(proxyUrl: string, targetUrl: string): Promise
|
||||
return data;
|
||||
}
|
||||
|
||||
async function requestRawThroughProxy(proxyUrl: string, request: string): Promise<string> {
|
||||
const proxy = new URL(proxyUrl);
|
||||
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(request);
|
||||
await new Promise<void>((resolve) => socket.once("end", resolve));
|
||||
socket.destroy();
|
||||
return data;
|
||||
}
|
||||
|
||||
async function startCanaryOrigin(): Promise<{
|
||||
requestCount: () => number;
|
||||
stop: () => Promise<void>;
|
||||
@@ -188,4 +206,20 @@ describe("debug proxy managed-proxy direct upstream policy", () => {
|
||||
await origin.stop();
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects malformed relative-form HTTP proxy targets before upstream handling", async () => {
|
||||
const server = await startDebugProxyServer({ settings: await makeSettings() });
|
||||
try {
|
||||
const response = await requestRawThroughProxy(
|
||||
server.proxyUrl,
|
||||
"GET /capture HTTP/1.1\r\nHost: [\r\nConnection: close\r\n\r\n",
|
||||
);
|
||||
|
||||
expect(response).toContain("400 Bad Request");
|
||||
expect(response).toContain("Connection: close");
|
||||
expect(response).toContain("Invalid proxy target URL");
|
||||
} finally {
|
||||
await server.stop();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,7 +98,34 @@ export async function startDebugProxyServer(params: {
|
||||
|
||||
const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
|
||||
const flowId = randomUUID();
|
||||
const target = normalizeTargetUrl(req);
|
||||
let target: URL;
|
||||
try {
|
||||
target = normalizeTargetUrl(req);
|
||||
} catch (error) {
|
||||
const message = "Invalid proxy target URL";
|
||||
store.recordEvent({
|
||||
sessionId: params.settings.sessionId,
|
||||
ts: Date.now(),
|
||||
sourceScope: "openclaw",
|
||||
sourceProcess: params.settings.sourceProcess,
|
||||
protocol: "http",
|
||||
direction: "local",
|
||||
kind: "error",
|
||||
flowId,
|
||||
method: req.method,
|
||||
host: req.headers.host,
|
||||
path: req.url ?? "",
|
||||
errorText: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
const responseBody = `${message}\n`;
|
||||
res.writeHead(400, {
|
||||
Connection: "close",
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
"Content-Length": Buffer.byteLength(responseBody),
|
||||
});
|
||||
res.end(responseBody);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
assertDebugProxyDirectUpstreamAllowed();
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user