Skip to content
Closed
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
5 changes: 1 addition & 4 deletions .github/actions/ci-cli-coverage-shard/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,7 @@ runs:
RIPGREP_VERSION: "14.1.0-1"
run: |
set -euo pipefail
sudo apt-get update -qq \
-o Dir::Etc::sourcelist="sources.list.d/ubuntu.sources" \
-o Dir::Etc::sourceparts="-"
sudo apt-get install -y --no-install-recommends \
bash "$GITHUB_ACTION_PATH/../ci-install-pinned-ubuntu-packages.sh" \
"fd-find=${FD_FIND_VERSION}" \
"ripgrep=${RIPGREP_VERSION}"

Expand Down
45 changes: 45 additions & 0 deletions .github/actions/ci-install-pinned-ubuntu-packages.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

set -euo pipefail

if [ "$#" -eq 0 ]; then
echo "::error::At least one pinned package specification is required"
exit 2
fi

for package_spec in "$@"; do
if [[ ! "$package_spec" =~ ^[A-Za-z0-9][A-Za-z0-9.+-]*(:[A-Za-z0-9][A-Za-z0-9.+-]*)?=[^[:space:]=]+$ ]]; then
echo "::error::Package specification must use package=version: $package_spec" >&2
exit 2
fi
done

UBUNTU_APT_SOURCES="/etc/apt/sources.list.d/ubuntu.sources"
if [ ! -r "$UBUNTU_APT_SOURCES" ]; then
echo "::error::Configured APT source list is unavailable: $UBUNTU_APT_SOURCES"
exit 1
fi

APT_SOURCE_OPTIONS=(
-o "Dir::Etc::sourcelist=$UBUNTU_APT_SOURCES"
-o "Dir::Etc::sourceparts=-"
)
# Hosted runners keep RUNNER_TEMP private to the runner user. Grant only
# traversal while APT runs, then restore its exact original mode on every exit.
RUNNER_TEMP_MODE="$(stat -c '%a' "$RUNNER_TEMP")"
restore_runner_temp_mode() {
sudo chmod "$RUNNER_TEMP_MODE" "$RUNNER_TEMP"
}
trap restore_runner_temp_mode EXIT
sudo chmod o+x "$RUNNER_TEMP"

