Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions scripts/nemoclaw-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5162,6 +5162,24 @@ arm_openclaw_gateway_supervisor_cleanup() {
trap clear_in_container_gateway_marker EXIT
}

launch_openclaw_gateway_process() {
local log_mode="$1"
shift
case "$log_mode" in
append)
nohup /usr/bin/env -u OPENCLAW_GATEWAY_TOKEN "$@" >>/tmp/gateway.log 2>&1 &
;;
truncate)
nohup /usr/bin/env -u OPENCLAW_GATEWAY_TOKEN "$@" >/tmp/gateway.log 2>&1 &
;;
*)
echo "[gateway] invalid gateway log mode: $log_mode" >&2
return 1
;;
esac
GATEWAY_PID=$!
}

launch_openclaw_gateway() {
# Drop the gateway marker whenever this supervisor exits -- clean gateway
# exit (`exit 0` below), a forwarded signal (cleanup_openclaw_on_signal ends
Expand All @@ -5174,10 +5192,10 @@ launch_openclaw_gateway() {
# script -- keeps it in place.
arm_openclaw_gateway_supervisor_cleanup
mark_in_container_gateway
nohup "${STEP_DOWN_PREFIX_GATEWAY[@]}" env HOME=/sandbox sh -c \
launch_openclaw_gateway_process truncate \
"${STEP_DOWN_PREFIX_GATEWAY[@]}" env HOME=/sandbox sh -c \
'umask 0007; exec "$@" >>/tmp/gateway.log 2>&1' sh \
"$OPENCLAW" gateway run --port "${_DASHBOARD_PORT}" &
GATEWAY_PID=$!
"$OPENCLAW" gateway run --port "${_DASHBOARD_PORT}"
if ! capture_openclaw_pid_start_identity "$GATEWAY_PID" GATEWAY_PID_START_IDENTITY; then
# An uncaptured numeric PID is never safe to signal: Bash may already have
# reaped the short-lived child and the kernel may have reused its PID. Fail
Expand All @@ -5197,8 +5215,8 @@ launch_openclaw_gateway() {
launch_openclaw_gateway_non_root() {
arm_openclaw_gateway_supervisor_cleanup
mark_in_container_gateway
nohup "$OPENCLAW" gateway run --port "${_DASHBOARD_PORT}" >/tmp/gateway.log 2>&1 &
GATEWAY_PID=$!
launch_openclaw_gateway_process truncate \
"$OPENCLAW" gateway run --port "${_DASHBOARD_PORT}"
capture_openclaw_pid_start_identity "$GATEWAY_PID" GATEWAY_PID_START_IDENTITY || exit 1
record_gateway_pid "$GATEWAY_PID" "$GATEWAY_PID_START_IDENTITY"
echo "[gateway] openclaw gateway launched (pid $GATEWAY_PID)" >&2
Expand Down Expand Up @@ -5868,8 +5886,8 @@ if [ "$(id -u)" -ne 0 ]; then
echo "[gateway] pid $EXITED_GATEWAY_PID exited (rc=$RC); respawning (#$RESPAWN_COUNT in 60s window) in 2s" >&2
sleep 2
prepare_openclaw_automatic_respawn || exit 1
nohup "$OPENCLAW" gateway run --port "${_DASHBOARD_PORT}" >>/tmp/gateway.log 2>&1 &
GATEWAY_PID=$!
launch_openclaw_gateway_process append \
"$OPENCLAW" gateway run --port "${_DASHBOARD_PORT}"
capture_openclaw_pid_start_identity "$GATEWAY_PID" GATEWAY_PID_START_IDENTITY || exit 1
record_gateway_pid "$GATEWAY_PID" "$GATEWAY_PID_START_IDENTITY"
# shellcheck disable=SC2034 # read by cleanup_on_signal from sandbox-init.sh
Expand Down
66 changes: 66 additions & 0 deletions test/nemoclaw-start-gateway-token-env.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { spawnSync } from "node:child_process";

import { describe, expect, it } from "vitest";

import { extractShellFunctionFromSource } from "./support/shell-function-extractor";

const START_SCRIPT = path.resolve(import.meta.dirname, "../scripts/nemoclaw-start.sh");

describe("OpenClaw gateway credential environment", () => {
it.each([
"truncate",
"append",
])("removes the dashboard token from a %s gateway launch (#8693)", (logMode) => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-gateway-token-env-"));
const gatewayLog = path.join(tmpDir, "gateway.log");
const source = fs.readFileSync(START_SCRIPT, "utf8");
const launch = extractShellFunctionFromSource(
source,
"launch_openclaw_gateway_process",
).replaceAll("/tmp/gateway.log", gatewayLog);
const script = [
"set -euo pipefail",
launch,
"export OPENCLAW_GATEWAY_TOKEN=dashboard-secret",
`launch_openclaw_gateway_process ${logMode} sh -c 'printf "%s\\n" "\${OPENCLAW_GATEWAY_TOKEN-unset}"'`,
'wait "$GATEWAY_PID"',
].join("\n");

try {
const result = spawnSync("bash", ["-c", script], { encoding: "utf8", timeout: 5000 });
expect(result.status, result.stderr).toBe(0);
expect(fs.readFileSync(gatewayLog, "utf8")).toBe("unset\n");
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
Comment on lines +16 to +42

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Test the log-mode effect.

The test uses an empty gateway log for both modes. It passes if append truncates the log or if truncate appends to it. Seed the log and assert the distinct final contents.

Proposed test update
     const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-gateway-token-env-"));
     const gatewayLog = path.join(tmpDir, "gateway.log");
+    fs.writeFileSync(gatewayLog, "existing\n");
     const source = fs.readFileSync(START_SCRIPT, "utf8");
@@
-      expect(fs.readFileSync(gatewayLog, "utf8")).toBe("unset\n");
+      expect(fs.readFileSync(gatewayLog, "utf8")).toBe(
+        logMode === "append" ? "existing\nunset\n" : "unset\n",
+      );

As per path instructions, “Review tests for behavioral confidence rather than implementation lock-in.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it.each([
"truncate",
"append",
])("removes the dashboard token from a %s gateway launch (#8693)", (logMode) => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-gateway-token-env-"));
const gatewayLog = path.join(tmpDir, "gateway.log");
const source = fs.readFileSync(START_SCRIPT, "utf8");
const launch = extractShellFunctionFromSource(
source,
"launch_openclaw_gateway_process",
).replaceAll("/tmp/gateway.log", gatewayLog);
const script = [
"set -euo pipefail",
launch,
"export OPENCLAW_GATEWAY_TOKEN=dashboard-secret",
`launch_openclaw_gateway_process ${logMode} sh -c 'printf "%s\\n" "\${OPENCLAW_GATEWAY_TOKEN-unset}"'`,
'wait "$GATEWAY_PID"',
].join("\n");
try {
const result = spawnSync("bash", ["-c", script], { encoding: "utf8", timeout: 5000 });
expect(result.status, result.stderr).toBe(0);
expect(fs.readFileSync(gatewayLog, "utf8")).toBe("unset\n");
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
it.each([
"truncate",
"append",
])("removes the dashboard token from a %s gateway launch (#8693)", (logMode) => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-gateway-token-env-"));
const gatewayLog = path.join(tmpDir, "gateway.log");
fs.writeFileSync(gatewayLog, "existing\n");
const source = fs.readFileSync(START_SCRIPT, "utf8");
const launch = extractShellFunctionFromSource(
source,
"launch_openclaw_gateway_process",
).replaceAll("/tmp/gateway.log", gatewayLog);
const script = [
"set -euo pipefail",
launch,
"export OPENCLAW_GATEWAY_TOKEN=dashboard-secret",
`launch_openclaw_gateway_process ${logMode} sh -c 'printf "%s\\n" "\${OPENCLAW_GATEWAY_TOKEN-unset}"'`,
'wait "$GATEWAY_PID"',
].join("\n");
try {
const result = spawnSync("bash", ["-c", script], { encoding: "utf8", timeout: 5000 });
expect(result.status, result.stderr).toBe(0);
expect(fs.readFileSync(gatewayLog, "utf8")).toBe(
logMode === "append" ? "existing\nunset\n" : "unset\n",
);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
🧰 Tools
🪛 ast-grep (0.45.1)

[warning] 21-21: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(START_SCRIPT, "utf8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)


[warning] 37-37: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(gatewayLog, "utf8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/nemoclaw-start-gateway-token-env.test.ts` around lines 16 - 42, Update
the test around launch_openclaw_gateway_process to seed gatewayLog with existing
content before launching, then assert mode-specific final contents: truncate
must replace the seed with the command output, while append must preserve the
seed and add the output. Keep the existing token-removal assertion and cleanup
behavior unchanged.

Source: Path instructions


it("rejects an unknown gateway log mode before launch (#8693)", () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-gateway-token-env-"));
const gatewayLog = path.join(tmpDir, "gateway.log");
const source = fs.readFileSync(START_SCRIPT, "utf8");
const launch = extractShellFunctionFromSource(
source,
"launch_openclaw_gateway_process",
).replaceAll("/tmp/gateway.log", gatewayLog);
const result = spawnSync(
"bash",
["-c", [launch, "launch_openclaw_gateway_process invalid true"].join("\n")],
{ encoding: "utf8", timeout: 5000 },
);

try {
expect(result.status).toBe(1);
expect(result.stderr).toContain("invalid gateway log mode: invalid");
expect(fs.existsSync(gatewayLog)).toBe(false);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
});
Loading