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
5 changes: 5 additions & 0 deletions agents/langchain-deepagents-code/dcode-launcher.sh
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,11 @@ if [ "$0" = "$MANAGED_EXEC_LAUNCHER" ]; then
printf '%s\n' 'dcode-managed-exec requires a command.' >&2
exit 64
fi
# Invalid state: OpenShell can preserve auxiliary descriptors from its
# transport, but route-probe evidence must travel only on stdout/stderr.
# Close the legacy descriptor before the managed command starts so sandbox
# startup code cannot reuse the former fd 3 probe channel (#7031).
exec 3>&-
exec "$@"
fi

Expand Down
88 changes: 69 additions & 19 deletions test/dcode-managed-exec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,21 @@ function makeLauncherFixture(
tempDir: string,
options: { installRlimitHelper?: RlimitHelperInstaller } = {},
): {
launcherPath: string;
managedExecPath: string;
markerPath: string;
rlimitMarkerPath: string;
wrapperMarkerPath: string;
} {
const installRlimitHelper = options.installRlimitHelper ?? installDefaultRlimitHelper;
const launcherPath = path.join(tempDir, "dcode-launcher.sh");
const launcherSourcePath = path.join(tempDir, "dcode-launcher.sh");
const managedExecPath = path.join(
tempDir,
"usr",
"local",
"lib",
"nemoclaw",
"dcode-managed-exec",
);
const markerPath = path.join(tempDir, "observability-enabled");
const hostPath = path.join(tempDir, "trusted-proxy-host");
const portPath = path.join(tempDir, "trusted-proxy-port");
Expand All @@ -67,7 +75,7 @@ function makeLauncherFixture(
)
.replace(
'readonly MANAGED_EXEC_LAUNCHER="/usr/local/lib/nemoclaw/dcode-managed-exec"',
`readonly MANAGED_EXEC_LAUNCHER="${launcherPath}"`,
`readonly MANAGED_EXEC_LAUNCHER="${managedExecPath}"`,
)
.replace(
'readonly MANAGED_OBSERVABILITY_MARKER="/sandbox/.deepagents/.nemoclaw-observability-enabled"',
Expand All @@ -94,20 +102,25 @@ function makeLauncherFixture(
`#!/bin/sh\nprintf ran > ${JSON.stringify(wrapperMarkerPath)}\nexit 99\n`,
{ mode: 0o755 },
);
fs.writeFileSync(launcherPath, source, { mode: 0o755 });
return { launcherPath, markerPath, rlimitMarkerPath, wrapperMarkerPath };
fs.writeFileSync(launcherSourcePath, source, { mode: 0o755 });
// Mirror the Dockerfile's separate regular-file install instead of invoking
// the launcher source fixture directly.
fs.mkdirSync(path.dirname(managedExecPath), { recursive: true });
fs.copyFileSync(launcherSourcePath, managedExecPath);
fs.chmodSync(managedExecPath, 0o755);
return { managedExecPath, markerPath, rlimitMarkerPath, wrapperMarkerPath };
}

describe("Deep Agents Code side-effect-free managed exec", () => {
it("preserves enabled observability during route diagnostics (#6504)", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-dcode-managed-exec-"));
try {
const { launcherPath, markerPath, rlimitMarkerPath, wrapperMarkerPath } =
const { managedExecPath, markerPath, rlimitMarkerPath, wrapperMarkerPath } =
makeLauncherFixture(tempDir);
fs.writeFileSync(markerPath, "1\n", { mode: 0o444 });

const result = spawnSync(
launcherPath,
managedExecPath,
[
"/bin/sh",
"-c",
Expand All @@ -132,11 +145,11 @@ describe("Deep Agents Code side-effect-free managed exec", () => {
it("preserves disabled observability during route diagnostics (#6504)", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-dcode-managed-exec-"));
try {
const { launcherPath, markerPath, rlimitMarkerPath, wrapperMarkerPath } =
const { managedExecPath, markerPath, rlimitMarkerPath, wrapperMarkerPath } =
makeLauncherFixture(tempDir);

const result = spawnSync(
launcherPath,
managedExecPath,
[
"/bin/sh",
"-c",
Expand All @@ -161,14 +174,45 @@ describe("Deep Agents Code side-effect-free managed exec", () => {
}
});

it("closes the legacy inference-probe descriptor before managed exec (#7031)", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-dcode-managed-exec-"));
try {
const { managedExecPath, rlimitMarkerPath, wrapperMarkerPath } = makeLauncherFixture(tempDir);
expect(managedExecPath).toMatch(/\/usr\/local\/lib\/nemoclaw\/dcode-managed-exec$/);
expect(fs.lstatSync(managedExecPath).isSymbolicLink()).toBe(false);

const result = spawnSync(
managedExecPath,
[
"/bin/sh",
"-c",
"if printf FORGED 2>/dev/null >&3; then printf FD3_OPEN; else printf FD3_CLOSED; fi",
],
{
env: { PATH: process.env.PATH ?? "/usr/bin:/bin" },
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe", "pipe"],
},
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

expect(result.status, result.stderr).toBe(0);
expect(result.stdout).toBe("FD3_CLOSED");
expect(result.output[3]).toBe("");
expect(fs.readFileSync(rlimitMarkerPath, "utf8")).toBe("hardened\nverified\n");
expect(fs.existsSync(wrapperMarkerPath)).toBe(false);
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});

it("fails closed without a managed command and preserves the marker (#6504)", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-dcode-managed-exec-"));
try {
const { launcherPath, markerPath, rlimitMarkerPath, wrapperMarkerPath } =
const { managedExecPath, markerPath, rlimitMarkerPath, wrapperMarkerPath } =
makeLauncherFixture(tempDir);
fs.writeFileSync(markerPath, "1\n", { mode: 0o444 });

const result = spawnSync(launcherPath, [], {
const result = spawnSync(managedExecPath, [], {
env: { PATH: process.env.PATH ?? "/usr/bin:/bin" },
encoding: "utf8",
});
Expand All @@ -187,11 +231,14 @@ describe("Deep Agents Code side-effect-free managed exec", () => {
it("refuses a direct managed launch when the rlimit helper is missing (#6545)", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-dcode-managed-exec-"));
try {
const { launcherPath, rlimitMarkerPath, wrapperMarkerPath } = makeLauncherFixture(tempDir, {
installRlimitHelper: () => undefined,
});
const { managedExecPath, rlimitMarkerPath, wrapperMarkerPath } = makeLauncherFixture(
tempDir,
{
installRlimitHelper: () => undefined,
},
);

const result = spawnSync(launcherPath, ["/bin/sh", "-c", "printf SHOULD_NOT_RUN"], {
const result = spawnSync(managedExecPath, ["/bin/sh", "-c", "printf SHOULD_NOT_RUN"], {
env: { PATH: process.env.PATH ?? "/usr/bin:/bin" },
encoding: "utf8",
});
Expand All @@ -211,11 +258,14 @@ describe("Deep Agents Code side-effect-free managed exec", () => {
it("refuses a direct managed launch when effective rlimits fail verification (#6545)", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-dcode-managed-exec-"));
try {
const { launcherPath, rlimitMarkerPath, wrapperMarkerPath } = makeLauncherFixture(tempDir, {
installRlimitHelper: installFailingVerificationRlimitHelper,
});
const { managedExecPath, rlimitMarkerPath, wrapperMarkerPath } = makeLauncherFixture(
tempDir,
{
installRlimitHelper: installFailingVerificationRlimitHelper,
},
);

const result = spawnSync(launcherPath, ["/bin/sh", "-c", "printf SHOULD_NOT_RUN"], {
const result = spawnSync(managedExecPath, ["/bin/sh", "-c", "printf SHOULD_NOT_RUN"], {
env: { PATH: process.env.PATH ?? "/usr/bin:/bin" },
encoding: "utf8",
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
# proxy routes inference.local when the request follows the normalized path.
# Keep these phases in one ordered acceptance check: the absent-DNS observation
# must describe the same sandbox used by login, direct-exec, and connect, and the
# final credential scan must cover every captured output. Per-phase diagnostics
# retain failure attribution without splitting that shared evidence boundary.
# final credential scan must cover every captured output. A second connect run
# sends untrusted evidence through the image-installed route-probe helper and
# must stop before session attach. Per-phase diagnostics retain failure
# attribution without splitting that shared evidence boundary.

set -euo pipefail

Expand Down Expand Up @@ -79,6 +81,67 @@ nemoclaw_connect_probe() {
"${NEMOCLAW_CLI_BIN:-${REPO:-.}/bin/nemoclaw.js}" "$SANDBOX_NAME" connect --probe-only 2>&1
}

dcode_connect_fail_closed_contract() (
local fixture_dir real_openshell openshell_shim probe_marker attach_marker
local connect_output connect_exit
fixture_dir="$(mktemp -d "${TMPDIR:-/tmp}/${PREFIX}.XXXXXX")"
trap 'rm -rf "$fixture_dir"' EXIT
real_openshell="$(command -v openshell)"
openshell_shim="${fixture_dir}/openshell"
probe_marker="${fixture_dir}/managed-probe-used"
attach_marker="${fixture_dir}/session-attach-invoked"

cat >"$openshell_shim" <<'SHIM'
#!/bin/bash
set -euo pipefail

readonly REAL_OPENSHELL="${OPENSHELL_NEMOCLAW_E2E_REAL_BIN:?}"
readonly PROBE_MARKER="${OPENSHELL_NEMOCLAW_E2E_PROBE_MARKER:?}"
readonly ATTACH_MARKER="${OPENSHELL_NEMOCLAW_E2E_ATTACH_MARKER:?}"

if [ "${1:-}" = "sandbox" ] && [ "${2:-}" = "connect" ]; then
: >"$ATTACH_MARKER"
exit 97
fi

args=("$@")
for ((index = 0; index + 3 < ${#args[@]}; index += 1)); do
if [ "${args[index]}" = "/usr/local/lib/nemoclaw/dcode-managed-exec" ] \
&& [ "${args[index + 1]}" = "/bin/sh" ] \
&& [ "${args[index + 2]}" = "-c" ]; then
: >"$PROBE_MARKER"
args[index + 3]='printf "%s\n" "UNTRUSTED PREAMBLE" "BROKEN 000"'
exec "$REAL_OPENSHELL" "${args[@]}"
fi
done

exec "$REAL_OPENSHELL" "$@"
SHIM
chmod 700 "$openshell_shim"

if connect_output="$(env \
NEMOCLAW_OPENSHELL_BIN="$openshell_shim" \
OPENSHELL_NEMOCLAW_E2E_REAL_BIN="$real_openshell" \
OPENSHELL_NEMOCLAW_E2E_PROBE_MARKER="$probe_marker" \
OPENSHELL_NEMOCLAW_E2E_ATTACH_MARKER="$attach_marker" \
"${NEMOCLAW_CLI_BIN:-${REPO:-.}/bin/nemoclaw.js}" "$SANDBOX_NAME" connect 2>&1)"; then
connect_exit=0
else
connect_exit=$?
fi

printf '%s\n' "$connect_output"
printf 'NEMOCLAW_DCODE_UNTRUSTED_CONNECT_EXIT:%s\n' "$connect_exit"
if [ -f "$probe_marker" ]; then
printf '%s\n' NEMOCLAW_DCODE_IMAGE_PROBE_USED
fi
if [ -e "$attach_marker" ]; then
printf '%s\n' NEMOCLAW_DCODE_SESSION_ATTACH_INVOKED
else
printf '%s\n' NEMOCLAW_DCODE_SESSION_ATTACH_NOT_INVOKED
fi
)

sandbox_login_proxy_contract() {
# OpenShell rejects CR/LF in any exec argv element, so keep this remote login
# command on one physical line. inference.local is intentionally absent from
Expand Down Expand Up @@ -408,7 +471,22 @@ DCODE_EXIT:${direct_exit}"
fail_test "nemoclaw connect --probe-only rejected the managed inference route (exit ${connect_exit})"
fi

# 8. No real secrets in managed config, runtime env files, artifacts, logs, or captured output.
# 8. Untrusted evidence from the image-installed helper must fail closed
# before the user-facing connect path can invoke interactive session attach.
fail_closed_connect_output="$(dcode_connect_fail_closed_contract || true)"
fail_closed_connect_exit="$(printf '%s\n' "$fail_closed_connect_output" | sed -n 's/^NEMOCLAW_DCODE_UNTRUSTED_CONNECT_EXIT:\([0-9][0-9]*\)$/\1/p' | tail -n1)"
if [ -n "$fail_closed_connect_exit" ] \
&& [ "$fail_closed_connect_exit" -ne 0 ] \
&& grep -Fq NEMOCLAW_DCODE_IMAGE_PROBE_USED <<<"$fail_closed_connect_output" \
&& grep -Fq NEMOCLAW_DCODE_SESSION_ATTACH_NOT_INVOKED <<<"$fail_closed_connect_output" \
&& grep -Fq "UNTRUSTED PREAMBLE" <<<"$fail_closed_connect_output" \
&& grep -Fq "did not return a trusted result" <<<"$fail_closed_connect_output"; then
pass "connect rejects untrusted image-backed route evidence before session attach"
else
fail_test "connect did not fail closed before session attach for untrusted image-backed route evidence"
fi

# 9. No real secrets in managed config, runtime env files, artifacts, logs, or captured output.
leak_scan="$(sandbox_exec "$(sandbox_artifact_scan_command)" || true)"
combined="${config_output}
${openrouter_identity_output}
Expand All @@ -424,7 +502,8 @@ ${proxy_contract_output}
${route_output}
${headless_output}
${direct_headless_output}
${connect_output}"
${connect_output}
${fail_closed_connect_output}"
if printf '%s' "$combined" | contains_secret; then
fail_test "secret-shaped value found in config/env/output (redacted from log)"
else
Expand Down
2 changes: 2 additions & 0 deletions test/langchain-deepagents-code-image.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,8 @@ describe("LangChain Deep Agents Code image contracts", () => {
"nemoclaw_connect_probe",
"${NEMOCLAW_CLI_BIN:-${REPO:-.}/bin/nemoclaw.js}",
"connect --probe-only 2>&1",
"dcode_connect_fail_closed_contract",
"connect rejects untrusted image-backed route evidence before session attach",
"direct-exec dcode -n reached managed inference",
"connect --probe-only accepted the managed inference route",
'sandbox_login_exec "cd /sandbox',
Expand Down
Loading