APT_LISTS_DIR="$RUNNER_TEMP/nemoclaw-apt-lists"
sudo install -d -m 0755 "$APT_LISTS_DIR"
sudo install -d -o _apt -g root -m 0700 "$APT_LISTS_DIR/partial"
APT_SOURCE_OPTIONS+=(
-o "Dir::State::lists=$APT_LISTS_DIR"
)
sudo apt-get "${APT_SOURCE_OPTIONS[@]}" update -qq
sudo apt-get "${APT_SOURCE_OPTIONS[@]}" install -y --no-install-recommends "$@"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
5 changes: 1 addition & 4 deletions .github/workflows/pr-review-advisor.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,7 @@ jobs:
- name: Install locked runtime
run: |
set -euo pipefail
sudo apt-get update -qq \
-o Dir::Etc::sourcelist="sources.list.d/ubuntu.sources" \
-o Dir::Etc::sourceparts="-"
sudo apt-get install -y --no-install-recommends "fd-find=${FD_FIND_VERSION}" "ripgrep=${RIPGREP_VERSION}"
bash "$ADVISOR_DIR/.github/actions/ci-install-pinned-ubuntu-packages.sh" "fd-find=${FD_FIND_VERSION}" "ripgrep=${RIPGREP_VERSION}"
test "$(dpkg-query -W -f='${Version}' fd-find)" = "$FD_FIND_VERSION"
test "$(dpkg-query -W -f='${Version}' ripgrep)" = "$RIPGREP_VERSION"
(cd "$ADVISOR_DIR" && npm ci --ignore-scripts --no-audit --no-fund)
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,7 @@ jobs:
.github/actions/ci-plugin-coverage
.github/actions/ci-installer-integration
.github/actions/ci-install-dependencies.sh
.github/actions/ci-install-pinned-ubuntu-packages.sh
ci/reviewed-npm-audit.json
scripts/audit-reviewed-npm-graph.mts
scripts/checks/prepare-ci-npm-install.mts
Expand Down
8 changes: 4 additions & 4 deletions ci/pi-agent-qualification-v1-linux-amd64.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
"agent": "pi",
"platform": "linux/amd64",
"image": "ghcr.io/nvidia/nemoclaw/pi-sandbox",
"digest": "sha256:fe933d8331387fe05a2ffad135dd493ca75f210343728e03b69c1a9ea7d58008",
"reference": "ghcr.io/nvidia/nemoclaw/pi-sandbox@sha256:fe933d8331387fe05a2ffad135dd493ca75f210343728e03b69c1a9ea7d58008",
"digest": "sha256:247099fe9eb4573dcbcc158cd0de064c613ab98f401fec3c25901b2968abaa69",
"reference": "ghcr.io/nvidia/nemoclaw/pi-sandbox@sha256:247099fe9eb4573dcbcc158cd0de064c613ab98f401fec3c25901b2968abaa69",
"source": {
"repository": "NVIDIA/NemoClaw",
"revision": "609d60a3562142350d6afb3f824b0248624a00d0",
"revision": "91b0bfb7f6bf491d8dadb7ec939f874164bcd882",
"release": "v0.1.0",
"cohort": "ghrun-34384136357-1"
"cohort": "ghrun-34414228186-1"
},
"startupProfileContractVersion": 1,
"capabilityContractVersion": 1
Expand Down
8 changes: 4 additions & 4 deletions ci/pi-agent-qualification-v1-linux-arm64.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
"agent": "pi",
"platform": "linux/arm64",
"image": "ghcr.io/nvidia/nemoclaw/pi-sandbox",
"digest": "sha256:dd47411d99704bc8464899bea5e5f30b5cdda862af64b281d62f38121adeec01",
"reference": "ghcr.io/nvidia/nemoclaw/pi-sandbox@sha256:dd47411d99704bc8464899bea5e5f30b5cdda862af64b281d62f38121adeec01",
"digest": "sha256:91a63d8e48d266d0e3d416a94201bbdccecea25c7a0ed8c5fa93736f7992ee8d",
"reference": "ghcr.io/nvidia/nemoclaw/pi-sandbox@sha256:91a63d8e48d266d0e3d416a94201bbdccecea25c7a0ed8c5fa93736f7992ee8d",
"source": {
"repository": "NVIDIA/NemoClaw",
"revision": "609d60a3562142350d6afb3f824b0248624a00d0",
"revision": "91b0bfb7f6bf491d8dadb7ec939f874164bcd882",
"release": "v0.1.0",
"cohort": "ghrun-34384136357-1"
"cohort": "ghrun-34414228186-1"
},
"startupProfileContractVersion": 1,
"capabilityContractVersion": 1
Expand Down
5 changes: 5 additions & 0 deletions ci/source-shape-test-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@
"test": "keeps %s plugin test typechecking after the trusted production build",
"category": "security"
},
{
"file": "test/automation/pull-requests/pr-workflow-contract.test.ts",
"test": "limits CLI shard package installation to the configured ubuntu.sources file",
"category": "security"
},
{
"file": "test/automation/pull-requests/pr-workflow-contract.test.ts",
"test": "passes only the base-packaged SDK archive to pull request dependency jobs",
Expand Down
2 changes: 1 addition & 1 deletion ci/test-file-size-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"src/lib/onboard/preflight.test.ts": 1875,
"test/generation/generate-openclaw-config.test.ts": 1898,
"test/installer-integration/install-preflight.test.ts": 3025,
"test/agents/openclaw/runtime/nemoclaw-start.test.ts": 4359,
"test/agents/openclaw/runtime/nemoclaw-start.test.ts": 4357,
"test/onboarding/onboard-messaging.test.ts": 1971,
"test/onboarding/onboard-selection.test.ts": 4133
}
Expand Down
10 changes: 7 additions & 3 deletions docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3925,16 +3925,20 @@ OpenClaw-specific onboarding configuration:
| `TAVILY_API_KEY` | Tavily Search API key | Supplies and implicitly selects Tavily Search when no provider is set and no Brave key is available. NemoClaw validates the key and stores it in OpenShell rather than the sandbox. |
| `NEMOCLAW_AGENT_TIMEOUT` | positive integer (seconds) | Onboarding setting that overrides `agents.defaults.timeoutSeconds` and `models.providers.<provider-id>.timeoutSeconds` in the generated OpenClaw config. Set it before onboarding creates or recreates the sandbox. Setting it only for a later `$$nemoclaw <name> agent` invocation does not change the existing sandbox. Raise for slow inference. |
| `NEMOCLAW_MCP_SHADOW_DIAGNOSTICS` | literal `1` to enable | Forwards opt-in successful Streamable HTTP MCP timing diagnostics to a newly created or rebuilt OpenClaw sandbox. It does not change timeouts, retries, requests, or responses. Unset it and rebuild after evidence collection to restore failure-only logging. Other values are ignored. |
| `NEMOCLAW_AUTO_PAIR_SLOW_INTERVAL_SECS` | positive number of seconds | Sets the in-sandbox OpenClaw auto-pair watcher cadence after the canonical local CLI device reaches the exact baseline scopes and has no same-device pending request. Defaults to `5` seconds. Before that settlement, the watcher polls every `1` second. Raise only on load-sensitive gateways. |
| `NEMOCLAW_AUTO_PAIR_FAST_REENTRY_POLLS` | positive integer | Sets how many fast polls run after the watcher observes a fresh allowlisted scope-upgrade request. Defaults to `5`; set lower only when you need to reduce gateway polling. |
| `NEMOCLAW_AUTO_PAIR_FAST_REENTRY_INTERVAL_SECS` | positive number of seconds | Sets the fast-reentry interval after a fresh allowlisted scope-upgrade request. Defaults to `1`. |
| `NEMOCLAW_AUTO_PAIR_DEADLINE_SECS` | `1` to `86400` seconds | Sets the total lifetime of the in-sandbox OpenClaw auto-pair watcher. Defaults to `28800` seconds (8 hours). Each command, poll, and sleep remains inside this deadline. The watcher logs this limit when it expires. |
| `NEMOCLAW_AUTO_PAIR_RUN_TIMEOUT_SECS` | `0.05` to `300` seconds | Sets the timeout for each OpenClaw CLI command that the auto-pair watcher runs. Defaults to `10` seconds. A timeout log names the effective limit. |
| `NEMOCLAW_AUTO_PAIR_SLOW_INTERVAL_SECS` | `0.05` to `300` seconds | Sets the in-sandbox OpenClaw auto-pair watcher cadence after the canonical local CLI device reaches the exact baseline scopes and has no same-device pending request. Defaults to `5` seconds. Before that settlement, the watcher polls every `1` second. Raise only on load-sensitive gateways. |
| `NEMOCLAW_AUTO_PAIR_FAST_REENTRY_POLLS` | positive integer up to `1728000` | Sets how many fast polls run after the watcher observes a fresh allowlisted scope-upgrade request. Defaults to `5`; the maximum is one 24-hour watcher lifetime at the minimum interval. Set lower only when you need to reduce gateway polling. |
| `NEMOCLAW_AUTO_PAIR_FAST_REENTRY_INTERVAL_SECS` | `0.05` to `300` seconds | Sets the fast-reentry interval after a fresh allowlisted scope-upgrade request. Defaults to `1`. |
| `NEMOCLAW_CONTEXT_WINDOW` | positive integer (tokens) | Overrides the model's context-window value in the generated OpenClaw config. |
| `NEMOCLAW_MAX_TOKENS` | positive integer (tokens) | Overrides the model's `maxTokens` in the generated OpenClaw config. |
| `NEMOCLAW_REASONING` | `true` or `false` | Overrides the model's reasoning-mode flag in the generated OpenClaw config. |
| `NEMOCLAW_REASONING_EFFORT` | `low`, `medium`, `high`, or `default` | Applies only to the `compatible-endpoint` provider with the `openai-completions` API. A `low`, `medium`, or `high` value writes `params.extra_body.reasoning_effort`; unset or `default` leaves the endpoint's own default in place. Onboarding parses every explicit value before provider effects and rejects an invalid value or provider/API mismatch before route, policy, sandbox, or registry mutation. During `inference set`, NemoClaw rejects every explicit value, including `default`, before mutation unless the resulting route is compatible. Without an explicit effort input, switching to another provider or API family clears a recorded effort. An ordinary restart preserves the persisted runtime state instead of replaying the image's original value. |
| `NEMOCLAW_AGENT_HEARTBEAT_EVERY` | duration with `s`, `m`, or `h` suffix (for example `30m`, `1h`, or `0m`) | Overrides `agents.defaults.heartbeat.every` in the generated OpenClaw config. Set `0m` to disable periodic agent turns. |
| `NEMOCLAW_EXTRA_AGENTS_JSON` | JSON array of OpenClaw secondary-agent entries | Adds secondary agents to `agents.list`. Refer to [Extra OpenClaw agents](#extra-openclaw-agents) for the entry schema, path constraints, and validation rules. |

NemoClaw rejects `Infinity`, `NaN`, unparseable text, and values outside the ranges of these five auto-pair controls before it creates the sandbox. The managed image entrypoint enforces the same ranges. A direct process environment assignment that bypasses the managed entrypoint uses the documented default when the watcher cannot use the value. The warning names the variable and default without printing the invalid value.

</AgentOnly>
<AgentOnly variant="hermes">

Expand Down
49 changes: 48 additions & 1 deletion scripts/lib/entrypoint-env-wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ nemoclaw_normalize_entrypoint_env_wrapper() {

local -a _nemoclaw_original_argv=("$@")
local -a _nemoclaw_assignments=()
local -a _nemoclaw_numeric_args=()
local _nemoclaw_self_index=-1
local _nemoclaw_index
local _nemoclaw_break_index
Expand All @@ -43,7 +44,6 @@ nemoclaw_normalize_entrypoint_env_wrapper() {
_nemoclaw_supported_names="${_nemoclaw_supported_names}|OPENCLAW_HOME|OPENCLAW_STATE_DIR"
_nemoclaw_supported_names="${_nemoclaw_supported_names}|OPENCLAW_WORKSPACE_DIR"
_nemoclaw_supported_names="${_nemoclaw_supported_names}|NEMOCLAW_AUTO_PAIR_DEADLINE_SECS"
_nemoclaw_supported_names="${_nemoclaw_supported_names}|NEMOCLAW_AUTO_PAIR_FAST_DEADLINE_SECS"
_nemoclaw_supported_names="${_nemoclaw_supported_names}|NEMOCLAW_AUTO_PAIR_FAST_REENTRY_INTERVAL_SECS"
_nemoclaw_supported_names="${_nemoclaw_supported_names}|NEMOCLAW_AUTO_PAIR_FAST_REENTRY_POLLS"
_nemoclaw_supported_names="${_nemoclaw_supported_names}|NEMOCLAW_AUTO_PAIR_RUN_TIMEOUT_SECS"
Expand Down Expand Up @@ -139,10 +139,57 @@ nemoclaw_normalize_entrypoint_env_wrapper() {
return 1
;;
esac
case "$_nemoclaw_name" in
NEMOCLAW_AUTO_PAIR_FAST_REENTRY_POLLS)
_nemoclaw_numeric_args+=(polls "${_nemoclaw_token#*=}" 1 1728000)
;;
NEMOCLAW_AUTO_PAIR_DEADLINE_SECS)
_nemoclaw_numeric_args+=(seconds "${_nemoclaw_token#*=}" 1 86400)
;;
NEMOCLAW_AUTO_PAIR_FAST_REENTRY_INTERVAL_SECS | \
NEMOCLAW_AUTO_PAIR_SLOW_INTERVAL_SECS)
_nemoclaw_numeric_args+=(seconds "${_nemoclaw_token#*=}" 0.05 300)
;;
NEMOCLAW_AUTO_PAIR_RUN_TIMEOUT_SECS)
_nemoclaw_numeric_args+=(seconds "${_nemoclaw_token#*=}" 0.05 300)
;;
esac
_nemoclaw_assignments+=("$_nemoclaw_token")
_nemoclaw_seen_names="${_nemoclaw_seen_names}${_nemoclaw_name}|"
done

