





















@@ -10,6 +10,7 @@ export XDG_CACHE_HOME="${HOME}/.cache"
10101111CDP_PORT="${OPENCLAW_BROWSER_CDP_PORT:-9222}"
1212CDP_SOURCE_RANGE="${OPENCLAW_BROWSER_CDP_SOURCE_RANGE:-}"
13+CDP_AUTH_TOKEN="${OPENCLAW_BROWSER_CDP_AUTH_TOKEN:-}"
1314VNC_PORT="${OPENCLAW_BROWSER_VNC_PORT:-5900}"
1415NOVNC_PORT="${OPENCLAW_BROWSER_NOVNC_PORT:-6080}"
1516ENABLE_NOVNC="${OPENCLAW_BROWSER_ENABLE_NOVNC:-1}"
@@ -53,7 +54,7 @@ cleanup() {
5354local pids=()
5455local pid
555656-for pid in "${WEBSOCKIFY_PID:-}" "${X11VNC_PID:-}" "${SOCAT_PID:-}" "${CHROME_PID:-}" "${XVFB_PID:-}"; do
57+for pid in "${WEBSOCKIFY_PID:-}" "${X11VNC_PID:-}" "${CDP_RELAY_PID:-}" "${CHROME_PID:-}" "${XVFB_PID:-}"; do
5758if [[ -n "${pid:-}" ]]; then
5859 pids+=("$pid")
5960fi
@@ -172,17 +173,140 @@ if [[ "${CDP_READY}" == "0" ]]; then
172173exit 1
173174fi
174175175-echo "[sandbox] CDP ready. Starting socat..."
176+echo "[sandbox] CDP ready. Starting relay..."
176177177-if [[ -z "${CDP_SOURCE_RANGE}" ]]; then
178-echo "[sandbox-browser] WARNING: CDP_SOURCE_RANGE unset; socat CDP relay will not start." >&2
179-echo "[sandbox-browser] Set OPENCLAW_BROWSER_CDP_SOURCE_RANGE to an explicit CIDR to enable CDP access." >&2
178+if [[ -z "${CDP_AUTH_TOKEN}" ]]; then
179+echo "[sandbox-browser] WARNING: CDP auth token unset; CDP relay will not start." >&2
180180else
181- SOCAT_LISTEN_ADDR="TCP-LISTEN:${CDP_PORT},fork,reuseaddr,bind=0.0.0.0"
182- SOCAT_LISTEN_ADDR="${SOCAT_LISTEN_ADDR},range=${CDP_SOURCE_RANGE}"
183- socat "${SOCAT_LISTEN_ADDR}" "TCP:127.0.0.1:${CHROME_CDP_PORT}" &
184- SOCAT_PID=$!
185-echo "[sandbox] socat started (PID: ${SOCAT_PID})"
181+ OPENCLAW_BROWSER_CHROME_CDP_PORT="${CHROME_CDP_PORT}" python3 - <<'PY' &
182+import base64
183+import hmac
184+import ipaddress
185+import os
186+import select
187+import socket
188+import socketserver
189+import sys
190+import time
191+192+LISTEN_PORT = int(os.environ["OPENCLAW_BROWSER_CDP_PORT"])
193+UPSTREAM_PORT = int(os.environ["OPENCLAW_BROWSER_CHROME_CDP_PORT"])
194+AUTH_TOKEN = os.environ["OPENCLAW_BROWSER_CDP_AUTH_TOKEN"]
195+SOURCE_RANGE = os.environ.get("OPENCLAW_BROWSER_CDP_SOURCE_RANGE", "").strip()
196+MAX_HEADER_BYTES = 65536
197+HEADER_READ_TIMEOUT_SECONDS = 5.0
198+199+try:
200+ SOURCE_NETWORK = ipaddress.ip_network(SOURCE_RANGE, strict=False) if SOURCE_RANGE else None
201+except ValueError:
202+ print(f"[sandbox-browser] ERROR: invalid CDP source range: {SOURCE_RANGE}", file=sys.stderr)
203+ raise SystemExit(1)
204+205+EXPECTED_BASIC = "Basic " + base64.b64encode(f"openclaw:{AUTH_TOKEN}".encode()).decode()
206+EXPECTED_BEARER = "Bearer " + AUTH_TOKEN
207+208+209+def source_allowed(host):
210+ if SOURCE_NETWORK is None:
211+ return True
212+ try:
213+ return ipaddress.ip_address(host) in SOURCE_NETWORK
214+ except ValueError:
215+ return False
216+217+218+def has_auth(header_bytes):
219+ try:
220+ text = header_bytes.decode("iso-8859-1")
221+ except UnicodeDecodeError:
222+ return False
223+ for line in text.split("\r\n")[1:]:
224+ name, sep, value = line.partition(":")
225+ if sep and name.strip().lower() == "authorization":
226+ auth = value.strip()
227+ basic_ok = hmac.compare_digest(auth, EXPECTED_BASIC)
228+ bearer_ok = hmac.compare_digest(auth, EXPECTED_BEARER)
229+ return basic_ok or bearer_ok
230+ return False
231+232+233+def read_headers(conn, deadline):
234+ data = b""
235+ while b"\r\n\r\n" not in data:
236+ remaining = deadline - time.monotonic()
237+ if remaining <= 0:
238+ return b""
239+ conn.settimeout(remaining)
240+ try:
241+ chunk = conn.recv(4096)
242+ except socket.timeout:
243+ return b""
244+ if not chunk:
245+ return b""
246+ data += chunk
247+ if len(data) > MAX_HEADER_BYTES:
248+ return b""
249+ return data
250+251+252+def relay(left, right):
253+ sockets = [left, right]
254+ try:
255+ while sockets:
256+ readable, _, _ = select.select(sockets, [], [])
257+ for src in readable:
258+ dst = right if src is left else left
259+ data = src.recv(65536)
260+ if not data:
261+ return
262+ dst.sendall(data)
263+ finally:
264+ for sock in (left, right):
265+ try:
266+ sock.shutdown(socket.SHUT_RDWR)
267+ except OSError:
268+ pass
269+ try:
270+ sock.close()
271+ except OSError:
272+ pass
273+274+275+class Handler(socketserver.BaseRequestHandler):
276+ def handle(self):
277+ client_host = self.client_address[0]
278+ if not source_allowed(client_host):
279+ return
280+ header_deadline = time.monotonic() + HEADER_READ_TIMEOUT_SECONDS
281+ header_bytes = read_headers(self.request, header_deadline)
282+ if not header_bytes:
283+ return
284+ if not has_auth(header_bytes):
285+ self.request.sendall(
286+ b"HTTP/1.1 401 Unauthorized\r\n"
287+ b'WWW-Authenticate: Basic realm="OpenClaw CDP"\r\n'
288+ b"Connection: close\r\n"
289+ b"Content-Length: 0\r\n\r\n"
290+ )
291+ return
292+ upstream = socket.create_connection(("127.0.0.1", UPSTREAM_PORT), timeout=5)
293+ upstream.settimeout(None)
294+ self.request.settimeout(None)
295+ upstream.sendall(header_bytes)
296+ relay(self.request, upstream)
297+298+299+class Server(socketserver.ThreadingTCPServer):
300+ allow_reuse_address = True
301+ daemon_threads = True
302+303+304+with Server(("0.0.0.0", LISTEN_PORT), Handler) as server:
305+ print("[sandbox] CDP relay started", flush=True)
306+ server.serve_forever()
307+PY
308+ CDP_RELAY_PID=$!
309+echo "[sandbox] CDP relay started (PID: ${CDP_RELAY_PID})"
186310fi
187311188312if [[ "${ENABLE_NOVNC}" == "1" && "${HEADLESS}" != "1" ]]; then
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。