Skip to content
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9e0029e
fix(install): refuse implicit CLI downgrades
rsliter Sep 8, 2026
5d62896
merge: refresh issue 10948 fix with latest main
rsliter Sep 8, 2026
a1bef74
merge: refresh issue 10948 fix with latest main
rsliter Sep 8, 2026
8ee08e8
merge: refresh issue 10948 fix with latest main
rsliter Sep 8, 2026
f733be1
fix: harden maintained installer downgrade guard
rsliter Sep 8, 2026
5f6c718
fix: fail closed on unverifiable installed CLI
rsliter Sep 8, 2026
093577d
fix: clean interrupted installer lookups
rsliter Sep 8, 2026
a08c366
fix: pin guarded installer revision
rsliter Sep 8, 2026
e2c56c4
fix: harden bounded installer lookup
rsliter Sep 8, 2026
1ba51e8
fix: bind installer lookup process group
rsliter Sep 8, 2026
2285e19
fix(installer): harden downgrade guard review gaps
rsliter Sep 8, 2026
9f361b0
test(installer): cover release and signal ordering
rsliter Sep 8, 2026
85c11f9
test(installer): target bootstrap signal traps
rsliter Sep 8, 2026
dd7ecd0
fix(installer): bound bootstrap lookup output
rsliter Sep 8, 2026
a1499a6
fix(installer): separate bootstrap lookup limits
rsliter Sep 8, 2026
00594f7
test(installer): harden lookup fixtures
rsliter Sep 8, 2026
01b4c0c
test(installer): verify bootstrap fetch boundary
rsliter Sep 8, 2026
e2c554c
fix(install): preserve alias and cancellation safeguards
rsliter Sep 9, 2026
13ed50e
merge: consume patched js-yaml from main
rsliter Sep 9, 2026
cd2b04f
fix(install): close lookup launch cancellation window
rsliter Sep 9, 2026
b1812ad
merge: refresh trusted validation configuration
rsliter Sep 9, 2026
7da64b9
fix(install): protect bootstrap source handoff
rsliter Sep 9, 2026
e74b5da
docs(security): pin the repaired credential helper pair
rsliter Sep 9, 2026
32db17a
merge: refresh trusted validation dependencies
rsliter Sep 9, 2026
a9b56b7
merge: integrate plugin cache repair
rsliter Sep 9, 2026
15bd0dd
merge: refresh installer validation base
rsliter Sep 9, 2026
6b75bf3
fix(docs): pin merged credential helper pair
rsliter Sep 9, 2026
e56c1f7
test(install): close downgrade guard review gaps
cjagwani Sep 9, 2026
e542334
chore(ci): validate after upstream image repair
cjagwani Sep 9, 2026
ba0d7e9
fix(images): refresh Hermes probe digest
jyaunches Sep 9, 2026
52149a8
Merge branch 'main' into codex/10948-rebuild-discord-resume
cjagwani Sep 9, 2026
a46d1b1
test(credentials): verify static form CSP hash
cjagwani Sep 9, 2026
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
8 changes: 7 additions & 1 deletion docs/manage-sandboxes/update-sandboxes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,13 @@ curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash
$$nemoclaw upgrade-sandboxes --check
```

If a support workflow asks you to pass the maintained tag explicitly, clear any inherited `NEMOCLAW_INSTALL_REF` and set `NEMOCLAW_INSTALL_TAG` on the `bash` side of the install pipeline.
The default installer preserves a newer installed NemoClaw release. When a NemoClaw CLI is installed, it also stops without changing the CLI when it cannot verify the maintained `lkg` release. To intentionally install an older release from a fixed source, select its version tag or full commit SHA with `NEMOCLAW_INSTALL_TAG`.

```bash
curl -fsSL https://www.nvidia.com/nemoclaw.sh | NEMOCLAW_INSTALL_REF= NEMOCLAW_INSTALL_TAG=vX.Y.Z bash
```

If a support workflow asks you to pass the maintained tag explicitly, clear any inherited `NEMOCLAW_INSTALL_REF` and set `NEMOCLAW_INSTALL_TAG` on the `bash` side of the install pipeline. The same downgrade protection applies to an explicit `lkg` selection.

```bash
curl -fsSL https://www.nvidia.com/nemoclaw.sh | NEMOCLAW_INSTALL_REF= NEMOCLAW_INSTALL_TAG=lkg bash
Expand Down
5 changes: 3 additions & 2 deletions docs/reference/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -575,12 +575,13 @@ migration N was previously applied
has been modified
```

The first error means the installed OpenShell migration set does not contain migration N. The second error means that migration set defines migration N with different contents. NemoClaw identifies `<selected-state-dir>/openshell.db` as incompatible with the installed OpenShell migration set for both errors. This failure can happen after an OpenShell downgrade. Installing a NemoClaw release that is older than the installed one performs that downgrade, because each release pins one OpenShell version and the installer reinstalls OpenShell at the pin. You reach that state in one of three ways:
The first error means the installed OpenShell migration set does not contain migration N. The second error means that migration set defines migration N with different contents. NemoClaw identifies `<selected-state-dir>/openshell.db` as incompatible with the installed OpenShell migration set for both errors. This failure can happen after an OpenShell downgrade. Installing a NemoClaw release that is older than the installed one performs that downgrade, because each release pins one OpenShell version and the installer reinstalls OpenShell at the pin. You reach that state in one of two ways:

- You select an older release with `NEMOCLAW_INSTALL_TAG` or `NEMOCLAW_INSTALL_REF`.
- The default `lkg` release is older than the NemoClaw release already on the host.
- You install an older OpenShell yourself.

The default installer stops before replacing a newer installed NemoClaw release whenever the selected ref is `lkg` or `refs/tags/lkg`, whether it came from `NEMOCLAW_INSTALL_TAG` or the higher-priority `NEMOCLAW_INSTALL_REF`. Other explicit refs can replace the installed release; use a version tag or full commit SHA when you need a fixed source.

The diagnosis always prints the database path. When an unused archive path is available, it also prints that archive path beside the selected state directory and the profile-specific onboarding command. When no unused archive path is available, it asks you to keep the gateway stopped and inspect the state directory instead.

<Warning>
Expand Down
3 changes: 2 additions & 1 deletion docs/resources/local-credential-form.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<!-- frame-ancestors is intentionally omitted here; helpers must send it as an HTTP header. -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none'; base-uri 'none'; form-action 'self'; script-src 'sha256-i3cXmSMUjTA5LqLSfFQpXe0BBZRj4cM8t36dJMm3YJw='; style-src 'sha256-W4wSJyrmRXSCgQSjhVRZBhEmsaHh6dbUj9ZlKhxipME='; connect-src 'self';"
content="default-src 'none'; base-uri 'none'; form-action 'self'; script-src 'sha256-RQ739hGLYr4CmEutjRVKjb7dT35NV8oTiAa1up9zY48='; style-src 'sha256-W4wSJyrmRXSCgQSjhVRZBhEmsaHh6dbUj9ZlKhxipME='; connect-src 'self';"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>NemoClaw Local Credential Form</title>
Expand Down Expand Up @@ -222,6 +222,7 @@ <h1>NemoClaw Local Credential Form</h1>
"NO_PROXY",
"NETRC",
"NEMOCLAW_ACCEPT_DEV_UNVERIFIED_INSTALL",
"NEMOCLAW_BOOTSTRAP_FETCH_REF",
"NEMOCLAW_BOOTSTRAP_PAYLOAD",
"NEMOCLAW_INSTALL_REF",
"NEMOCLAW_INSTALL_TAG",
Expand Down
4 changes: 2 additions & 2 deletions docs/resources/starter-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ Before collecting secrets, determine every environment-variable name and the com
Do not generate, rewrite, or redesign the helper or form.
Use this reviewed pair without modification:

- Helper: `https://raw.githubusercontent.com/NVIDIA/NemoClaw/11e0bb209103b03ee8239cbc307d1cb5d3521866/scripts/local-credential-helper.mts` (SHA-256 `72c187067dca6423e7aa7fa4bfab660e4202805f503c79520d8a0cf24869b668`).
- Form: `https://raw.githubusercontent.com/NVIDIA/NemoClaw/11e0bb209103b03ee8239cbc307d1cb5d3521866/docs/resources/local-credential-form.html` (SHA-256 `5512a256e0ad7c63a26ab82cf4f5924e98652097172ab8a5dc9d9358dd4f6ae8`).
- Helper: `https://raw.githubusercontent.com/NVIDIA/NemoClaw/15bd0dd25b185eafae02a067532fe18718b2be23/scripts/local-credential-helper.mts` (SHA-256 `f7c255120735307c93767c348bfcd54e7c7008275f9f5215f249e4c1a9daa9be`).
- Form: `https://raw.githubusercontent.com/NVIDIA/NemoClaw/15bd0dd25b185eafae02a067532fe18718b2be23/docs/resources/local-credential-form.html` (SHA-256 `cdd85dc6a0b31a8b9773e7ddaacb38a4e8162b0a13b70bebef3dc297ae7a6c44`).