# Every shipped image provides the root-owned system Python at this fixed
# path. Isolated mode ignores user-controlled Python environment and site
# packages. Values remain argv data, and one process checks the complete
# vector before any assignment reaches the root environment. float() uses
# the same binary64 underflow boundary as the TypeScript launch validator.
if [ "${#_nemoclaw_numeric_args[@]}" -gt 0 ] && ! /usr/bin/python3 -I -c '
import math, re, sys
seconds = re.compile(r"\+?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]{1,3})?\Z")
polls = re.compile(r"\+?[0-9]+\Z")
try:
for index in range(1, len(sys.argv), 4):
kind, raw, minimum, maximum = sys.argv[index:index + 4]
raw = raw.strip()
if not raw:
continue
grammar = seconds if kind == "seconds" else polls
digits = raw.removeprefix("+").lstrip("0")
if grammar.fullmatch(raw) is None or (kind == "polls" and (not digits or len(digits) > 16)):
raise ValueError
value = float(raw) if kind == "seconds" else int(raw, 10)
lower = float(minimum) if kind == "seconds" else int(minimum)
upper = float(maximum) if kind == "seconds" else int(maximum)
if not math.isfinite(value) or not lower <= value <= upper:
raise ValueError
except (OverflowError, ValueError):
raise SystemExit(1)
' "${_nemoclaw_numeric_args[@]}" 2>/dev/null; then
printf '%s\n' \
'[SECURITY] Managed startup env wrapper contains an out-of-range assignment.' >&2
return 1
fi

# Export only after the complete vector has passed validation so malformed
# input cannot leave a partially mutated root process.
if [ "$_nemoclaw_self_index" -gt 1 ]; then
Expand Down
Loading
Loading