diff --git a/Dockerfile b/Dockerfile index 8526c8bdda6..fcd6c3bfcab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1490,8 +1490,13 @@ RUN managed_runtime_assertion_failed() { \ nemoclaw_assertion="$1"; \ nemoclaw_artifact_path="$2"; \ if [ -e "$nemoclaw_artifact_path" ] || [ -L "$nemoclaw_artifact_path" ]; then \ - nemoclaw_metadata="$(stat -c 'uid=%u gid=%g type=%F mode=%a' -- "$nemoclaw_artifact_path" 2>/dev/null)" \ - || nemoclaw_metadata='uid=unavailable gid=unavailable type=unavailable mode=unavailable'; \ + if [ "${3:-}" = dereference ] && [ -e "$nemoclaw_artifact_path" ]; then \ + nemoclaw_metadata="$(stat -L -c 'uid=%u gid=%g type=%F mode=%a' -- "$nemoclaw_artifact_path" 2>/dev/null)" \ + || nemoclaw_metadata='uid=unavailable gid=unavailable type=unavailable mode=unavailable'; \ + else \ + nemoclaw_metadata="$(stat -c 'uid=%u gid=%g type=%F mode=%a' -- "$nemoclaw_artifact_path" 2>/dev/null)" \ + || nemoclaw_metadata='uid=unavailable gid=unavailable type=unavailable mode=unavailable'; \ + fi; \ if [ -L "$nemoclaw_artifact_path" ]; then nemoclaw_symlink_state='yes'; else nemoclaw_symlink_state='no'; fi; \ else \ nemoclaw_metadata='uid=unavailable gid=unavailable type=missing mode=unavailable'; \ @@ -1501,10 +1506,20 @@ RUN managed_runtime_assertion_failed() { \ "$nemoclaw_assertion" "$nemoclaw_artifact_path" "$nemoclaw_metadata" "$nemoclaw_symlink_state" >&2; \ exit 1; \ }; \ + managed_image_command_failed() { \ + nemoclaw_command_assertion="$1"; \ + nemoclaw_command_status="$2"; \ + printf 'ERROR: managed image assertion failed: %s exit-status=%s\n' \ + "$nemoclaw_command_assertion" "$nemoclaw_command_status" >&2; \ + exit 1; \ + }; \ discovery_contract="$(node /usr/local/lib/nemoclaw/mcp-tool-discovery-runtime/mcp-tool-discovery.mjs)" \ - && node -e "const result = JSON.parse(process.argv[1]); if (result.protocol !== 1 || result.ok !== false || result.detail !== \"tool discovery received invalid runtime arguments\") process.exit(1);" "$discovery_contract" \ - && discovery_unsafe="$(find -L /usr/local/lib/nemoclaw/mcp-tool-discovery-runtime \( ! -user root -o -perm /022 \) -print -quit)" \ - && test -z "$discovery_unsafe" \ + || managed_image_command_failed mcp-tool-discovery-bundle-execution "$?"; \ + node -e 'const expected = { protocol: 1, ok: false, detail: "tool discovery received invalid runtime arguments" }; const standaloneSecretPatterns = [/(?:nvapi-|nvcf-|gh[pousr]_|sk-proj-|sk-ant-|hf_|glpat-|gsk_|pypi-|tvly-)[A-Za-z0-9_-]{10,}/gu, /github_pat_[A-Za-z0-9_]{30,}/gu, /sk-[A-Za-z0-9_-]{20,}/gu, /(?:xox[bpas]|xapp)-[A-Za-z0-9-]{10,}/gu, /A(?:K|S)IA[A-Z0-9]{16}/gu, /\bbot\d{8,10}:[A-Za-z0-9_-]{35}\b/gu, /\b\d{8,10}:[A-Za-z0-9_-]{35}\b/gu, /\b[A-Za-z0-9]{24}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27,}\b/gu, /lsv2_(?:pt|sk)_[A-Za-z0-9]{10,}(?:_[A-Za-z0-9]+)*/gu, /\beyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{2,}\.[A-Za-z0-9_-]{10,}\b/gu, /\b[A-Za-z0-9_=-]{32,}\b/gu]; const redactContextSecrets = (value) => value.replace(/\b(?:Bearer|Basic)\s+\S+/giu, "").replace(/((?:^|[^A-Za-z0-9])(?:[A-Za-z0-9]{1,128}_(?:KEY|TOKEN|SECRET|CREDENTIAL|PASSWORD|PASSWD|PASS)|(?:X[-_])?API[-_]KEY|TOKEN|SECRET|CREDENTIAL|PASSWORD|PASSWD|PASS)["\x27]?(?:[ \t]{0,32}[=:][ \t]{0,32}|[ \t]{1,32})["\x27]?)[^\s"\x27]{10,}/giu, (_match, prefix) => prefix + "").replace(/((?:^|[^A-Za-z0-9])(?:[A-Za-z0-9]{1,128}(?:Token|Secret|Credential)|[A-Za-z0-9]{0,128}(?:[Aa]ccess|[Rr]efresh|[Cc]lient|[Bb]earer|[Aa]uth|[Aa][Pp][Ii]|[Pp]rivate|[Ss]igning|[Ss]ession|[Bb]ot|[Aa]pp|[Rr]esolved)Key|[A-Za-z0-9]{1,128}(?:Password|Passwd|Pass))["\x27]?(?:[ \t]{0,32}[=:][ \t]{0,32}|[ \t]{1,32})["\x27]?)[^\s"\x27]{10,}/gu, (_match, prefix) => prefix + "").replace(/((?:^|[^A-Za-z0-9])KEY["\x27]?(?:[ \t]{0,32}[=:][ \t]{0,32}|[ \t]{1,32})["\x27]?)[^\s"\x27]{10,}/gu, (_match, prefix) => prefix + ""); const sanitize = (value) => { if (value === undefined) return ""; if (value === null || typeof value === "boolean" || typeof value === "number") return value; if (typeof value !== "string") return "<" + (Array.isArray(value) ? "array" : typeof value) + ">"; let printable = value.replace(/-----BEGIN (?:[A-Z0-9]+ )?PRIVATE KEY-----[\s\S]*/gu, "").replace(/[^\x20-\x7e]/gu, "?"); for (const pattern of standaloneSecretPatterns) printable = printable.replace(pattern, ""); printable = redactContextSecrets(printable); return printable.length <= 240 ? printable : printable.slice(0, 237) + "..."; }; let result; let parsed = true; try { result = JSON.parse(process.argv[1]); } catch { parsed = false; } const record = parsed && result !== null && typeof result === "object" && !Array.isArray(result) ? result : undefined; if (record && record.protocol === expected.protocol && record.ok === expected.ok && record.detail === expected.detail) process.exit(0); const actual = record ? { protocol: sanitize(record.protocol), ok: sanitize(record.ok), detail: sanitize(record.detail) } : parsed ? { type: result === null ? "null" : Array.isArray(result) ? "array" : typeof result, value: sanitize(result) } : { type: "invalid-json", preview: sanitize(process.argv[1]) }; console.error("ERROR: managed image assertion failed: mcp-tool-discovery-json-contract actual=%s expected=%s", JSON.stringify(actual), JSON.stringify(expected)); process.exit(1);' "$discovery_contract" \ + || exit 1; \ + discovery_unsafe="$(find -L /usr/local/lib/nemoclaw/mcp-tool-discovery-runtime \( ! -user root -o -perm /022 \) -print -quit)" \ + || managed_image_command_failed mcp-tool-discovery-tree-find-execution "$?"; \ + { test -z "$discovery_unsafe" || managed_runtime_assertion_failed mcp-tool-discovery-tree-safety "$discovery_unsafe" dereference; } \ && { test -f /usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs || managed_runtime_assertion_failed regular-file /usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs; } \ && { test ! -L /usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs || managed_runtime_assertion_failed non-symlink /usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs; } \ && { chown root:root /usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs 2>/dev/null || managed_runtime_assertion_failed owner-root-root /usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs; } \ diff --git a/agents/hermes/Dockerfile b/agents/hermes/Dockerfile index 307fb207287..3e4cefe3e3b 100644 --- a/agents/hermes/Dockerfile +++ b/agents/hermes/Dockerfile @@ -352,8 +352,13 @@ RUN managed_runtime_assertion_failed() { \ nemoclaw_assertion="$1"; \ nemoclaw_artifact_path="$2"; \ if [ -e "$nemoclaw_artifact_path" ] || [ -L "$nemoclaw_artifact_path" ]; then \ - nemoclaw_metadata="$(stat -c 'uid=%u gid=%g type=%F mode=%a' -- "$nemoclaw_artifact_path" 2>/dev/null)" \ - || nemoclaw_metadata='uid=unavailable gid=unavailable type=unavailable mode=unavailable'; \ + if [ "${3:-}" = dereference ] && [ -e "$nemoclaw_artifact_path" ]; then \ + nemoclaw_metadata="$(stat -L -c 'uid=%u gid=%g type=%F mode=%a' -- "$nemoclaw_artifact_path" 2>/dev/null)" \ + || nemoclaw_metadata='uid=unavailable gid=unavailable type=unavailable mode=unavailable'; \ + else \ + nemoclaw_metadata="$(stat -c 'uid=%u gid=%g type=%F mode=%a' -- "$nemoclaw_artifact_path" 2>/dev/null)" \ + || nemoclaw_metadata='uid=unavailable gid=unavailable type=unavailable mode=unavailable'; \ + fi; \ if [ -L "$nemoclaw_artifact_path" ]; then nemoclaw_symlink_state='yes'; else nemoclaw_symlink_state='no'; fi; \ else \ nemoclaw_metadata='uid=unavailable gid=unavailable type=missing mode=unavailable'; \ @@ -363,10 +368,20 @@ RUN managed_runtime_assertion_failed() { \ "$nemoclaw_assertion" "$nemoclaw_artifact_path" "$nemoclaw_metadata" "$nemoclaw_symlink_state" >&2; \ exit 1; \ }; \ + managed_image_command_failed() { \ + nemoclaw_command_assertion="$1"; \ + nemoclaw_command_status="$2"; \ + printf 'ERROR: managed image assertion failed: %s exit-status=%s\n' \ + "$nemoclaw_command_assertion" "$nemoclaw_command_status" >&2; \ + exit 1; \ + }; \ discovery_contract="$(node /usr/local/lib/nemoclaw/mcp-tool-discovery-runtime/mcp-tool-discovery.mjs)" \ - && node -e 'const result = JSON.parse(process.argv[1]); if (result.protocol !== 1 || result.ok !== false || result.detail !== "tool discovery received invalid runtime arguments") process.exit(1);' "$discovery_contract" \ - && discovery_unsafe="$(find -L /usr/local/lib/nemoclaw/mcp-tool-discovery-runtime \( ! -user root -o -perm /022 \) -print -quit)" \ - && test -z "$discovery_unsafe" \ + || managed_image_command_failed mcp-tool-discovery-bundle-execution "$?"; \ + node -e 'const expected = { protocol: 1, ok: false, detail: "tool discovery received invalid runtime arguments" }; const standaloneSecretPatterns = [/(?:nvapi-|nvcf-|gh[pousr]_|sk-proj-|sk-ant-|hf_|glpat-|gsk_|pypi-|tvly-)[A-Za-z0-9_-]{10,}/gu, /github_pat_[A-Za-z0-9_]{30,}/gu, /sk-[A-Za-z0-9_-]{20,}/gu, /(?:xox[bpas]|xapp)-[A-Za-z0-9-]{10,}/gu, /A(?:K|S)IA[A-Z0-9]{16}/gu, /\bbot\d{8,10}:[A-Za-z0-9_-]{35}\b/gu, /\b\d{8,10}:[A-Za-z0-9_-]{35}\b/gu, /\b[A-Za-z0-9]{24}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27,}\b/gu, /lsv2_(?:pt|sk)_[A-Za-z0-9]{10,}(?:_[A-Za-z0-9]+)*/gu, /\beyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{2,}\.[A-Za-z0-9_-]{10,}\b/gu, /\b[A-Za-z0-9_=-]{32,}\b/gu]; const redactContextSecrets = (value) => value.replace(/\b(?:Bearer|Basic)\s+\S+/giu, "").replace(/((?:^|[^A-Za-z0-9])(?:[A-Za-z0-9]{1,128}_(?:KEY|TOKEN|SECRET|CREDENTIAL|PASSWORD|PASSWD|PASS)|(?:X[-_])?API[-_]KEY|TOKEN|SECRET|CREDENTIAL|PASSWORD|PASSWD|PASS)["\x27]?(?:[ \t]{0,32}[=:][ \t]{0,32}|[ \t]{1,32})["\x27]?)[^\s"\x27]{10,}/giu, (_match, prefix) => prefix + "").replace(/((?:^|[^A-Za-z0-9])(?:[A-Za-z0-9]{1,128}(?:Token|Secret|Credential)|[A-Za-z0-9]{0,128}(?:[Aa]ccess|[Rr]efresh|[Cc]lient|[Bb]earer|[Aa]uth|[Aa][Pp][Ii]|[Pp]rivate|[Ss]igning|[Ss]ession|[Bb]ot|[Aa]pp|[Rr]esolved)Key|[A-Za-z0-9]{1,128}(?:Password|Passwd|Pass))["\x27]?(?:[ \t]{0,32}[=:][ \t]{0,32}|[ \t]{1,32})["\x27]?)[^\s"\x27]{10,}/gu, (_match, prefix) => prefix + "").replace(/((?:^|[^A-Za-z0-9])KEY["\x27]?(?:[ \t]{0,32}[=:][ \t]{0,32}|[ \t]{1,32})["\x27]?)[^\s"\x27]{10,}/gu, (_match, prefix) => prefix + ""); const sanitize = (value) => { if (value === undefined) return ""; if (value === null || typeof value === "boolean" || typeof value === "number") return value; if (typeof value !== "string") return "<" + (Array.isArray(value) ? "array" : typeof value) + ">"; let printable = value.replace(/-----BEGIN (?:[A-Z0-9]+ )?PRIVATE KEY-----[\s\S]*/gu, "").replace(/[^\x20-\x7e]/gu, "?"); for (const pattern of standaloneSecretPatterns) printable = printable.replace(pattern, ""); printable = redactContextSecrets(printable); return printable.length <= 240 ? printable : printable.slice(0, 237) + "..."; }; let result; let parsed = true; try { result = JSON.parse(process.argv[1]); } catch { parsed = false; } const record = parsed && result !== null && typeof result === "object" && !Array.isArray(result) ? result : undefined; if (record && record.protocol === expected.protocol && record.ok === expected.ok && record.detail === expected.detail) process.exit(0); const actual = record ? { protocol: sanitize(record.protocol), ok: sanitize(record.ok), detail: sanitize(record.detail) } : parsed ? { type: result === null ? "null" : Array.isArray(result) ? "array" : typeof result, value: sanitize(result) } : { type: "invalid-json", preview: sanitize(process.argv[1]) }; console.error("ERROR: managed image assertion failed: mcp-tool-discovery-json-contract actual=%s expected=%s", JSON.stringify(actual), JSON.stringify(expected)); process.exit(1);' "$discovery_contract" \ + || exit 1; \ + discovery_unsafe="$(find -L /usr/local/lib/nemoclaw/mcp-tool-discovery-runtime \( ! -user root -o -perm /022 \) -print -quit)" \ + || managed_image_command_failed mcp-tool-discovery-tree-find-execution "$?"; \ + { test -z "$discovery_unsafe" || managed_runtime_assertion_failed mcp-tool-discovery-tree-safety "$discovery_unsafe" dereference; } \ && { test -f /usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs || managed_runtime_assertion_failed regular-file /usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs; } \ && { test ! -L /usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs || managed_runtime_assertion_failed non-symlink /usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs; } \ && { chown root:root /usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs 2>/dev/null || managed_runtime_assertion_failed owner-root-root /usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs; } \ diff --git a/agents/langchain-deepagents-code/Dockerfile b/agents/langchain-deepagents-code/Dockerfile index 914748ac335..b6c97012657 100644 --- a/agents/langchain-deepagents-code/Dockerfile +++ b/agents/langchain-deepagents-code/Dockerfile @@ -102,8 +102,13 @@ RUN managed_runtime_assertion_failed() { \ nemoclaw_assertion="$1"; \ nemoclaw_artifact_path="$2"; \ if [ -e "$nemoclaw_artifact_path" ] || [ -L "$nemoclaw_artifact_path" ]; then \ - nemoclaw_metadata="$(stat -c 'uid=%u gid=%g type=%F mode=%a' -- "$nemoclaw_artifact_path" 2>/dev/null)" \ - || nemoclaw_metadata='uid=unavailable gid=unavailable type=unavailable mode=unavailable'; \ + if [ "${3:-}" = dereference ] && [ -e "$nemoclaw_artifact_path" ]; then \ + nemoclaw_metadata="$(stat -L -c 'uid=%u gid=%g type=%F mode=%a' -- "$nemoclaw_artifact_path" 2>/dev/null)" \ + || nemoclaw_metadata='uid=unavailable gid=unavailable type=unavailable mode=unavailable'; \ + else \ + nemoclaw_metadata="$(stat -c 'uid=%u gid=%g type=%F mode=%a' -- "$nemoclaw_artifact_path" 2>/dev/null)" \ + || nemoclaw_metadata='uid=unavailable gid=unavailable type=unavailable mode=unavailable'; \ + fi; \ if [ -L "$nemoclaw_artifact_path" ]; then nemoclaw_symlink_state='yes'; else nemoclaw_symlink_state='no'; fi; \ else \ nemoclaw_metadata='uid=unavailable gid=unavailable type=missing mode=unavailable'; \ @@ -113,10 +118,20 @@ RUN managed_runtime_assertion_failed() { \ "$nemoclaw_assertion" "$nemoclaw_artifact_path" "$nemoclaw_metadata" "$nemoclaw_symlink_state" >&2; \ exit 1; \ }; \ + managed_image_command_failed() { \ + nemoclaw_command_assertion="$1"; \ + nemoclaw_command_status="$2"; \ + printf 'ERROR: managed image assertion failed: %s exit-status=%s\n' \ + "$nemoclaw_command_assertion" "$nemoclaw_command_status" >&2; \ + exit 1; \ + }; \ discovery_contract="$(node /usr/local/lib/nemoclaw/mcp-tool-discovery-runtime/mcp-tool-discovery.mjs)" \ - && node -e 'const result = JSON.parse(process.argv[1]); if (result.protocol !== 1 || result.ok !== false || result.detail !== "tool discovery received invalid runtime arguments") process.exit(1);' "$discovery_contract" \ - && discovery_unsafe="$(find -L /usr/local/lib/nemoclaw/mcp-tool-discovery-runtime \( ! -user root -o -perm /022 \) -print -quit)" \ - && test -z "$discovery_unsafe" \ + || managed_image_command_failed mcp-tool-discovery-bundle-execution "$?"; \ + node -e 'const expected = { protocol: 1, ok: false, detail: "tool discovery received invalid runtime arguments" }; const standaloneSecretPatterns = [/(?:nvapi-|nvcf-|gh[pousr]_|sk-proj-|sk-ant-|hf_|glpat-|gsk_|pypi-|tvly-)[A-Za-z0-9_-]{10,}/gu, /github_pat_[A-Za-z0-9_]{30,}/gu, /sk-[A-Za-z0-9_-]{20,}/gu, /(?:xox[bpas]|xapp)-[A-Za-z0-9-]{10,}/gu, /A(?:K|S)IA[A-Z0-9]{16}/gu, /\bbot\d{8,10}:[A-Za-z0-9_-]{35}\b/gu, /\b\d{8,10}:[A-Za-z0-9_-]{35}\b/gu, /\b[A-Za-z0-9]{24}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27,}\b/gu, /lsv2_(?:pt|sk)_[A-Za-z0-9]{10,}(?:_[A-Za-z0-9]+)*/gu, /\beyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{2,}\.[A-Za-z0-9_-]{10,}\b/gu, /\b[A-Za-z0-9_=-]{32,}\b/gu]; const redactContextSecrets = (value) => value.replace(/\b(?:Bearer|Basic)\s+\S+/giu, "").replace(/((?:^|[^A-Za-z0-9])(?:[A-Za-z0-9]{1,128}_(?:KEY|TOKEN|SECRET|CREDENTIAL|PASSWORD|PASSWD|PASS)|(?:X[-_])?API[-_]KEY|TOKEN|SECRET|CREDENTIAL|PASSWORD|PASSWD|PASS)["\x27]?(?:[ \t]{0,32}[=:][ \t]{0,32}|[ \t]{1,32})["\x27]?)[^\s"\x27]{10,}/giu, (_match, prefix) => prefix + "").replace(/((?:^|[^A-Za-z0-9])(?:[A-Za-z0-9]{1,128}(?:Token|Secret|Credential)|[A-Za-z0-9]{0,128}(?:[Aa]ccess|[Rr]efresh|[Cc]lient|[Bb]earer|[Aa]uth|[Aa][Pp][Ii]|[Pp]rivate|[Ss]igning|[Ss]ession|[Bb]ot|[Aa]pp|[Rr]esolved)Key|[A-Za-z0-9]{1,128}(?:Password|Passwd|Pass))["\x27]?(?:[ \t]{0,32}[=:][ \t]{0,32}|[ \t]{1,32})["\x27]?)[^\s"\x27]{10,}/gu, (_match, prefix) => prefix + "").replace(/((?:^|[^A-Za-z0-9])KEY["\x27]?(?:[ \t]{0,32}[=:][ \t]{0,32}|[ \t]{1,32})["\x27]?)[^\s"\x27]{10,}/gu, (_match, prefix) => prefix + ""); const sanitize = (value) => { if (value === undefined) return ""; if (value === null || typeof value === "boolean" || typeof value === "number") return value; if (typeof value !== "string") return "<" + (Array.isArray(value) ? "array" : typeof value) + ">"; let printable = value.replace(/-----BEGIN (?:[A-Z0-9]+ )?PRIVATE KEY-----[\s\S]*/gu, "").replace(/[^\x20-\x7e]/gu, "?"); for (const pattern of standaloneSecretPatterns) printable = printable.replace(pattern, ""); printable = redactContextSecrets(printable); return printable.length <= 240 ? printable : printable.slice(0, 237) + "..."; }; let result; let parsed = true; try { result = JSON.parse(process.argv[1]); } catch { parsed = false; } const record = parsed && result !== null && typeof result === "object" && !Array.isArray(result) ? result : undefined; if (record && record.protocol === expected.protocol && record.ok === expected.ok && record.detail === expected.detail) process.exit(0); const actual = record ? { protocol: sanitize(record.protocol), ok: sanitize(record.ok), detail: sanitize(record.detail) } : parsed ? { type: result === null ? "null" : Array.isArray(result) ? "array" : typeof result, value: sanitize(result) } : { type: "invalid-json", preview: sanitize(process.argv[1]) }; console.error("ERROR: managed image assertion failed: mcp-tool-discovery-json-contract actual=%s expected=%s", JSON.stringify(actual), JSON.stringify(expected)); process.exit(1);' "$discovery_contract" \ + || exit 1; \ + discovery_unsafe="$(find -L /usr/local/lib/nemoclaw/mcp-tool-discovery-runtime \( ! -user root -o -perm /022 \) -print -quit)" \ + || managed_image_command_failed mcp-tool-discovery-tree-find-execution "$?"; \ + { test -z "$discovery_unsafe" || managed_runtime_assertion_failed mcp-tool-discovery-tree-safety "$discovery_unsafe" dereference; } \ && { test -f /usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs || managed_runtime_assertion_failed regular-file /usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs; } \ && { test ! -L /usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs || managed_runtime_assertion_failed non-symlink /usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs; } \ && { chown root:root /usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs 2>/dev/null || managed_runtime_assertion_failed owner-root-root /usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs; } \ diff --git a/test/support/managed-bootstrap-image-contract.ts b/test/support/managed-bootstrap-image-contract.ts index 4b12be7c2ef..cb9507658aa 100644 --- a/test/support/managed-bootstrap-image-contract.ts +++ b/test/support/managed-bootstrap-image-contract.ts @@ -31,6 +31,10 @@ const COMPILER_FLAGS = [ const MANAGED_BOOTSTRAP_BUILDER_IMAGE = "node:22-trixie@sha256:a566dd560283ae5615c8bb86b58fa8a1b6f3c82b492473a061672416266625da"; +const DISCOVERY_RUNTIME_ROOT = "/usr/local/lib/nemoclaw/mcp-tool-discovery-runtime"; +const DISCOVERY_RUNTIME_PATH = `${DISCOVERY_RUNTIME_ROOT}/mcp-tool-discovery.mjs`; +const DISCOVERY_EXPECTED_CONTRACT = + '{"protocol":1,"ok":false,"detail":"tool discovery received invalid runtime arguments"}'; const MANAGED_STARTUP_RUNTIME_PATH = "/usr/local/lib/nemoclaw/managed-startup-image-runtime.cjs"; function expectManagedRuntimeDiagnostic(dockerfile: string): void { @@ -40,20 +44,34 @@ function expectManagedRuntimeDiagnostic(dockerfile: string): void { instruction.body.includes("managed_runtime_assertion_failed()"), ); expect(instructions).toHaveLength(1); - const logicalInstruction = (instructions[0]?.body ?? "").replace(/\\\r?\n[ \t]*/gu, " "); + const logicalInstruction = (instructions[0]?.body ?? "") + .replace(/\\\r?\n[ \t]*/gu, " ") + .replace(/[ \t]+/gu, " "); const discoveryStart = logicalInstruction.indexOf("discovery_contract="); expect(discoveryStart).toBeGreaterThan(0); + const managedRuntimeStart = logicalInstruction.indexOf( + `&& { test -f ${MANAGED_STARTUP_RUNTIME_PATH}`, + discoveryStart, + ); + expect(managedRuntimeStart).toBeGreaterThan(discoveryStart); const functionSource = logicalInstruction.slice(0, discoveryStart).trim(); + const discoverySource = logicalInstruction.slice(discoveryStart, managedRuntimeStart).trim(); for (const fragment of [ + "stat -L -c 'uid=%u gid=%g type=%F mode=%a' -- \"$nemoclaw_artifact_path\" 2>/dev/null", "stat -c 'uid=%u gid=%g type=%F mode=%a' -- \"$nemoclaw_artifact_path\" 2>/dev/null", "uid=unavailable gid=unavailable type=missing mode=unavailable", "printf 'ERROR: managed image assertion failed: %s path=%s %s symlink=%s\\n'", + "printf 'ERROR: managed image assertion failed: %s exit-status=%s\\n'", ]) { expect(functionSource).toContain(fragment); } for (const assertion of [ + `discovery_contract="$(node ${DISCOVERY_RUNTIME_PATH})" || managed_image_command_failed mcp-tool-discovery-bundle-execution "$?"`, + "ERROR: managed image assertion failed: mcp-tool-discovery-json-contract actual=%s expected=%s", + `discovery_unsafe="$(find -L ${DISCOVERY_RUNTIME_ROOT} \\( ! -user root -o -perm /022 \\) -print -quit)" || managed_image_command_failed mcp-tool-discovery-tree-find-execution "$?"`, + 'test -z "$discovery_unsafe" || managed_runtime_assertion_failed mcp-tool-discovery-tree-safety "$discovery_unsafe" dereference', `test -f ${MANAGED_STARTUP_RUNTIME_PATH} || managed_runtime_assertion_failed regular-file ${MANAGED_STARTUP_RUNTIME_PATH}`, `test ! -L ${MANAGED_STARTUP_RUNTIME_PATH} || managed_runtime_assertion_failed non-symlink ${MANAGED_STARTUP_RUNTIME_PATH}`, `chown root:root ${MANAGED_STARTUP_RUNTIME_PATH} 2>/dev/null || managed_runtime_assertion_failed owner-root-root ${MANAGED_STARTUP_RUNTIME_PATH}`, @@ -69,6 +87,64 @@ function expectManagedRuntimeDiagnostic(dockerfile: string): void { const linkPath = path.join(tmp, "runtime-link.cjs"); fs.writeFileSync(targetPath, "fixture\n", { mode: 0o444 }); fs.symlinkSync(targetPath, linkPath); + const runDiscoveryChecks = ({ + discoveryOutput = DISCOVERY_EXPECTED_CONTRACT, + discoveryStatus = 0, + findOutput = "", + findStatus = 0, + dereferencedStatOutput = "uid=0 gid=0 type=regular file mode=444", + statOutput = "uid=0 gid=0 type=regular file mode=444", + }: { + discoveryOutput?: string; + discoveryStatus?: number; + findOutput?: string; + findStatus?: number; + dereferencedStatOutput?: string; + statOutput?: string; + } = {}) => + spawnSync( + "sh", + [ + "-c", + [ + "node() {", + ' if [ "$1" = "$NEMOCLAW_TEST_DISCOVERY_RUNTIME" ]; then', + " printf '%s' \"$NEMOCLAW_TEST_DISCOVERY_OUTPUT\"", + ' return "$NEMOCLAW_TEST_DISCOVERY_STATUS"', + " fi", + ' "$NEMOCLAW_TEST_NODE" "$@"', + "}", + "find() {", + " printf '%s' \"$NEMOCLAW_TEST_FIND_OUTPUT\"", + ' return "$NEMOCLAW_TEST_FIND_STATUS"', + "}", + "stat() {", + ' if [ "$1" = "-L" ]; then', + " printf '%s' \"$NEMOCLAW_TEST_DEREFERENCED_STAT_OUTPUT\"", + " else", + " printf '%s' \"$NEMOCLAW_TEST_STAT_OUTPUT\"", + " fi", + "}", + functionSource, + discoverySource, + "printf 'discovery-ok\\n'", + ].join("\n"), + ], + { + encoding: "utf-8", + env: { + PATH: process.env.PATH ?? "", + NEMOCLAW_TEST_DISCOVERY_OUTPUT: discoveryOutput, + NEMOCLAW_TEST_DISCOVERY_RUNTIME: DISCOVERY_RUNTIME_PATH, + NEMOCLAW_TEST_DISCOVERY_STATUS: String(discoveryStatus), + NEMOCLAW_TEST_DEREFERENCED_STAT_OUTPUT: dereferencedStatOutput, + NEMOCLAW_TEST_FIND_OUTPUT: findOutput, + NEMOCLAW_TEST_FIND_STATUS: String(findStatus), + NEMOCLAW_TEST_NODE: process.execPath, + NEMOCLAW_TEST_STAT_OUTPUT: statOutput, + }, + }, + ); const runDiagnostic = (artifactPath: string, invariant: string, statOutput: string) => spawnSync( "sh", @@ -92,6 +168,132 @@ function expectManagedRuntimeDiagnostic(dockerfile: string): void { ); try { + const bundleFailure = runDiscoveryChecks({ + discoveryOutput: "output must remain private", + discoveryStatus: 23, + }); + expect(bundleFailure.status).toBe(1); + expect(bundleFailure.stdout).toBe(""); + expect(bundleFailure.stderr).toBe( + "ERROR: managed image assertion failed: mcp-tool-discovery-bundle-execution exit-status=23\n", + ); + + const standaloneCredentials = [ + "nvapi-abcdefghij", + "nvcf-abcdefghij", + "ghp_abcdefghij", + "gho_abcdefghij", + `github_pat_${"a".repeat(30)}`, + "sk-proj-abcdefghij", + "sk-ant-abcdefghij", + `sk-${"a".repeat(20)}`, + "xoxb-abcdefghij", + "xapp-abcdefghij", + "AKIA1234567890ABCDEF", + "ASIA1234567890ABCDEF", + "hf_abcdefghij", + "glpat-abcdefghij", + "gsk_abcdefghij", + "pypi-abcdefghij", + `bot12345678:${"a".repeat(35)}`, + `12345678:${"a".repeat(35)}`, + `${"a".repeat(24)}.${"b".repeat(6)}.${"c".repeat(27)}`, + "tvly-abcdefghij", + "lsv2_pt_abcdefghij_tail", + "lsv2_sk_abcdefghij", + `eyJabcde.${"b".repeat(2)}.${"c".repeat(10)}`, + ]; + for (const credential of standaloneCredentials) { + const contractFailure = runDiscoveryChecks({ + discoveryOutput: JSON.stringify({ + protocol: 2, + ok: true, + detail: `wrong\n${credential}\tcontinued\u001b[31m`, + }), + }); + expect(contractFailure.status).toBe(1); + expect(contractFailure.stdout).toBe(""); + expect(contractFailure.stderr).toBe( + `ERROR: managed image assertion failed: mcp-tool-discovery-json-contract actual={"protocol":2,"ok":true,"detail":"wrong??continued?[31m"} expected=${DISCOVERY_EXPECTED_CONTRACT}\n`, + ); + expect(contractFailure.stderr).not.toContain(credential); + expect(contractFailure.stderr).not.toContain("\u001b"); + } + + for (const [credential, sanitized] of [ + ["Bearer abcdefghij", ""], + ["Basic abcdefghij", ""], + ["OPENAI_API_KEY=abcdefghij", "OPENAI_API_KEY="], + ["accessToken=abcdefghij", "accessToken="], + ["KEY=abcdefghij", "KEY="], + ]) { + const contractFailure = runDiscoveryChecks({ + discoveryOutput: JSON.stringify({ protocol: 2, ok: true, detail: credential }), + }); + expect(contractFailure.status).toBe(1); + expect(contractFailure.stdout).toBe(""); + expect(contractFailure.stderr).toBe( + `ERROR: managed image assertion failed: mcp-tool-discovery-json-contract actual={"protocol":2,"ok":true,"detail":"${sanitized}"} expected=${DISCOVERY_EXPECTED_CONTRACT}\n`, + ); + expect(contractFailure.stderr).not.toContain(credential); + } + + const invalidJsonFailure = runDiscoveryChecks({ + discoveryOutput: '{"detail":"nvcf-abcdefghij"', + }); + expect(invalidJsonFailure.status).toBe(1); + expect(invalidJsonFailure.stdout).toBe(""); + expect(invalidJsonFailure.stderr).toBe( + `ERROR: managed image assertion failed: mcp-tool-discovery-json-contract actual={"type":"invalid-json","preview":"{\\"detail\\":\\"\\""} expected=${DISCOVERY_EXPECTED_CONTRACT}\n`, + ); + + const privateKeyLabel = `${"PRIVATE"} KEY`; + const privateKeyFailure = runDiscoveryChecks({ + discoveryOutput: JSON.stringify({ + protocol: 2, + ok: true, + detail: `wrong\n-----BEGIN ${privateKeyLabel}-----\nprivate-material\n-----END ${privateKeyLabel}-----`, + }), + }); + expect(privateKeyFailure.status).toBe(1); + expect(privateKeyFailure.stdout).toBe(""); + expect(privateKeyFailure.stderr).toBe( + `ERROR: managed image assertion failed: mcp-tool-discovery-json-contract actual={"protocol":2,"ok":true,"detail":"wrong?"} expected=${DISCOVERY_EXPECTED_CONTRACT}\n`, + ); + expect(privateKeyFailure.stderr).not.toContain("private-material"); + + const findFailure = runDiscoveryChecks({ findOutput: linkPath, findStatus: 42 }); + expect(findFailure.status).toBe(1); + expect(findFailure.stdout).toBe(""); + expect(findFailure.stderr).toBe( + "ERROR: managed image assertion failed: mcp-tool-discovery-tree-find-execution exit-status=42\n", + ); + + const unsafePath = runDiscoveryChecks({ + dereferencedStatOutput: "uid=123 gid=456 type=regular file mode=664", + findOutput: linkPath, + statOutput: "uid=0 gid=0 type=symbolic link mode=777", + }); + expect(unsafePath.status).toBe(1); + expect(unsafePath.stdout).toBe(""); + expect(unsafePath.stderr).toBe( + `ERROR: managed image assertion failed: mcp-tool-discovery-tree-safety path=${linkPath} uid=123 gid=456 type=regular file mode=664 symlink=yes\n`, + ); + + const success = runDiscoveryChecks({ + discoveryOutput: JSON.stringify({ + protocol: 1, + ok: false, + detail: "tool discovery received invalid runtime arguments", + count: 0, + tools: [], + truncated: false, + }), + }); + expect(success.status, success.stderr).toBe(0); + expect(success.stdout).toBe("discovery-ok\n"); + expect(success.stderr).toBe(""); + const missing = runDiagnostic(missingPath, "regular-file", "unused"); expect(missing.status).toBe(1); expect(missing.stdout).toBe("");