From 6f2aefa28b3647c65a3279797705c1dfa576baf4 Mon Sep 17 00:00:00 2001 From: Yanyun Liao Date: Tue, 28 Apr 2026 11:30:29 +0800 Subject: [PATCH 1/2] fix(onboard): propagate NEMOCLAW_PROXY_HOST/PORT to sandbox env (#2424) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `patchStagedDockerfile()` already substitutes NEMOCLAW_PROXY_HOST and NEMOCLAW_PROXY_PORT into the staged Dockerfile's ARG defaults at build time, so the resulting image's `Config.Env` carries the user-supplied values. But the runtime container does not see them: the create command emitted by `createSandbox()` is openshell sandbox create … -- env nemoclaw-start and only the env vars listed before `nemoclaw-start` reach the container — image-baked ENV is not forwarded by this path. The existing whitelist already covers CHAT_UI_URL, NEMOCLAW_DASHBOARD_PORT (conditional), Brave/Slack tokens, but omits the proxy pair. Effect of the omission: - Build time: Dockerfile ENV / RUN python step sees the override and would bake e.g. discord/telegram channel proxies with the new host:port if those channels are configured. - Runtime: nemoclaw-start.sh:898 reads `${NEMOCLAW_PROXY_HOST:-…}`, sees the var unset, falls back to the default 10.200.0.1:3128, writes that into /tmp/nemoclaw-proxy-env.sh, and `HTTPS_PROXY` inside the sandbox ignores the host override entirely. Reporter on #2424 verified on v0.0.24 that `HTTPS_PROXY` inside the sandbox is `http://10.200.0.1:3128` even after exporting custom values on the host before `nemoclaw onboard`. End-to-end isolated tracing of patchStagedDockerfile() + docker inspect of the built image + cat of /tmp/nemoclaw-proxy-env.sh + .bashrc source chain confirmed the only break point is between image ENV and pod runtime ENV. Add the two assignments to the whitelist with the same input validation regex used in `patchStagedDockerfile()` so build-time and runtime accept identical inputs: PROXY_HOST_RE = /^[A-Za-z0-9._:-]+$/ PROXY_PORT_RE = /^[0-9]{1,5}$/ Conditional push (only when the host env is present) keeps the default behaviour byte-identical when no override is exported. --- src/lib/onboard.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/lib/onboard.ts b/src/lib/onboard.ts index 1c38fc44a35..ff436d0e81f 100644 --- a/src/lib/onboard.ts +++ b/src/lib/onboard.ts @@ -3878,6 +3878,26 @@ async function createSandbox( if (process.env.NEMOCLAW_DASHBOARD_PORT) { envArgs.push(formatEnvAssignment("NEMOCLAW_DASHBOARD_PORT", String(DASHBOARD_PORT))); } + // Propagate NEMOCLAW_PROXY_HOST / NEMOCLAW_PROXY_PORT to the runtime + // sandbox container. patchStagedDockerfile() already substitutes them + // into the build-time Dockerfile ARG/ENV, but `openshell sandbox create + // -- env … nemoclaw-start` only forwards the explicitly listed env vars + // — image-baked ENV does not propagate into the running pod. Without + // this, nemoclaw-start.sh:898 falls back to the default 10.200.0.1:3128 + // and `HTTPS_PROXY` inside the sandbox ignores the host override. The + // build-time substitution and runtime env stay in sync as a result. + // Fixes #2424. Validation regex mirrors patchStagedDockerfile() so the + // two paths accept identical values. + const PROXY_HOST_RE = /^[A-Za-z0-9._:-]+$/; + const PROXY_PORT_RE = /^[0-9]{1,5}$/; + const sandboxProxyHost = process.env.NEMOCLAW_PROXY_HOST; + if (sandboxProxyHost && PROXY_HOST_RE.test(sandboxProxyHost)) { + envArgs.push(formatEnvAssignment("NEMOCLAW_PROXY_HOST", sandboxProxyHost)); + } + const sandboxProxyPort = process.env.NEMOCLAW_PROXY_PORT; + if (sandboxProxyPort && PROXY_PORT_RE.test(sandboxProxyPort)) { + envArgs.push(formatEnvAssignment("NEMOCLAW_PROXY_PORT", sandboxProxyPort)); + } if (webSearchConfig?.fetchEnabled) { const braveKey = getCredential(webSearch.BRAVE_API_KEY_ENV) || process.env[webSearch.BRAVE_API_KEY_ENV]; From d3e6bb8aea6d8eeb74d66e5ccffd42870e2c883a Mon Sep 17 00:00:00 2001 From: Yanyun Liao Date: Tue, 28 Apr 2026 13:06:35 +0800 Subject: [PATCH 2/2] fix(onboard): tighten proxy host/port validation, reuse across both call sites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review feedback on #2424. The previous regexes accepted inputs that nemoclaw-start.sh cannot turn into a valid http://${HOST}:${PORT} URL: - PROXY_HOST_RE = /^[A-Za-z0-9._:-]+$/ admitted raw IPv6 literals such as `2001:db8::1`. The runtime template does not bracket the host, so it would emit `http://2001:db8::1:1080`, which is not a legal URL. - PROXY_PORT_RE = /^[0-9]{1,5}$/ admitted out-of-range values such as `70000`. Extract isValidProxyHost / isValidProxyPort module-level helpers and use them in both `patchStagedDockerfile()` (build-time Dockerfile ARG override) and `createSandbox()` (runtime sandbox env whitelist). The host regex now drops `:` and the port path adds an explicit 1..65535 range check on Number(value). Keeping a single pair of helpers avoids the build-time and runtime paths drifting apart in future edits — the reviewer specifically asked for that alignment. --- src/lib/onboard.ts | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/lib/onboard.ts b/src/lib/onboard.ts index 3fa962a51af..fe4511fb65d 100644 --- a/src/lib/onboard.ts +++ b/src/lib/onboard.ts @@ -1270,6 +1270,26 @@ async function configureWebSearch( // getSandboxInferenceConfig — moved to onboard-providers.ts +// Shared validators for NEMOCLAW_PROXY_HOST / NEMOCLAW_PROXY_PORT. +// Both `patchStagedDockerfile()` (build-time Dockerfile ARG override) and +// `createSandbox()` (runtime sandbox env whitelist) must reject the same +// inputs, otherwise the build and runtime paths can diverge — e.g. a +// build-time-accepted value silently no-ops at runtime, leaving the +// container running with the default proxy. Hostname regex deliberately +// excludes `:` so raw IPv6 literals are rejected: the runtime +// `http://${HOST}:${PORT}` template does not bracket them and would +// produce a malformed URL. Port is range-checked because a 5-digit +// length filter alone would accept out-of-range values like 70000. +const PROXY_HOST_RE = /^[A-Za-z0-9._-]+$/; +function isValidProxyHost(value: string): boolean { + return PROXY_HOST_RE.test(value); +} +function isValidProxyPort(value: string): boolean { + if (!/^[0-9]{1,5}$/.test(value)) return false; + const port = Number(value); + return port >= 1 && port <= 65535; +} + function patchStagedDockerfile( dockerfilePath: string, model: string, @@ -1381,17 +1401,15 @@ function patchStagedDockerfile( // shell so the sandbox-side nemoclaw-start.sh sees them via $ENV at runtime. // Without this, the host export is silently dropped at image build time and // the sandbox falls back to the default 10.200.0.1:3128 proxy. See #1409. - const PROXY_HOST_RE = /^[A-Za-z0-9._:-]+$/; - const PROXY_PORT_RE = /^[0-9]{1,5}$/; const proxyHostEnv = process.env.NEMOCLAW_PROXY_HOST; - if (proxyHostEnv && PROXY_HOST_RE.test(proxyHostEnv)) { + if (proxyHostEnv && isValidProxyHost(proxyHostEnv)) { dockerfile = dockerfile.replace( /^ARG NEMOCLAW_PROXY_HOST=.*$/m, `ARG NEMOCLAW_PROXY_HOST=${proxyHostEnv}`, ); } const proxyPortEnv = process.env.NEMOCLAW_PROXY_PORT; - if (proxyPortEnv && PROXY_PORT_RE.test(proxyPortEnv)) { + if (proxyPortEnv && isValidProxyPort(proxyPortEnv)) { dockerfile = dockerfile.replace( /^ARG NEMOCLAW_PROXY_PORT=.*$/m, `ARG NEMOCLAW_PROXY_PORT=${proxyPortEnv}`, @@ -3596,16 +3614,14 @@ async function createSandbox( // this, nemoclaw-start.sh:898 falls back to the default 10.200.0.1:3128 // and `HTTPS_PROXY` inside the sandbox ignores the host override. The // build-time substitution and runtime env stay in sync as a result. - // Fixes #2424. Validation regex mirrors patchStagedDockerfile() so the - // two paths accept identical values. - const PROXY_HOST_RE = /^[A-Za-z0-9._:-]+$/; - const PROXY_PORT_RE = /^[0-9]{1,5}$/; + // Fixes #2424. Uses the shared isValidProxyHost / isValidProxyPort + // helpers so build-time and runtime validation stay aligned. const sandboxProxyHost = process.env.NEMOCLAW_PROXY_HOST; - if (sandboxProxyHost && PROXY_HOST_RE.test(sandboxProxyHost)) { + if (sandboxProxyHost && isValidProxyHost(sandboxProxyHost)) { envArgs.push(formatEnvAssignment("NEMOCLAW_PROXY_HOST", sandboxProxyHost)); } const sandboxProxyPort = process.env.NEMOCLAW_PROXY_PORT; - if (sandboxProxyPort && PROXY_PORT_RE.test(sandboxProxyPort)) { + if (sandboxProxyPort && isValidProxyPort(sandboxProxyPort)) { envArgs.push(formatEnvAssignment("NEMOCLAW_PROXY_PORT", sandboxProxyPort)); } if (webSearchConfig?.fetchEnabled) {