diff --git a/nemoclaw-blueprint/orchestrator/runner.py b/nemoclaw-blueprint/orchestrator/runner.py index 432c228c3be..4c2293bd712 100644 --- a/nemoclaw-blueprint/orchestrator/runner.py +++ b/nemoclaw-blueprint/orchestrator/runner.py @@ -17,6 +17,7 @@ import argparse import json import os +import re import shutil import subprocess import sys @@ -72,6 +73,19 @@ def openshell_available() -> bool: return shutil.which("openshell") is not None +def normalize_sandbox_name(name: object) -> str: + """Normalize sandbox name to lowercase and validate it.""" + if not isinstance(name, str): + raise ValueError(f"Invalid sandbox name: expected string, got {type(name).__name__!r}.") + normalized = name.lower() + # Only lowercase letters, numbers, and hyphens allowed + if not re.match(r'^[a-z0-9-]+$', normalized): + raise ValueError(f"Invalid sandbox name: '{name}'. Only lowercase letters, numbers, and hyphens are allowed.") + if len(normalized) > 64: + raise ValueError(f"Sandbox name too long: '{name}'. Must be 64 characters or less.") + return normalized + + # --------------------------------------------------------------------------- # Actions # --------------------------------------------------------------------------- @@ -109,12 +123,18 @@ def action_plan( if endpoint_url: inference_cfg = {**inference_cfg, "endpoint": endpoint_url} + # Normalize sandbox name to lowercase + try: + sandbox_name = normalize_sandbox_name(sandbox_cfg.get("name", "openclaw")) + except ValueError as exc: + sys.exit(f"Error: {exc}") + plan: dict[str, Any] = { "run_id": rid, "profile": profile, "sandbox": { "image": sandbox_cfg.get("image", "openclaw"), - "name": sandbox_cfg.get("name", "openclaw"), + "name": sandbox_name, "forward_ports": sandbox_cfg.get("forward_ports", [18789]), }, "inference": { @@ -160,7 +180,11 @@ def action_apply( sandbox_cfg: dict[str, Any] = blueprint.get("components", {}).get("sandbox", {}) - sandbox_name: str = sandbox_cfg.get("name", "openclaw") + # Normalize sandbox name to lowercase + try: + sandbox_name: str = normalize_sandbox_name(sandbox_cfg.get("name", "openclaw")) + except ValueError as exc: + sys.exit(f"Error: {exc}") sandbox_image: str = sandbox_cfg.get("image", "openclaw") forward_ports: list[int] = sandbox_cfg.get("forward_ports", [18789]) @@ -282,7 +306,11 @@ def action_rollback(rid: str) -> None: plan_file = state_dir / "plan.json" if plan_file.exists(): plan = json.loads(plan_file.read_text()) - sandbox_name = plan.get("sandbox_name", "openclaw") + # Normalize sandbox name from plan (in case it was saved before normalization) + try: + sandbox_name = normalize_sandbox_name(plan.get("sandbox_name", "openclaw")) + except ValueError as exc: + sys.exit(f"Error: {exc}") progress(30, f"Stopping sandbox {sandbox_name}") run_cmd( diff --git a/nemoclaw/dist/index.d.ts b/nemoclaw/dist/index.d.ts index 1ad200e605c..a48e982213e 100644 --- a/nemoclaw/dist/index.d.ts +++ b/nemoclaw/dist/index.d.ts @@ -121,6 +121,11 @@ export interface NemoClawConfig { sandboxName: string; inferenceProvider: string; } +/** + * Normalizes a sandbox name to lowercase and validates it. + * Returns the normalized name or the default if invalid. + */ +export declare function normalizeSandboxName(name: string | unknown, defaultName: string): string; export declare function getPluginConfig(api: OpenClawPluginApi): NemoClawConfig; export default function register(api: OpenClawPluginApi): void; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/nemoclaw/dist/index.d.ts.map b/nemoclaw/dist/index.d.ts.map index be261bffe08..aa8dcf50cca 100644 --- a/nemoclaw/dist/index.d.ts.map +++ b/nemoclaw/dist/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAazC,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC7B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,0CAA0C;AAC1C,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,gDAAgD;AAChD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,iDAAiD;AACjD,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,8CAA8C;AAC9C,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,CAAC,GAAG,EAAE,oBAAoB,KAAK,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;CAC5F;AAED,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,cAAc,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,mCAAmC;AACnC,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEjF,yCAAyC;AACzC,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,iDAAiD;AACjD,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,2BAA2B;AAC3B,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,kBAAkB,EAAE,CAAC;CACnC;AAED,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,IAAI,EAAE,kBAAkB,EAAE,CAAC;CAC5B;AAED,uCAAuC;AACvC,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,CAAC,GAAG,EAAE;QAAE,MAAM,EAAE,cAAc,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvF,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE;QAAE,MAAM,EAAE,cAAc,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxF;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,cAAc,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,MAAM,EAAE,YAAY,CAAC;IACrB,eAAe,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAC;IAC5D,WAAW,EAAE,CAAC,SAAS,EAAE,kBAAkB,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,KAAK,IAAI,CAAC;IACrF,gBAAgB,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAC;IACrD,eAAe,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAClD,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACvC,EAAE,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,KAAK,IAAI,CAAC;CACvE;AAMD,MAAM,WAAW,cAAc;IAC7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAoED,wBAAgB,eAAe,CAAC,GAAG,EAAE,iBAAiB,GAAG,cAAc,CAoBtE;AAMD,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,GAAG,EAAE,iBAAiB,GAAG,IAAI,CAoC7D"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/nemoclaw/dist/index.js b/nemoclaw/dist/index.js index 058f2303085..64d587f5d61 100644 --- a/nemoclaw/dist/index.js +++ b/nemoclaw/dist/index.js @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 Object.defineProperty(exports, "__esModule", { value: true }); +exports.normalizeSandboxName = normalizeSandboxName; exports.getPluginConfig = getPluginConfig; exports.default = register; const cli_js_1 = require("./cli.js"); @@ -64,6 +65,25 @@ const DEFAULT_PLUGIN_CONFIG = { sandboxName: "openclaw", inferenceProvider: "nvidia", }; +/** + * Normalizes a sandbox name to lowercase and validates it. + * Returns the normalized name or the default if invalid. + */ +function normalizeSandboxName(name, defaultName) { + if (typeof name !== "string" || !name) { + return defaultName; + } + // Normalize to lowercase + const normalized = name.toLowerCase(); + // Validate: only lowercase letters, numbers, and hyphens allowed + if (!/^[a-z0-9-]+$/.test(normalized)) { + return defaultName; + } + if (normalized.length > 64) { + return defaultName; + } + return normalized; +} function getPluginConfig(api) { const raw = api.pluginConfig ?? {}; return { @@ -73,9 +93,7 @@ function getPluginConfig(api) { blueprintRegistry: typeof raw["blueprintRegistry"] === "string" ? raw["blueprintRegistry"] : DEFAULT_PLUGIN_CONFIG.blueprintRegistry, - sandboxName: typeof raw["sandboxName"] === "string" - ? raw["sandboxName"] - : DEFAULT_PLUGIN_CONFIG.sandboxName, + sandboxName: normalizeSandboxName(raw["sandboxName"], DEFAULT_PLUGIN_CONFIG.sandboxName), inferenceProvider: typeof raw["inferenceProvider"] === "string" ? raw["inferenceProvider"] : DEFAULT_PLUGIN_CONFIG.inferenceProvider, diff --git a/nemoclaw/dist/index.js.map b/nemoclaw/dist/index.js.map index 1c22a36ec3e..c887d000e0c 100644 --- a/nemoclaw/dist/index.js.map +++ b/nemoclaw/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,mGAAmG;AACnG,sCAAsC;;AA0NtC,oDAcC;AAED,0CAoBC;AAMD,2BAoCC;AA3RD,qCAA+C;AAC/C,kDAAyD;AACzD,mDAI6B;AAiI7B,SAAS,kBAAkB,CAAC,UAAgD;IAC1E,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,CAAC;QACvB,OAAO;YACL;gBACE,EAAE,EAAE,mCAAmC;gBACvC,KAAK,EAAE,oCAAoC;gBAC3C,aAAa,EAAE,MAAM;gBACrB,SAAS,EAAE,IAAI;aAChB;YACD;gBACE,EAAE,EAAE,yCAAyC;gBAC7C,KAAK,EAAE,qBAAqB;gBAC5B,aAAa,EAAE,MAAM;gBACrB,SAAS,EAAE,IAAI;aAChB;YACD;gBACE,EAAE,EAAE,0CAA0C;gBAC9C,KAAK,EAAE,yBAAyB;gBAChC,aAAa,EAAE,MAAM;gBACrB,SAAS,EAAE,IAAI;aAChB;YACD;gBACE,EAAE,EAAE,gCAAgC;gBACpC,KAAK,EAAE,qBAAqB;gBAC5B,aAAa,EAAE,MAAM;gBACrB,SAAS,EAAE,IAAI;aAChB;SACF,CAAC;IACJ,CAAC;IAED,OAAO;QACL;YACE,EAAE,EAAE,aAAa,UAAU,CAAC,KAAK,EAAE;YACnC,KAAK,EAAE,UAAU,CAAC,KAAK;YACvB,aAAa,EAAE,MAAM;YACrB,SAAS,EAAE,IAAI;SAChB;KACF,CAAC;AACJ,CAAC;AAED,SAAS,2BAA2B,CAClC,UAAgD,EAChD,qBAA6B;IAE7B,MAAM,SAAS,GACb,qBAAqB,KAAK,gBAAgB;QACxC,CAAC,CAAC,mBAAmB,qBAAqB,GAAG;QAC7C,CAAC,CAAC,mBAAmB,qBAAqB,GAAG,CAAC;IAElD,OAAO;QACL,EAAE,EAAE,WAAW;QACf,KAAK,EAAE,yBAAyB;QAChC,OAAO,EAAE,CAAC,iBAAiB,EAAE,UAAU,CAAC;QACxC,OAAO,EAAE,CAAC,qBAAqB,CAAC;QAChC,MAAM,EAAE,EAAE,IAAI,EAAE,kBAAkB,CAAC,UAAU,CAAC,EAAE;QAChD,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,qBAAqB,EAAE,UAAU,EAAE,eAAe,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;KACzG,CAAC;AACJ,CAAC;AAED,MAAM,qBAAqB,GAAmB;IAC5C,gBAAgB,EAAE,QAAQ;IAC1B,iBAAiB,EAAE,mCAAmC;IACtD,WAAW,EAAE,UAAU;IACvB,iBAAiB,EAAE,QAAQ;CAC5B,CAAC;AAEF;;;GAGG;AACH,SAAgB,oBAAoB,CAAC,IAAsB,EAAE,WAAmB;IAC9E,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QACtC,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,yBAAyB;IACzB,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACtC,iEAAiE;IACjE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACrC,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QAC3B,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAgB,eAAe,CAAC,GAAsB;IACpD,MAAM,GAAG,GAAG,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC;IACnC,OAAO;QACL,gBAAgB,EACd,OAAO,GAAG,CAAC,kBAAkB,CAAC,KAAK,QAAQ;YACzC,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC;YACzB,CAAC,CAAC,qBAAqB,CAAC,gBAAgB;QAC5C,iBAAiB,EACf,OAAO,GAAG,CAAC,mBAAmB,CAAC,KAAK,QAAQ;YAC1C,CAAC,CAAC,GAAG,CAAC,mBAAmB,CAAC;YAC1B,CAAC,CAAC,qBAAqB,CAAC,iBAAiB;QAC7C,WAAW,EAAE,oBAAoB,CAC/B,GAAG,CAAC,aAAa,CAAC,EAClB,qBAAqB,CAAC,WAAW,CAClC;QACD,iBAAiB,EACf,OAAO,GAAG,CAAC,mBAAmB,CAAC,KAAK,QAAQ;YAC1C,CAAC,CAAC,GAAG,CAAC,mBAAmB,CAAC;YAC1B,CAAC,CAAC,qBAAqB,CAAC,iBAAiB;KAC9C,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E,SAAwB,QAAQ,CAAC,GAAsB;IACrD,uDAAuD;IACvD,GAAG,CAAC,eAAe,CAAC;QAClB,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,8CAA8C;QAC3D,WAAW,EAAE,IAAI;QACjB,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAA,6BAAkB,EAAC,GAAG,EAAE,GAAG,CAAC;KAC/C,CAAC,CAAC;IAEH,iEAAiE;IACjE,GAAG,CAAC,WAAW,CACb,CAAC,MAAM,EAAE,EAAE;QACT,IAAA,4BAAmB,EAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC,CAAC,EACD,EAAE,QAAQ,EAAE,CAAC,UAAU,CAAC,EAAE,CAC3B,CAAC;IAEF,oEAAoE;IACpE,MAAM,UAAU,GAAG,IAAA,6BAAiB,GAAE,CAAC;IACvC,MAAM,qBAAqB,GAAG,UAAU,EAAE,aAAa,IAAI,gBAAgB,CAAC;IAC5E,GAAG,CAAC,gBAAgB,CAAC,2BAA2B,CAAC,UAAU,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAErF,MAAM,cAAc,GAAG,UAAU,CAAC,CAAC,CAAC,IAAA,mCAAuB,EAAC,UAAU,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC;IAC7F,MAAM,cAAc,GAAG,UAAU,CAAC,CAAC,CAAC,IAAA,mCAAuB,EAAC,UAAU,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC;IAC7F,MAAM,WAAW,GAAG,UAAU,EAAE,KAAK,IAAI,mCAAmC,CAAC;IAE7E,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;IAC7E,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;IAC7E,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;IAC7E,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACjE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACjE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAC9D,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;IAC7E,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;IAC7E,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACtB,CAAC"} \ No newline at end of file diff --git a/nemoclaw/src/index.ts b/nemoclaw/src/index.ts index f6defcd41b5..0b77ee44c11 100644 --- a/nemoclaw/src/index.ts +++ b/nemoclaw/src/index.ts @@ -213,6 +213,26 @@ const DEFAULT_PLUGIN_CONFIG: NemoClawConfig = { inferenceProvider: "nvidia", }; +/** + * Normalizes a sandbox name to lowercase and validates it. + * Returns the normalized name or the default if invalid. + */ +export function normalizeSandboxName(name: string | unknown, defaultName: string): string { + if (typeof name !== "string" || !name) { + return defaultName; + } + // Normalize to lowercase + const normalized = name.toLowerCase(); + // Validate: only lowercase letters, numbers, and hyphens allowed + if (!/^[a-z0-9-]+$/.test(normalized)) { + return defaultName; + } + if (normalized.length > 64) { + return defaultName; + } + return normalized; +} + export function getPluginConfig(api: OpenClawPluginApi): NemoClawConfig { const raw = api.pluginConfig ?? {}; return { @@ -224,10 +244,10 @@ export function getPluginConfig(api: OpenClawPluginApi): NemoClawConfig { typeof raw["blueprintRegistry"] === "string" ? raw["blueprintRegistry"] : DEFAULT_PLUGIN_CONFIG.blueprintRegistry, - sandboxName: - typeof raw["sandboxName"] === "string" - ? raw["sandboxName"] - : DEFAULT_PLUGIN_CONFIG.sandboxName, + sandboxName: normalizeSandboxName( + raw["sandboxName"], + DEFAULT_PLUGIN_CONFIG.sandboxName, + ), inferenceProvider: typeof raw["inferenceProvider"] === "string" ? raw["inferenceProvider"] diff --git a/test/sandbox-name.test.js b/test/sandbox-name.test.js new file mode 100644 index 00000000000..d0a0d932e1e --- /dev/null +++ b/test/sandbox-name.test.js @@ -0,0 +1,201 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const { execSync } = require("child_process"); +const path = require("path"); +const fs = require("fs"); +const os = require("os"); + +// Path to nemoclaw dist directory +const NEMOCLAW_DIST_PATH = path.join(__dirname, "..", "nemoclaw", "dist"); +const RUNNER_PY_PATH = path.join(__dirname, "..", "nemoclaw-blueprint", "orchestrator", "runner.py"); + +describe("Sandbox Name Normalization", () => { + describe("TypeScript normalizeSandboxName (nemoclaw/src/index.ts)", () => { + const indexPath = path.join(NEMOCLAW_DIST_PATH, "index.js"); + + function runNormalizeSandboxName(name, defaultName = "openclaw") { + // Create a temporary test script file + const testScriptPath = path.join(os.tmpdir(), `test-sandbox-${Date.now()}.js`); + const scriptContent = ` +const { normalizeSandboxName } = require('${indexPath.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'); +const result = normalizeSandboxName(${JSON.stringify(name)}, ${JSON.stringify(defaultName)}); +console.log(JSON.stringify(result)); +`; + fs.writeFileSync(testScriptPath, scriptContent); + try { + const result = execSync(`node "${testScriptPath}"`, { encoding: "utf-8", cwd: path.join(__dirname, "..") }); + return JSON.parse(result.trim()); + } finally { + fs.unlinkSync(testScriptPath); + } + } + + it("accepts valid lowercase names", () => { + const result = runNormalizeSandboxName("my-assistant"); + assert.equal(result, "my-assistant"); + }); + + it("normalizes uppercase letters to lowercase", () => { + const result = runNormalizeSandboxName("My-Assistant"); + assert.equal(result, "my-assistant"); + }); + + it("normalizes all uppercase names to lowercase", () => { + const result = runNormalizeSandboxName("MY-ASSISTANT"); + assert.equal(result, "my-assistant"); + }); + + it("accepts names with numbers", () => { + const result = runNormalizeSandboxName("assistant-123"); + assert.equal(result, "assistant-123"); + }); + + it("accepts names starting with numbers", () => { + const result = runNormalizeSandboxName("123-assistant"); + assert.equal(result, "123-assistant"); + }); + + it("returns default for names with special characters", () => { + const result = runNormalizeSandboxName("my_assistant", "default-sandbox"); + assert.equal(result, "default-sandbox"); + }); + + it("returns default for names with spaces", () => { + const result = runNormalizeSandboxName("my assistant", "default-sandbox"); + assert.equal(result, "default-sandbox"); + }); + + it("returns default for empty names", () => { + const result = runNormalizeSandboxName("", "default-sandbox"); + assert.equal(result, "default-sandbox"); + }); + + it("returns default for names longer than 64 characters", () => { + const longName = "a".repeat(65); + const result = runNormalizeSandboxName(longName, "default-sandbox"); + assert.equal(result, "default-sandbox"); + }); + + it("accepts names exactly 64 characters", () => { + const name64 = "a".repeat(64); + const result = runNormalizeSandboxName(name64); + assert.equal(result, name64); + }); + + it("normalizes mixed case with numbers and hyphens", () => { + const result = runNormalizeSandboxName("My-Assistant-123-Test"); + assert.equal(result, "my-assistant-123-test"); + }); + + it("returns default for non-string input", () => { + const result = runNormalizeSandboxName(null, "default-sandbox"); + assert.equal(result, "default-sandbox"); + }); + }); + + describe("Python normalize_sandbox_name (runner.py)", () => { + function runNormalizeSandboxName(name) { + const testScriptPath = path.join(os.tmpdir(), `test-sandbox-py-${Date.now()}.py`); + const scriptContent = ` +import sys +sys.path.insert(0, '${path.dirname(RUNNER_PY_PATH).replace(/\\/g, '\\\\').replace(/'/g, "\\'")}') +from runner import normalize_sandbox_name +result = normalize_sandbox_name(${JSON.stringify(name)}) +print(result) +`; + fs.writeFileSync(testScriptPath, scriptContent); + try { + return execSync(`python3 "${testScriptPath}"`, { encoding: "utf-8" }).trim(); + } finally { + fs.unlinkSync(testScriptPath); + } + } + + function runNormalizeSandboxNameError(name) { + const testScriptPath = path.join(os.tmpdir(), `test-sandbox-py-err-${Date.now()}.py`); + // Handle null specially since JSON.stringify(null) becomes "null" not None in Python + const pythonName = name === null ? "None" : JSON.stringify(name); + const scriptContent = ` +import sys +sys.path.insert(0, '${path.dirname(RUNNER_PY_PATH).replace(/\\/g, '\\\\').replace(/'/g, "\\'")}') +from runner import normalize_sandbox_name +try: + normalize_sandbox_name(${pythonName}) + print("NO_ERROR") +except ValueError as e: + print("ERROR: " + str(e)) +except Exception as e: + print("ERROR: " + str(e)) +`; + fs.writeFileSync(testScriptPath, scriptContent); + try { + return execSync(`python3 "${testScriptPath}"`, { encoding: "utf-8" }).trim(); + } finally { + fs.unlinkSync(testScriptPath); + } + } + + it("accepts valid lowercase names", () => { + const result = runNormalizeSandboxName("my-assistant"); + assert.equal(result, "my-assistant"); + }); + + it("normalizes uppercase letters to lowercase", () => { + const result = runNormalizeSandboxName("My-Assistant"); + assert.equal(result, "my-assistant"); + }); + + it("normalizes all uppercase names to lowercase", () => { + const result = runNormalizeSandboxName("MY-ASSISTANT"); + assert.equal(result, "my-assistant"); + }); + + it("accepts names with numbers", () => { + const result = runNormalizeSandboxName("assistant-123"); + assert.equal(result, "assistant-123"); + }); + + it("accepts names starting with numbers", () => { + const result = runNormalizeSandboxName("123-assistant"); + assert.equal(result, "123-assistant"); + }); + + it("rejects names with special characters", () => { + const result = runNormalizeSandboxNameError("my_assistant"); + assert.ok(result.startsWith("ERROR:")); + assert.ok(result.includes("lowercase letters, numbers, and hyphens")); + }); + + it("rejects names with spaces", () => { + const result = runNormalizeSandboxNameError("my assistant"); + assert.ok(result.startsWith("ERROR:")); + }); + + it("rejects non-string input", () => { + const result = runNormalizeSandboxNameError(null); + assert.ok(result.startsWith("ERROR:")); + assert.ok(result.includes("expected") || result.includes("Invalid")); + }); + + it("rejects names longer than 64 characters", () => { + const longName = "a".repeat(65); + const result = runNormalizeSandboxNameError(longName); + assert.ok(result.startsWith("ERROR:")); + assert.ok(result.includes("64 characters")); + }); + + it("accepts names exactly 64 characters", () => { + const name64 = "a".repeat(64); + const result = runNormalizeSandboxName(name64); + assert.equal(result, name64); + }); + + it("normalizes mixed case with numbers and hyphens", () => { + const result = runNormalizeSandboxName("My-Assistant-123-Test"); + assert.equal(result, "my-assistant-123-test"); + }); + }); +});