Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 1.2k
feat(sandbox): upgrade Landlock to ABI V2 and fix sandbox venv PATH#151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
667eccf3f88213a14e2986b382cd34b54ec78077caFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -95,6 +95,28 @@ network_policies: | ||
| - { path: /usr/local/bin/claude } | ||
| - { path: /usr/bin/gh } | ||
| pypi: | ||
| name: pypi | ||
| endpoints: | ||
| - { host: pypi.org, port: 443 } | ||
| - { host: files.pythonhosted.org, port: 443 } | ||
| # uv python install downloads from python-build-standalone on GitHub | ||
| - { host: github.com, port: 443 } | ||
| - { host: objects.githubusercontent.com, port: 443 } | ||
| # uv resolves python-build-standalone release metadata via the GitHub API | ||
| - { host: api.github.com, port: 443 } | ||
| - { host: downloads.python.org, port: 443 } | ||
Comment on lines
+101
to
+108
Collaborator There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. awesome to have this included in the default sandbox | ||
| binaries: | ||
| - { path: /sandbox/.venv/bin/python } | ||
| - { path: /sandbox/.venv/bin/python3 } | ||
| - { path: /sandbox/.venv/bin/pip } | ||
| - { path: /app/.venv/bin/python } | ||
| - { path: /app/.venv/bin/python3 } | ||
| - { path: /app/.venv/bin/pip } | ||
| - { path: /usr/local/bin/uv } | ||
| # Managed Python installations from uv python install | ||
| - { path: "/sandbox/.uv/python/**" } | ||
| vscode: | ||
| name: vscode | ||
| endpoints: | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| """Tests for the writable sandbox venv, PATH, and package installation. | ||
| Verifies that: | ||
| - /sandbox/.venv/bin is in PATH for both interactive and non-interactive sessions | ||
| - pip install works inside the sandbox (pypi policy in dev-sandbox-policy.yaml) | ||
| - uv pip install works (validates Landlock V2 cross-directory rename support) | ||
| - uv run --with works for ephemeral dependency injection | ||
| - Installed packages are importable after installation | ||
| All tests use the default dev sandbox policy -- no custom policy overrides. | ||
| The SDK omits the policy field from the spec so the sandbox container discovers | ||
| its policy from /etc/navigator/policy.yaml (the dev-sandbox-policy.yaml baked | ||
| into the image), which already includes the pypi network policy. | ||
| """ | ||
| from __future__ import annotations | ||
| from typing import TYPE_CHECKING | ||
| if TYPE_CHECKING: | ||
| from collections.abc import Callable | ||
| from navigator import Sandbox | ||
| def test_sandbox_venv_in_path( | ||
| sandbox: Callable[..., Sandbox], | ||
| ) -> None: | ||
| """Non-interactive exec sees /sandbox/.venv/bin in PATH.""" | ||
| with sandbox(delete_on_exit=True) as sb: | ||
| result = sb.exec(["bash", "-c", "echo $PATH"], timeout_seconds=20) | ||
| assert result.exit_code == 0, result.stderr | ||
| path_dirs = result.stdout.strip().split(":") | ||
| assert "/sandbox/.venv/bin" in path_dirs, ( | ||
| f"Expected /sandbox/.venv/bin in PATH, got: {result.stdout.strip()}" | ||
| ) | ||
| # /sandbox/.venv/bin must come before /app/.venv/bin | ||
| sandbox_idx = path_dirs.index("/sandbox/.venv/bin") | ||
| app_idx = path_dirs.index("/app/.venv/bin") | ||
| assert sandbox_idx < app_idx, ( | ||
| "/sandbox/.venv/bin must precede /app/.venv/bin in PATH" | ||
| ) | ||
| def test_pip_install_in_sandbox( | ||
| sandbox: Callable[..., Sandbox], | ||
| ) -> None: | ||
| """pip install works inside the sandbox and installed packages are importable.""" | ||
| with sandbox(delete_on_exit=True) as sb: | ||
| install = sb.exec( | ||
| ["pip", "install", "--quiet", "cowsay"], | ||
| timeout_seconds=60, | ||
| ) | ||
| assert install.exit_code == 0, ( | ||
| f"pip install failed:\nstdout: {install.stdout}\nstderr: {install.stderr}" | ||
| ) | ||
| # Verify the package is importable | ||
| verify = sb.exec( | ||
| ["python", "-c", "import cowsay; print(cowsay.char_names[0])"], | ||
| timeout_seconds=20, | ||
| ) | ||
| assert verify.exit_code == 0, ( | ||
| f"import failed:\nstdout: {verify.stdout}\nstderr: {verify.stderr}" | ||
| ) | ||
| assert verify.stdout.strip(), "Expected non-empty output from cowsay" | ||
| def test_uv_pip_install_in_sandbox( | ||
| sandbox: Callable[..., Sandbox], | ||
| ) -> None: | ||
| """uv pip install works inside the sandbox (validates Landlock V2 REFER support). | ||
| Under Landlock V1 this would fail with EXDEV (cross-device link, os error 18) | ||
| because uv uses cross-directory rename() for cache population and installation. | ||
| Landlock V2 adds the REFER right which permits this. | ||
| """ | ||
| with sandbox(delete_on_exit=True) as sb: | ||
| install = sb.exec( | ||
| [ | ||
| "uv", | ||
| "pip", | ||
| "install", | ||
| "--python", | ||
| "/sandbox/.venv/bin/python", | ||
| "--quiet", | ||
| "cowsay", | ||
| ], | ||
| timeout_seconds=60, | ||
| ) | ||
| assert install.exit_code == 0, ( | ||
| f"uv pip install failed:\nstdout: {install.stdout}\nstderr: {install.stderr}" | ||
| ) | ||
| # Verify the package is importable | ||
| verify = sb.exec( | ||
| ["python", "-c", "import cowsay; print(cowsay.char_names[0])"], | ||
| timeout_seconds=20, | ||
| ) | ||
| assert verify.exit_code == 0, ( | ||
| f"import failed after uv install:\n" | ||
| f"stdout: {verify.stdout}\nstderr: {verify.stderr}" | ||
| ) | ||
| assert verify.stdout.strip(), "Expected non-empty output from cowsay" | ||
| def test_uv_run_with_ephemeral_dependency( | ||
| sandbox: Callable[..., Sandbox], | ||
| ) -> None: | ||
| """uv run --with installs a dependency on-the-fly and runs a script using it.""" | ||
| with sandbox(delete_on_exit=True) as sb: | ||
| result = sb.exec( | ||
| [ | ||
| "uv", | ||
| "run", | ||
| "--python", | ||
| "/sandbox/.venv/bin/python", | ||
| "--with", | ||
| "cowsay", | ||
| "python", | ||
| "-c", | ||
| "import cowsay; print(cowsay.char_names[0])", | ||
| ], | ||
| timeout_seconds=60, | ||
| ) | ||
| assert result.exit_code == 0, ( | ||
| f"uv run --with failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" | ||
| ) | ||
| assert result.stdout.strip(), "Expected non-empty output from uv run" |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sweet!!