feat(config): add browser and web fetch proxy support

- Add CLAWDBOT_BROWSER_PROXY env for Chrome --proxy-server flag
- Add CLAWDBOT_WEB_FETCH_PROXY env for web fetch tool requests
- Browser: passes --proxy-server to Chrome launch args when set
- Fetch: uses ProxyAgent instead of pinned dispatcher when set

This enables routing browser and fetch traffic through a proxy for:
- Multi-tenant deployments with blocked outbound 443
- Usage metering and audit logging
- Security filtering

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Developer 2026-01-27 08:06:38 +01:00
parent 5768e6a202
commit 02cf8cd2c1
5 changed files with 9234 additions and 4 deletions

9193
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
import { Type } from "@sinclair/typebox";
import { WEB_FETCH_PROXY } from "../../config/api-endpoints.js";
import type { ClawdbotConfig } from "../../config/config.js";
import {
closeDispatcher,
@ -7,7 +8,7 @@ import {
resolvePinnedHostname,
SsrFBlockedError,
} from "../../infra/net/ssrf.js";
import type { Dispatcher } from "undici";
import { ProxyAgent, type Dispatcher } from "undici";
import { stringEnum } from "../schema/typebox.js";
import type { AnyAgentTool } from "./common.js";
import { jsonResult, readNumberParam, readStringParam } from "./common.js";
@ -190,8 +191,14 @@ async function fetchWithRedirects(params: {
throw new Error("Invalid URL: must be http or https");
}
const pinned = await resolvePinnedHostname(parsedUrl.hostname);
const dispatcher = createPinnedDispatcher(pinned);
// Use proxy if configured, otherwise use pinned dispatcher for SSRF protection
let dispatcher: Dispatcher;
if (WEB_FETCH_PROXY) {
dispatcher = new ProxyAgent(WEB_FETCH_PROXY);
} else {
const pinned = await resolvePinnedHostname(parsedUrl.hostname);
dispatcher = createPinnedDispatcher(pinned);
}
let res: Response;
try {
res = await fetch(parsedUrl.toString(), {

View File

@ -4,6 +4,7 @@ import os from "node:os";
import path from "node:path";
import WebSocket from "ws";
import { BROWSER_PROXY } from "../config/api-endpoints.js";
import { ensurePortAvailable } from "../infra/ports.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { CONFIG_DIR } from "../utils.js";
@ -203,6 +204,9 @@ export async function launchClawdChrome(
if (process.platform === "linux") {
args.push("--disable-dev-shm-usage");
}
if (BROWSER_PROXY) {
args.push(`--proxy-server=${BROWSER_PROXY}`);
}
// Always open a blank tab to ensure a target exists.
args.push("about:blank");

View File

@ -1 +1 @@
2567ca5bbc065b922d96717a488d5db3120b5b033c5d0508682d1aa8fbba470a
471383b08a85227f644f7fcde5ecaee84d00097a5b73cbdc1067a0e12a44b8e4

View File

@ -102,3 +102,29 @@ export const OPENAI_TTS_BASE = trimSlash(process.env.OPENAI_TTS_BASE_URL) ?? OPE
*/
export const ELEVENLABS_API_BASE =
trimSlash(process.env.ELEVENLABS_API_BASE) ?? "https://api.elevenlabs.io";
// =============================================================================
// WEB PROXY (Browser & Fetch)
// =============================================================================
/**
* HTTP proxy for browser (Playwright/Puppeteer).
* When set, adds --proxy-server flag to Chrome launch args.
*
* @example
* ```bash
* CLAWDBOT_BROWSER_PROXY=http://proxy.example.com:8080
* ```
*/
export const BROWSER_PROXY = process.env.CLAWDBOT_BROWSER_PROXY?.trim() || undefined;
/**
* HTTP proxy for web fetch tool requests.
* When set, routes all fetch tool HTTP requests through the proxy.
*
* @example
* ```bash
* CLAWDBOT_WEB_FETCH_PROXY=http://proxy.example.com:8080
* ```
*/
export const WEB_FETCH_PROXY = process.env.CLAWDBOT_WEB_FETCH_PROXY?.trim() || undefined;