- Treat the two immutable URL and digest pairs as one reviewed trust boundary; before executing the helper, compute the SHA-256 digest of both downloaded files and compare each result with its pinned digest.
- If either digest differs, do not execute the helper; delete both temporary files and stop.
Expand Down
179 changes: 177 additions & 2 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ BOOTSTRAP_TMPDIR=""
PAYLOAD_MARKER="NEMOCLAW_VERSIONED_INSTALLER_PAYLOAD=1"
DEFAULT_INSTALL_REF="lkg"
INSTALL_TAG_EXAMPLE="vX.Y.Z"
BOOTSTRAP_LOOKUP_TIMEOUT_SECONDS=30
BOOTSTRAP_CLI_LOOKUP_MAX_OUTPUT_BYTES=65536
BOOTSTRAP_TAG_LOOKUP_MAX_OUTPUT_BYTES=1048576
SELECTED_PAYLOAD_IDENTITY_REF=""

resolve_release_tag() {
if [[ -n "${NEMOCLAW_INSTALL_REF:-}" ]]; then
Expand Down Expand Up @@ -79,17 +83,186 @@ clone_nemoclaw_ref() {
)
}

installed_nemoclaw_release_version() {
local cli_name cli_path normalized_agent output status
normalized_agent="$(printf '%s' "${NEMOCLAW_AGENT:-openclaw}" | tr '[:upper:]_ ' '[:lower:]--' | sed -E 's/-+/-/g; s/^-//; s/-$//')"
case "$normalized_agent" in
nemohermes | nemo-hermes | hermes) cli_name="nemohermes" ;;
nemo-deepagents | nemo-deepagent | nemodeepagents | nemodeepagent | dcode | deepagent | deepagents | deep-agent | deep-agents | deepagentcode | deepagentscode | deepagent-code | deepagents-code | deep-agent-code | deep-agents-code | langchain | langchain-code | langchaindeepagent | langchaindeepagents | langchain-deepagent | langchain-deepagents | langchaindeepagentcode | langchaindeepagentscode | langchain-deepagent-code | langchain-deepagents-code | langchain-deep-agent | langchain-deep-agents | langchain-deep-agent-code | langchain-deep-agents-code) cli_name="nemo-deepagents" ;;
*) cli_name="nemoclaw" ;;
esac
cli_path="$(command -v "$cli_name" 2>/dev/null || true)"
[[ -n "$cli_path" ]] || return 3
output="$(run_bounded_bootstrap_lookup "installed NemoClaw version lookup" "$BOOTSTRAP_CLI_LOOKUP_MAX_OUTPUT_BYTES" "$cli_path" --version)" || {
status=$?
((status == 124)) && return 124
((status >= 128)) && return "$status"
return 2
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}
if [[ "$output" =~ ^${cli_name}[[:space:]]+v([0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?)$ ]]; then
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
printf '%s' "${BASH_REMATCH[1]}"
return 0
fi
return 2
}

