Skip to content

Commit b320b45

Browse files
fix(policy): restore Tavily egress for managed Python (NVIDIA#6134)
<!-- markdownlint-disable MD041 --> ## Summary Restore the managed Deep Agents Code Python interpreter to the Tavily policy and provider-profile allowlists. PR NVIDIA#5969 removed Python while tightening the Tavily boundary, which left `policy-add tavily` successful but Python-originated Tavily requests blocked by OpenShell with HTTP 403. ## Changes - Add `/opt/venv/bin/python3*` to `nemoclaw-blueprint/policies/presets/tavily.yaml`. - Add the same managed interpreter path to `nemoclaw-blueprint/provider-profiles/tavily.yaml` so both enforcement layers agree. - Document why the interpreter is required for OpenShell process attribution. - Update the exact preset and provider-profile allowlist contracts. - Keep system Python paths excluded so the restored access remains limited to the managed Deep Agents environment. ## Type of Change - [x] Code change (feature, bug fix, or refactor) - [ ] Code change with doc updates - [ ] Doc only (prose changes, no code sample modifications) - [ ] Doc only (includes code sample changes) ## Quality Gates <!-- Check all that apply. For any "covered by existing tests", "not applicable", or waiver entry, add a brief justification on the same line or in the Changes section. --> - [x] Tests added or updated for changed behavior - [ ] Existing tests cover changed behavior — justification: - [ ] Tests not applicable — justification: - [ ] Docs updated for user-facing behavior changes - [x] Docs not applicable — justification: existing Deep Agents documentation already states that OpenShell attributes Tavily calls to the managed Python interpreter; this restores the documented behavior. - [x] Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging) - [ ] Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: pending maintainer review; the change restores only `/opt/venv/bin/python3*`, not system Python paths. - [ ] Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue: ## Verification <!-- Check each item you ran and confirmed. Leave unchecked items you skipped. Doc-only changes do not require npm test unless you ran it. --> - [x] PR description includes the DCO sign-off declaration and every commit appears as `Verified` in GitHub - [ ] Git hooks passed during commit and push, or `npx prek run --from-ref main --to-ref HEAD` passes — the diff-scoped fallback passed formatting, schema, repository, and secret-scan hooks; its broad integration lane requires Linux utilities/semantics unavailable on this macOS host, so CI remains authoritative. - [x] Targeted tests pass for changed behavior - [ ] Full `npm test` passes (broad runtime changes only) - [x] Quality Gates section completed with required justifications or waivers - [x] No secrets, API keys, or credentials committed - [ ] `npm run docs` builds without warnings (doc changes only) - [ ] Doc pages follow the [style guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md) (doc changes only) - [ ] New doc pages include SPDX header and frontmatter (new pages only) --- <!-- DCO sign-off is required in this PR description, and every commit must appear as Verified in GitHub. Run: git config user.name && git config user.email --> Signed-off-by: Apurv Kumaria <36614+apurvvkumaria@users.noreply.github.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Updated Tavily-related runtime permissions to allow the managed Python interpreter, which should reduce permission-related failures when running Python-based tasks. * Kept existing allowed binaries unchanged while extending support for the additional Python runtime path. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Apurv Kumaria <36614+apurvvkumaria@users.noreply.github.com> Co-authored-by: Apurv Kumaria <36614+apurvvkumaria@users.noreply.github.com>
1 parent 7d7d1f7 commit b320b45

6 files changed

Lines changed: 69 additions & 2 deletions

File tree

nemoclaw-blueprint/policies/presets/tavily.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ network_policies:
1717
- allow: { method: GET, path: "/**" }
1818
- allow: { method: POST, path: "/**" }
1919
binaries:
20+
# OpenShell attributes Deep Agents Code Tavily requests to this managed
21+
# Python venv, which its strict Landlock policy mounts read-only.
22+
- { path: /opt/venv/bin/python3* }
2023
- { path: /usr/local/bin/node }
2124
- { path: /usr/bin/node }
2225
- { path: /usr/local/bin/curl }

nemoclaw-blueprint/provider-profiles/tavily.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ endpoints:
2020
access: read-write
2121
enforcement: enforce
2222
binaries:
23+
# OpenShell attributes Deep Agents Code Tavily requests to this managed
24+
# Python venv, which its strict Landlock policy mounts read-only.
25+
- /opt/venv/bin/python3*
2326
- /usr/local/bin/node
2427
- /usr/bin/node
2528
- /usr/local/bin/curl

test/e2e/e2e-cloud-experimental/checks/09-deepagents-code-tavily-opt-in.sh

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ SANDBOX_NAME="${SANDBOX_NAME:-${NEMOCLAW_SANDBOX_NAME:-e2e-cloud-onboard}}"
1010
PREFIX="09-deepagents-code-tavily-opt-in"
1111
REPO="${REPO:-$(pwd)}"
1212
CLI="${NEMOCLAW_E2E_CLI:-${REPO}/bin/nemoclaw.js}"
13+
PROJECT_VENV="/sandbox/.nemoclaw-e2e-project-venv"
14+
PROJECT_PYTHON="${PROJECT_VENV}/bin/python3"
1315

1416
ok() { printf '%s\n' "${PREFIX}: OK ($*)"; }
1517
info() { printf '%s\n' "${PREFIX}: $*"; }
@@ -93,13 +95,14 @@ PY
9395

9496
python_probe() {
9597
local url="$1"
98+
local python_bin="${2:-python3}"
9699
local encoded remote_cmd
97100
if [ -n "${NEMOCLAW_E2E_TAVILY_PROBE_FIXTURE+x}" ]; then
98101
printf '%s\n' "$NEMOCLAW_E2E_TAVILY_PROBE_FIXTURE"
99102
return 0
100103
fi
101104
encoded="$(python_probe_source | base64 | tr -d '\n')"
102-
remote_cmd="python3 -c \"\$(printf '%s' ${encoded@Q} | base64 -d)\" ${url@Q}"
105+
remote_cmd="${python_bin@Q} -c \"\$(printf '%s' ${encoded@Q} | base64 -d)\" ${url@Q}"
103106
sandbox_exec "$remote_cmd"
104107
}
105108

@@ -167,5 +170,28 @@ else
167170
fail_test "Tavily probe lacked reachability evidence after policy-add: $PROBE_OUTPUT"
168171
fi
169172

173+
SYSTEM_PROBE_OUTPUT="$(python_probe "https://api.tavily.com/" "/usr/bin/python3" || true)"
174+
if echo "$SYSTEM_PROBE_OUTPUT" | grep -q "BLOCKED:" && ! echo "$SYSTEM_PROBE_OUTPUT" | grep -q "REACHED:"; then
175+
pass "system Python remains blocked from Tavily after policy-add"
176+
elif echo "$SYSTEM_PROBE_OUTPUT" | grep -q "REACHED:"; then
177+
fail_test "system Python reached Tavily unexpectedly after policy-add: $SYSTEM_PROBE_OUTPUT"
178+
else
179+
fail_test "system Python Tavily probe lacked denial evidence after policy-add: $SYSTEM_PROBE_OUTPUT"
180+
fi
181+
182+
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)"
183+
if echo "$PROJECT_OUT" | grep -Fxq "$PROJECT_PYTHON"; then
184+
PROJECT_PROBE_OUTPUT="$(python_probe "https://api.tavily.com/" "$PROJECT_PYTHON" || true)"
185+
if echo "$PROJECT_PROBE_OUTPUT" | grep -q "BLOCKED:" && ! echo "$PROJECT_PROBE_OUTPUT" | grep -q "REACHED:"; then
186+
pass "project venv Python under /sandbox remains blocked from Tavily after policy-add"
187+
elif echo "$PROJECT_PROBE_OUTPUT" | grep -q "REACHED:"; then
188+
fail_test "project venv Python reached Tavily unexpectedly after policy-add: $PROJECT_PROBE_OUTPUT"
189+
else
190+
fail_test "project venv Python Tavily probe lacked denial evidence after policy-add: $PROJECT_PROBE_OUTPUT"
191+
fi
192+
else
193+
fail_test "project venv under /sandbox did not expose a usable python3 executable: $PROJECT_OUT"
194+
fi
195+
170196
printf '%s\n' "${PREFIX}: $PASSED passed, $FAILED failed"
171197
[ "$FAILED" -eq 0 ] || exit 1

