From d325e2cba12f02fb2f002daa4f65d4fc0d58d7a4 Mon Sep 17 00:00:00 2001 From: Apurv Kumaria <36614+apurvvkumaria@users.noreply.github.com> Date: Wed, 1 Jul 2026 10:58:36 -0700 Subject: [PATCH 1/3] fix(policy): restore Tavily egress for managed Python Signed-off-by: Apurv Kumaria <36614+apurvvkumaria@users.noreply.github.com> --- nemoclaw-blueprint/policies/presets/tavily.yaml | 2 ++ nemoclaw-blueprint/provider-profiles/tavily.yaml | 2 ++ test/tavily-preset.test.ts | 1 + test/validate-blueprint.test.ts | 1 + 4 files changed, 6 insertions(+) diff --git a/nemoclaw-blueprint/policies/presets/tavily.yaml b/nemoclaw-blueprint/policies/presets/tavily.yaml index 192fe0eb941..19f3147461e 100644 --- a/nemoclaw-blueprint/policies/presets/tavily.yaml +++ b/nemoclaw-blueprint/policies/presets/tavily.yaml @@ -17,6 +17,8 @@ network_policies: - allow: { method: GET, path: "/**" } - allow: { method: POST, path: "/**" } binaries: + # OpenShell attributes Deep Agents Code Tavily requests to its managed Python venv. + - { path: /opt/venv/bin/python3* } - { path: /usr/local/bin/node } - { path: /usr/bin/node } - { path: /usr/local/bin/curl } diff --git a/nemoclaw-blueprint/provider-profiles/tavily.yaml b/nemoclaw-blueprint/provider-profiles/tavily.yaml index 1e74bef9ea4..bf8b5ccdbbd 100644 --- a/nemoclaw-blueprint/provider-profiles/tavily.yaml +++ b/nemoclaw-blueprint/provider-profiles/tavily.yaml @@ -20,6 +20,8 @@ endpoints: access: read-write enforcement: enforce binaries: + # OpenShell attributes Deep Agents Code Tavily requests to its managed Python venv. + - /opt/venv/bin/python3* - /usr/local/bin/node - /usr/bin/node - /usr/local/bin/curl diff --git a/test/tavily-preset.test.ts b/test/tavily-preset.test.ts index 9f09d276499..1f2b0ea0c9b 100644 --- a/test/tavily-preset.test.ts +++ b/test/tavily-preset.test.ts @@ -45,6 +45,7 @@ describe("tavily opt-in preset", () => { }, ]); expect(policy?.binaries).toEqual([ + { path: "/opt/venv/bin/python3*" }, { path: "/usr/local/bin/node" }, { path: "/usr/bin/node" }, { path: "/usr/local/bin/curl" }, diff --git a/test/validate-blueprint.test.ts b/test/validate-blueprint.test.ts index f633ebe8d9e..2510b6df484 100644 --- a/test/validate-blueprint.test.ts +++ b/test/validate-blueprint.test.ts @@ -505,6 +505,7 @@ describe("Tavily Search provider profile", () => { it("limits the binary allowlist to runtimes the Tavily client actually uses", () => { expect(profile.binaries).toEqual([ + "/opt/venv/bin/python3*", "/usr/local/bin/node", "/usr/bin/node", "/usr/local/bin/curl", From 9bb50720e8d930a66d182d738892444190a80a42 Mon Sep 17 00:00:00 2001 From: Apurv Kumaria <36614+apurvvkumaria@users.noreply.github.com> Date: Wed, 1 Jul 2026 11:24:16 -0700 Subject: [PATCH 2/3] test(policy): lock Tavily managed Python boundary Signed-off-by: Apurv Kumaria <36614+apurvvkumaria@users.noreply.github.com> --- nemoclaw-blueprint/policies/presets/tavily.yaml | 3 ++- nemoclaw-blueprint/provider-profiles/tavily.yaml | 3 ++- .../checks/09-deepagents-code-tavily-opt-in.sh | 12 +++++++++++- test/langchain-deepagents-code-image.test.ts | 6 +++++- test/validate-blueprint.test.ts | 10 ++++++++++ 5 files changed, 30 insertions(+), 4 deletions(-) diff --git a/nemoclaw-blueprint/policies/presets/tavily.yaml b/nemoclaw-blueprint/policies/presets/tavily.yaml index 19f3147461e..94705a34cf8 100644 --- a/nemoclaw-blueprint/policies/presets/tavily.yaml +++ b/nemoclaw-blueprint/policies/presets/tavily.yaml @@ -17,7 +17,8 @@ network_policies: - allow: { method: GET, path: "/**" } - allow: { method: POST, path: "/**" } binaries: - # OpenShell attributes Deep Agents Code Tavily requests to its managed Python venv. + # OpenShell attributes Deep Agents Code Tavily requests to this managed + # Python venv, which its strict Landlock policy mounts read-only. - { path: /opt/venv/bin/python3* } - { path: /usr/local/bin/node } - { path: /usr/bin/node } diff --git a/nemoclaw-blueprint/provider-profiles/tavily.yaml b/nemoclaw-blueprint/provider-profiles/tavily.yaml index bf8b5ccdbbd..7ec89679359 100644 --- a/nemoclaw-blueprint/provider-profiles/tavily.yaml +++ b/nemoclaw-blueprint/provider-profiles/tavily.yaml @@ -20,7 +20,8 @@ endpoints: access: read-write enforcement: enforce binaries: - # OpenShell attributes Deep Agents Code Tavily requests to its managed Python venv. + # OpenShell attributes Deep Agents Code Tavily requests to this managed + # Python venv, which its strict Landlock policy mounts read-only. - /opt/venv/bin/python3* - /usr/local/bin/node - /usr/bin/node diff --git a/test/e2e/e2e-cloud-experimental/checks/09-deepagents-code-tavily-opt-in.sh b/test/e2e/e2e-cloud-experimental/checks/09-deepagents-code-tavily-opt-in.sh index d7784ccdfe1..b898f08c2b7 100755 --- a/test/e2e/e2e-cloud-experimental/checks/09-deepagents-code-tavily-opt-in.sh +++ b/test/e2e/e2e-cloud-experimental/checks/09-deepagents-code-tavily-opt-in.sh @@ -93,13 +93,14 @@ PY python_probe() { local url="$1" + local python_bin="${2:-python3}" local encoded remote_cmd if [ -n "${NEMOCLAW_E2E_TAVILY_PROBE_FIXTURE+x}" ]; then printf '%s\n' "$NEMOCLAW_E2E_TAVILY_PROBE_FIXTURE" return 0 fi encoded="$(python_probe_source | base64 | tr -d '\n')" - remote_cmd="python3 -c \"\$(printf '%s' ${encoded@Q} | base64 -d)\" ${url@Q}" + remote_cmd="${python_bin@Q} -c \"\$(printf '%s' ${encoded@Q} | base64 -d)\" ${url@Q}" sandbox_exec "$remote_cmd" } @@ -167,5 +168,14 @@ else fail_test "Tavily probe lacked reachability evidence after policy-add: $PROBE_OUTPUT" fi +SYSTEM_PROBE_OUTPUT="$(python_probe "https://api.tavily.com/" "/usr/bin/python3" || true)" +if echo "$SYSTEM_PROBE_OUTPUT" | grep -q "BLOCKED:" && ! echo "$SYSTEM_PROBE_OUTPUT" | grep -q "REACHED:"; then + pass "system Python remains blocked from Tavily after policy-add" +elif echo "$SYSTEM_PROBE_OUTPUT" | grep -q "REACHED:"; then + fail_test "system Python reached Tavily unexpectedly after policy-add: $SYSTEM_PROBE_OUTPUT" +else + fail_test "system Python Tavily probe lacked denial evidence after policy-add: $SYSTEM_PROBE_OUTPUT" +fi + printf '%s\n' "${PREFIX}: $PASSED passed, $FAILED failed" [ "$FAILED" -eq 0 ] || exit 1 diff --git a/test/langchain-deepagents-code-image.test.ts b/test/langchain-deepagents-code-image.test.ts index 7f9a9e56dc9..c63c6fa9120 100644 --- a/test/langchain-deepagents-code-image.test.ts +++ b/test/langchain-deepagents-code-image.test.ts @@ -520,10 +520,14 @@ describe("LangChain Deep Agents Code image contracts", () => { expect(tavilyOptInCheck).toContain("https://api.tavily.com/"); expect(tavilyOptInCheck).toContain("python_probe_source"); expect(tavilyOptInCheck).toContain("base64 | tr -d"); - expect(tavilyOptInCheck).toContain("python3 -c"); + expect(tavilyOptInCheck).toContain("${python_bin@Q} -c"); expect(tavilyOptInCheck).toContain("NEMOCLAW_E2E_TAVILY_SELF_TEST"); expect(tavilyOptInCheck).toContain("/opt/venv/"); expect(tavilyOptInCheck).toContain("managed Deep Agents Code python can reach Tavily"); + expect(tavilyOptInCheck).toContain('python_probe "https://api.tavily.com/" "/usr/bin/python3"'); + expect(tavilyOptInCheck).toContain( + "system Python remains blocked from Tavily after policy-add", + ); expect(cloudExperimentalChecksForOnboarding("cloud-langchain-deepagents-code")).toEqual([ "test/e2e/e2e-cloud-experimental/checks/05-deepagents-code-landlock-readonly.sh", "test/e2e/e2e-cloud-experimental/checks/06-deepagents-code-python-egress.sh", diff --git a/test/validate-blueprint.test.ts b/test/validate-blueprint.test.ts index 2510b6df484..6e3c1c0731a 100644 --- a/test/validate-blueprint.test.ts +++ b/test/validate-blueprint.test.ts @@ -29,6 +29,10 @@ const TAVILY_PROVIDER_PROFILE_PATH = new URL( "../nemoclaw-blueprint/provider-profiles/tavily.yaml", import.meta.url, ); +const TAVILY_POLICY_PRESET_PATH = new URL( + "../nemoclaw-blueprint/policies/presets/tavily.yaml", + import.meta.url, +); const PERMISSIVE_POLICY_PATH = new URL( "../nemoclaw-blueprint/policies/openclaw-sandbox-permissive.yaml", import.meta.url, @@ -479,6 +483,7 @@ describe("Brave Search provider profile", () => { describe("Tavily Search provider profile", () => { const profile = loadYaml(TAVILY_PROVIDER_PROFILE_PATH); + const preset = loadYaml(TAVILY_POLICY_PRESET_PATH); it("routes TAVILY_API_KEY through a bearer authorization header", () => { expect(profile.id).toBe("tavily"); @@ -512,6 +517,11 @@ describe("Tavily Search provider profile", () => { "/usr/bin/curl", ]); }); + + it("keeps its binary allowlist aligned with the Tavily policy preset", () => { + const presetBinaries = preset.network_policies?.tavily?.binaries?.map(({ path }) => path); + expect(profile.binaries).toEqual(presetBinaries); + }); }); describe("permissive sandbox policy", () => { From f2a161c37743a92984488a71f18d02d5b08d99a8 Mon Sep 17 00:00:00 2001 From: Apurv Kumaria <36614+apurvvkumaria@users.noreply.github.com> Date: Wed, 1 Jul 2026 11:28:49 -0700 Subject: [PATCH 3/3] test(policy): cover Tavily project venv denial Signed-off-by: Apurv Kumaria <36614+apurvvkumaria@users.noreply.github.com> --- .../checks/09-deepagents-code-tavily-opt-in.sh | 16 ++++++++++++++++ test/langchain-deepagents-code-image.test.ts | 4 ++++ test/validate-blueprint.test.ts | 15 +++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/test/e2e/e2e-cloud-experimental/checks/09-deepagents-code-tavily-opt-in.sh b/test/e2e/e2e-cloud-experimental/checks/09-deepagents-code-tavily-opt-in.sh index b898f08c2b7..40c0d4db181 100755 --- a/test/e2e/e2e-cloud-experimental/checks/09-deepagents-code-tavily-opt-in.sh +++ b/test/e2e/e2e-cloud-experimental/checks/09-deepagents-code-tavily-opt-in.sh @@ -10,6 +10,8 @@ SANDBOX_NAME="${SANDBOX_NAME:-${NEMOCLAW_SANDBOX_NAME:-e2e-cloud-onboard}}" PREFIX="09-deepagents-code-tavily-opt-in" REPO="${REPO:-$(pwd)}" CLI="${NEMOCLAW_E2E_CLI:-${REPO}/bin/nemoclaw.js}" +PROJECT_VENV="/sandbox/.nemoclaw-e2e-project-venv" +PROJECT_PYTHON="${PROJECT_VENV}/bin/python3" ok() { printf '%s\n' "${PREFIX}: OK ($*)"; } info() { printf '%s\n' "${PREFIX}: $*"; } @@ -177,5 +179,19 @@ else fail_test "system Python Tavily probe lacked denial evidence after policy-add: $SYSTEM_PROBE_OUTPUT" fi +PROJECT_OUT="$(sandbox_exec "if ! test -x ${PROJECT_PYTHON@Q}; then python3 -m venv --copies ${PROJECT_VENV@Q}; fi; test -x ${PROJECT_PYTHON@Q} && readlink -f ${PROJECT_PYTHON@Q}" || true)" +if echo "$PROJECT_OUT" | grep -Fxq "$PROJECT_PYTHON"; then + PROJECT_PROBE_OUTPUT="$(python_probe "https://api.tavily.com/" "$PROJECT_PYTHON" || true)" + if echo "$PROJECT_PROBE_OUTPUT" | grep -q "BLOCKED:" && ! echo "$PROJECT_PROBE_OUTPUT" | grep -q "REACHED:"; then + pass "project venv Python under /sandbox remains blocked from Tavily after policy-add" + elif echo "$PROJECT_PROBE_OUTPUT" | grep -q "REACHED:"; then + fail_test "project venv Python reached Tavily unexpectedly after policy-add: $PROJECT_PROBE_OUTPUT" + else + fail_test "project venv Python Tavily probe lacked denial evidence after policy-add: $PROJECT_PROBE_OUTPUT" + fi +else + fail_test "project venv under /sandbox did not expose a usable python3 executable: $PROJECT_OUT" +fi + printf '%s\n' "${PREFIX}: $PASSED passed, $FAILED failed" [ "$FAILED" -eq 0 ] || exit 1 diff --git a/test/langchain-deepagents-code-image.test.ts b/test/langchain-deepagents-code-image.test.ts index c63c6fa9120..1edfb5f4d09 100644 --- a/test/langchain-deepagents-code-image.test.ts +++ b/test/langchain-deepagents-code-image.test.ts @@ -528,6 +528,10 @@ describe("LangChain Deep Agents Code image contracts", () => { expect(tavilyOptInCheck).toContain( "system Python remains blocked from Tavily after policy-add", ); + expect(tavilyOptInCheck).toContain("/sandbox/.nemoclaw-e2e-project-venv"); + expect(tavilyOptInCheck).toContain( + "project venv Python under /sandbox remains blocked from Tavily after policy-add", + ); expect(cloudExperimentalChecksForOnboarding("cloud-langchain-deepagents-code")).toEqual([ "test/e2e/e2e-cloud-experimental/checks/05-deepagents-code-landlock-readonly.sh", "test/e2e/e2e-cloud-experimental/checks/06-deepagents-code-python-egress.sh", diff --git a/test/validate-blueprint.test.ts b/test/validate-blueprint.test.ts index 6e3c1c0731a..22d9cc05dcd 100644 --- a/test/validate-blueprint.test.ts +++ b/test/validate-blueprint.test.ts @@ -33,6 +33,10 @@ const TAVILY_POLICY_PRESET_PATH = new URL( "../nemoclaw-blueprint/policies/presets/tavily.yaml", import.meta.url, ); +const DEEPAGENTS_POLICY_PATH = new URL( + "../agents/langchain-deepagents-code/policy-additions.yaml", + import.meta.url, +); const PERMISSIVE_POLICY_PATH = new URL( "../nemoclaw-blueprint/policies/openclaw-sandbox-permissive.yaml", import.meta.url, @@ -91,6 +95,7 @@ type PolicyEntry = { type SandboxPolicy = { version?: number; + filesystem_policy?: { read_only?: string[] }; network_policies?: Record; }; @@ -484,6 +489,7 @@ describe("Brave Search provider profile", () => { describe("Tavily Search provider profile", () => { const profile = loadYaml(TAVILY_PROVIDER_PROFILE_PATH); const preset = loadYaml(TAVILY_POLICY_PRESET_PATH); + const deepAgentsPolicy = loadYaml(DEEPAGENTS_POLICY_PATH); it("routes TAVILY_API_KEY through a bearer authorization header", () => { expect(profile.id).toBe("tavily"); @@ -522,6 +528,15 @@ describe("Tavily Search provider profile", () => { const presetBinaries = preset.network_policies?.tavily?.binaries?.map(({ path }) => path); expect(profile.binaries).toEqual(presetBinaries); }); + + it("anchors managed Python access to Deep Agents Code's read-only venv", () => { + const managedPython = "/opt/venv/bin/python3*"; + const managedInferenceBinaries = deepAgentsPolicy.network_policies?.managed_inference?.binaries; + + expect(deepAgentsPolicy.filesystem_policy?.read_only).toContain("/opt/venv"); + expect(managedInferenceBinaries).toContainEqual({ path: managedPython }); + expect(profile.binaries).toContain(managedPython); + }); }); describe("permissive sandbox policy", () => {