checkout_release_version() {
local source_root="$1" target_commit refs status ref version
target_commit="$(git -C "$source_root" rev-parse HEAD 2>/dev/null || true)"
[[ -n "$target_commit" ]] || return 0
refs="$(run_bounded_bootstrap_lookup "maintained release tag lookup" "$BOOTSTRAP_TAG_LOOKUP_MAX_OUTPUT_BYTES" git -C "$source_root" ls-remote --tags origin 'refs/tags/v*')" || {
status=$?
((status == 124)) && exit 1
((status >= 128)) && exit "$status"
return 0
}
while read -r commit ref; do
[[ "$commit" == "$target_commit" ]] || continue
ref="${ref%\^\{\}}"
version="${ref#refs/tags/v}"
# The maintained lkg must resolve to a stable release. Rejecting prerelease
# tags also prevents replacing a stable install with the same core version.
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
printf '%s' "$version"
return 0
fi
done <<<"$refs"
}

release_version_is_newer() {
local left="$1" right="$2" left_major left_minor left_patch right_major right_minor right_patch
left="${left%%+*}"
left="${left%%-*}"
IFS=. read -r left_major left_minor left_patch <<<"$left"
IFS=. read -r right_major right_minor right_patch <<<"$right"
((10#$left_major > 10#$right_major)) && return 0
((10#$left_major < 10#$right_major)) && return 1
((10#$left_minor > 10#$right_minor)) && return 0
((10#$left_minor < 10#$right_minor)) && return 1
((10#$left_patch > 10#$right_patch))
}

run_bounded_bootstrap_lookup() (
local label="$1" max_output_bytes="$2" output_file command_pid="" output_bytes status ticks=0 pending_signal=0
shift 2
output_file="$(mktemp "${TMPDIR:-/tmp}/nemoclaw-bootstrap-lookup.XXXXXX")"
# Invoked indirectly by the signal and exit traps below.
# shellcheck disable=SC2329
cleanup_bootstrap_lookup() {
local cleanup_status="$1"
Comment thread
rsliter marked this conversation as resolved.
Fixed
# Preserve cancellation status without interrupting or reentering cleanup.
trap 'cleanup_status=130' INT
Comment thread
rsliter marked this conversation as resolved.
Fixed
trap 'cleanup_status=143' TERM
Comment thread
rsliter marked this conversation as resolved.
Fixed
trap - EXIT
Comment thread
rsliter marked this conversation as resolved.
Fixed
[[ -z "$command_pid" ]] || terminate_bootstrap_lookup_group "$command_pid"
Comment thread
rsliter marked this conversation as resolved.
Fixed
Comment thread
rsliter marked this conversation as resolved.
Fixed
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
rm -f "$output_file"
Comment thread
rsliter marked this conversation as resolved.
Fixed
exit "$cleanup_status"
Comment thread
rsliter marked this conversation as resolved.
Fixed
}
# Defer cancellation until the newly launched process group has an owned PID.
trap 'pending_signal=130' INT
trap 'pending_signal=143' TERM
trap 'cleanup_bootstrap_lookup "$?"' EXIT
set -m
(
set -o pipefail
"$@" 2>/dev/null </dev/null | head -c "$((max_output_bytes + 1))"
) >"$output_file" 2>/dev/null &
command_pid=$!
set +m
trap 'cleanup_bootstrap_lookup 130' INT
trap 'cleanup_bootstrap_lookup 143' TERM
((pending_signal == 0)) || cleanup_bootstrap_lookup "$pending_signal"
while bootstrap_lookup_group_is_alive "$command_pid"; do
if ((ticks >= BOOTSTRAP_LOOKUP_TIMEOUT_SECONDS * 10)); then
printf '[ERROR] Timed out during %s after %s seconds.\n' "$label" "$BOOTSTRAP_LOOKUP_TIMEOUT_SECONDS" >&2
printf ' The installed CLI was not changed. Retry or select an explicit immutable release tag.\n' >&2
return 124
fi
sleep 0.1
ticks=$((ticks + 1))
done
if wait "$command_pid"; then
status=0
else
status=$?
fi
command_pid=""
output_bytes="$(wc -c <"$output_file")"
if ((output_bytes > max_output_bytes)); then
status=2
elif ((status == 0)); then
cat "$output_file"
elif ((status >= 128)); then
status=2
fi
return "$status"
)

bootstrap_lookup_group_is_alive() {
local command_pid="$1"
kill -0 -- "-$command_pid" 2>/dev/null || kill -0 "$command_pid" 2>/dev/null
}

terminate_bootstrap_lookup_group() {
local command_pid="$1" grace_ticks
bootstrap_lookup_group_is_alive "$command_pid" || {
wait "$command_pid" 2>/dev/null || true
return
}
kill -TERM -- "-$command_pid" 2>/dev/null || kill -TERM "$command_pid" 2>/dev/null || true
for ((grace_ticks = 0; grace_ticks < 10; grace_ticks++)); do
bootstrap_lookup_group_is_alive "$command_pid" || break
sleep 0.1
done
if bootstrap_lookup_group_is_alive "$command_pid"; then
kill -KILL -- "-$command_pid" 2>/dev/null || kill -KILL "$command_pid" 2>/dev/null || true
fi
wait "$command_pid" 2>/dev/null || true
}

guard_implicit_maintained_downgrade() {
local source_root="$1" selected_ref="$2" installed_version target_version status
case "$selected_ref" in
lkg | refs/tags/lkg) ;;
*) return 0 ;;
esac
installed_version="$(installed_nemoclaw_release_version)" || {
status=$?
((status == 3)) && return 0
((status == 124)) && exit 1
((status >= 128)) && exit "$status"
printf '[ERROR] Cannot verify the installed NemoClaw version before selecting maintained lkg.\n' >&2
printf ' The installed CLI was not changed. Repair it or select an explicit immutable release tag.\n' >&2
exit 1
}
target_version="$(checkout_release_version "$source_root")"
if [[ -z "$target_version" ]]; then
printf "[ERROR] Cannot verify the maintained lkg version before replacing installed NemoClaw v%s.\n" "$installed_version" >&2
printf " The installed CLI was not changed. Set NEMOCLAW_INSTALL_TAG=v%s to reinstall this release.\n" "$installed_version" >&2
exit 1
fi
SELECTED_PAYLOAD_IDENTITY_REF="v${target_version}"
if release_version_is_newer "$installed_version" "$target_version"; then
printf "[ERROR] Refusing to replace installed NemoClaw v%s with maintained lkg v%s.\n" "$installed_version" "$target_version" >&2
printf " The installed CLI was not changed. Set NEMOCLAW_INSTALL_TAG=v%s to reinstall this release.\n" "$installed_version" >&2
exit 1
fi
}

exec_installer_from_ref() {
local ref="$1"
shift

local tmpdir source_root payload_script legacy_script
local tmpdir source_root payload_script legacy_script selected_commit
tmpdir="$(mktemp -d)"
BOOTSTRAP_TMPDIR="$tmpdir"
trap 'rm -rf "${BOOTSTRAP_TMPDIR:-}"' EXIT
source_root="${tmpdir}/source"

clone_nemoclaw_ref "$ref" "$source_root"
selected_commit="$(git -C "$source_root" rev-parse HEAD)"

guard_implicit_maintained_downgrade "$source_root" "$ref"

payload_script="${source_root}/scripts/install.sh"
legacy_script="${source_root}/install.sh"
Expand All @@ -100,7 +273,9 @@ exec_installer_from_ref() {
# helpers beside scripts/install.sh (including DGX Station preparation)
# are therefore staged from the same ref before payload execution.
verify_downloaded_script "$payload_script" "versioned installer"
NEMOCLAW_INSTALL_REF="$ref" NEMOCLAW_INSTALL_TAG="$ref" NEMOCLAW_BOOTSTRAP_PAYLOAD=1 \
NEMOCLAW_BOOTSTRAP_FETCH_REF="$selected_commit" \
NEMOCLAW_INSTALL_REF="${SELECTED_PAYLOAD_IDENTITY_REF:-$ref}" \
NEMOCLAW_INSTALL_TAG="$ref" NEMOCLAW_BOOTSTRAP_PAYLOAD=1 \
bash "$payload_script" "$@"
return
fi
Expand Down
6 changes: 5 additions & 1 deletion scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,12 @@ canonical_agent_name() {
}

# Resolve which Git ref to install from.
# Priority: NEMOCLAW_INSTALL_TAG env var > lkg tag.
# Priority: bootstrap fetch pin > NEMOCLAW_INSTALL_REF > NEMOCLAW_INSTALL_TAG > lkg tag.
resolve_release_tag() {
if [[ -n "${NEMOCLAW_BOOTSTRAP_FETCH_REF:-}" ]]; then
printf "%s" "${NEMOCLAW_BOOTSTRAP_FETCH_REF}"
return
fi
if [[ -n "${NEMOCLAW_INSTALL_REF:-}" ]]; then
printf "%s" "${NEMOCLAW_INSTALL_REF}"
return
Expand Down
3 changes: 2 additions & 1 deletion scripts/local-credential-helper.mts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import path from "node:path";
import { fileURLToPath } from "node:url";

export const EXPECTED_LOCAL_CREDENTIAL_FORM_SHA256 =
"5512a256e0ad7c63a26ab82cf4f5924e98652097172ab8a5dc9d9358dd4f6ae8"; // gitleaks:allow -- checked-in SHA-256 integrity pin
"cdd85dc6a0b31a8b9773e7ddaacb38a4e8162b0a13b70bebef3dc297ae7a6c44"; // gitleaks:allow -- checked-in SHA-256 integrity pin

export const LOCAL_CREDENTIAL_HELPER_HOST = "127.0.0.1";
export const LOCAL_CREDENTIAL_FORM_PATH = "/local-credential-form.html";
Expand Down Expand Up @@ -110,6 +110,7 @@ const FORBIDDEN_CHILD_ENV_NAMES = new Set([
"NO_PROXY",
"NETRC",
"NEMOCLAW_ACCEPT_DEV_UNVERIFIED_INSTALL",
"NEMOCLAW_BOOTSTRAP_FETCH_REF",
"NEMOCLAW_BOOTSTRAP_PAYLOAD",
"NEMOCLAW_INSTALL_REF",
"NEMOCLAW_INSTALL_TAG",
Expand Down
1 change: 1 addition & 0 deletions src/lib/security/process-control-env.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe("credential-handoff process-control environment policy", () => {
"LOCPATH",
"NETRC",
"NEMOCLAW_ACCEPT_DEV_UNVERIFIED_INSTALL",
"NEMOCLAW_BOOTSTRAP_FETCH_REF",
"NEMOCLAW_BOOTSTRAP_PAYLOAD",
"NEMOCLAW_INSTALL_REF",
"NEMOCLAW_INSTALL_TAG",
Expand Down
1 change: 1 addition & 0 deletions src/lib/security/process-control-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const PROCESS_CONTROL_ENV_NAMES: ReadonlySet<string> = new Set([
"NO_PROXY",
"NETRC",
"NEMOCLAW_ACCEPT_DEV_UNVERIFIED_INSTALL",
"NEMOCLAW_BOOTSTRAP_FETCH_REF",
"NEMOCLAW_BOOTSTRAP_PAYLOAD",
"NEMOCLAW_INSTALL_REF",
"NEMOCLAW_INSTALL_TAG",
Expand Down
30 changes: 19 additions & 11 deletions test/credentials/local-credential-helper-suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const CONFIG_ROOT_ENV_NAMES = [
"LOCPATH",
"NETRC",
"NEMOCLAW_ACCEPT_DEV_UNVERIFIED_INSTALL",
"NEMOCLAW_BOOTSTRAP_FETCH_REF",
"NEMOCLAW_BOOTSTRAP_PAYLOAD",
"NEMOCLAW_INSTALL_REF",
"NEMOCLAW_INSTALL_TAG",
Expand Down Expand Up @@ -624,19 +625,26 @@ export function registerLocalCredentialHelperTests(group: LocalCredentialHelperT
).toThrow();
});

it("wires field rejection through the CLI entrypoint before listening (#5048)", async () => {
const captured = captureChild(
helperArgs(["PATH:text"], [process.execPath, "-e", "process.exit(0)"]),
);
it.each(["PATH", "NEMOCLAW_BOOTSTRAP_FETCH_REF"])(
"rejects %s through the CLI entrypoint before listening (#5048)",
async (name) => {
const captured = captureChild(
helperArgs([`${name}:text`], [process.execPath, "-e", "process.exit(0)"]),
);

const result = await withTimeout(captured.closed, PROCESS_TIMEOUT_MS, "invalid helper CLI");
const result = await withTimeout(
captured.closed,
PROCESS_TIMEOUT_MS,
"invalid helper CLI",
);

expect(result.code).not.toBe(0);
expect(captured.output()).toContain(
"--field PATH is a process-control environment variable and is not allowed",
);
expect(captured.output()).not.toMatch(READINESS_URL_PATTERN);
});
expect(result.code).not.toBe(0);
expect(captured.output()).toContain(
`--field ${name} is a process-control environment variable and is not allowed`,
);
expect(captured.output()).not.toMatch(READINESS_URL_PATTERN);
},
);

it.each([
{ executable: "node" },
Expand Down
1 change: 1 addition & 0 deletions test/generation/starter-prompt-docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const localCredentialConfigControlNames = [
"LOCPATH",
"NETRC",
"NEMOCLAW_ACCEPT_DEV_UNVERIFIED_INSTALL",
"NEMOCLAW_BOOTSTRAP_FETCH_REF",
"NEMOCLAW_BOOTSTRAP_PAYLOAD",
"NEMOCLAW_INSTALL_REF",
"NEMOCLAW_INSTALL_TAG",
Expand Down
Loading
Loading