test/langchain-deepagents-code-image.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,10 +520,18 @@ describe("LangChain Deep Agents Code image contracts", () => {
520520
expect(tavilyOptInCheck).toContain("https://api.tavily.com/");
521521
expect(tavilyOptInCheck).toContain("python_probe_source");
522522
expect(tavilyOptInCheck).toContain("base64 | tr -d");
523-
expect(tavilyOptInCheck).toContain("python3 -c");
523+
expect(tavilyOptInCheck).toContain("${python_bin@Q} -c");
524524
expect(tavilyOptInCheck).toContain("NEMOCLAW_E2E_TAVILY_SELF_TEST");
525525
expect(tavilyOptInCheck).toContain("/opt/venv/");
526526
expect(tavilyOptInCheck).toContain("managed Deep Agents Code python can reach Tavily");
527+
expect(tavilyOptInCheck).toContain('python_probe "https://api.tavily.com/" "/usr/bin/python3"');
528+
expect(tavilyOptInCheck).toContain(
529+
"system Python remains blocked from Tavily after policy-add",
530+
);
531+
expect(tavilyOptInCheck).toContain("/sandbox/.nemoclaw-e2e-project-venv");
532+
expect(tavilyOptInCheck).toContain(
533+
"project venv Python under /sandbox remains blocked from Tavily after policy-add",
534+
);
527535
expect(cloudExperimentalChecksForOnboarding("cloud-langchain-deepagents-code")).toEqual([
528536
"test/e2e/e2e-cloud-experimental/checks/05-deepagents-code-landlock-readonly.sh",
529537
"test/e2e/e2e-cloud-experimental/checks/06-deepagents-code-python-egress.sh",

test/tavily-preset.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ describe("tavily opt-in preset", () => {
4545
},
4646
]);
4747
expect(policy?.binaries).toEqual([
48+
{ path: "/opt/venv/bin/python3*" },
4849
{ path: "/usr/local/bin/node" },
4950
{ path: "/usr/bin/node" },
5051
{ path: "/usr/local/bin/curl" },

test/validate-blueprint.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ const TAVILY_PROVIDER_PROFILE_PATH = new URL(
2929
"../nemoclaw-blueprint/provider-profiles/tavily.yaml",
3030
import.meta.url,
3131
);
32+
const TAVILY_POLICY_PRESET_PATH = new URL(
33+
"../nemoclaw-blueprint/policies/presets/tavily.yaml",
34+
import.meta.url,
35+
);
36+
const DEEPAGENTS_POLICY_PATH = new URL(
37+
"../agents/langchain-deepagents-code/policy-additions.yaml",
38+
import.meta.url,
39+
);
3240
const PERMISSIVE_POLICY_PATH = new URL(
3341
"../nemoclaw-blueprint/policies/openclaw-sandbox-permissive.yaml",
3442
import.meta.url,
@@ -87,6 +95,7 @@ type PolicyEntry = {
8795

8896
type SandboxPolicy = {
8997
version?: number;
98+
filesystem_policy?: { read_only?: string[] };
9099
network_policies?: Record<string, PolicyEntry>;
91100
};
92101

@@ -479,6 +488,8 @@ describe("Brave Search provider profile", () => {
479488

480489
describe("Tavily Search provider profile", () => {
481490
const profile = loadYaml<ProviderProfile>(TAVILY_PROVIDER_PROFILE_PATH);
491+
const preset = loadYaml<PolicyPreset>(TAVILY_POLICY_PRESET_PATH);
492+
const deepAgentsPolicy = loadYaml<SandboxPolicy>(DEEPAGENTS_POLICY_PATH);
482493

483494
it("routes TAVILY_API_KEY through a bearer authorization header", () => {
484495
expect(profile.id).toBe("tavily");
@@ -505,12 +516,27 @@ describe("Tavily Search provider profile", () => {
505516

506517
it("limits the binary allowlist to runtimes the Tavily client actually uses", () => {
507518
expect(profile.binaries).toEqual([
519+
"/opt/venv/bin/python3*",
508520
"/usr/local/bin/node",
509521
"/usr/bin/node",
510522
"/usr/local/bin/curl",
511523
"/usr/bin/curl",
512524
]);
513525
});
526+
527+
it("keeps its binary allowlist aligned with the Tavily policy preset", () => {
528+
const presetBinaries = preset.network_policies?.tavily?.binaries?.map(({ path }) => path);
529+
expect(profile.binaries).toEqual(presetBinaries);
530+
});
531+
532+
it("anchors managed Python access to Deep Agents Code's read-only venv", () => {
533+
const managedPython = "/opt/venv/bin/python3*";
534+
const managedInferenceBinaries = deepAgentsPolicy.network_policies?.managed_inference?.binaries;
535+
536+
expect(deepAgentsPolicy.filesystem_policy?.read_only).toContain("/opt/venv");
537+
expect(managedInferenceBinaries).toContainEqual({ path: managedPython });
538+
expect(profile.binaries).toContain(managedPython);
539+
});
514540
});
515541

516542
describe("permissive sandbox policy", () => {

0 commit comments

Comments
 (0)