200 lines
5.7 KiB
Python
200 lines
5.7 KiB
Python
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path, PurePosixPath
|
|
|
|
import pytest
|
|
|
|
from agents.sandbox.session.runtime_helpers import (
|
|
RESOLVE_WORKSPACE_PATH_HELPER,
|
|
RuntimeHelperScript,
|
|
)
|
|
|
|
requires_posix_shell = pytest.mark.skipif(
|
|
sys.platform == "win32",
|
|
reason="runtime helper shell script tests require a POSIX shell",
|
|
)
|
|
|
|
|
|
def _install_resolve_helper(tmp_path: Path) -> Path:
|
|
helper_path = tmp_path / "resolve-workspace-path"
|
|
helper_path.write_text(RESOLVE_WORKSPACE_PATH_HELPER.content, encoding="utf-8")
|
|
helper_path.chmod(0o755)
|
|
return helper_path
|
|
|
|
|
|
def test_runtime_helper_from_content_uses_posix_install_path() -> None:
|
|
helper = RuntimeHelperScript.from_content(
|
|
name="test-helper",
|
|
content="#!/bin/sh\nprintf 'ok\\n'",
|
|
)
|
|
|
|
assert isinstance(helper.install_path, PurePosixPath)
|
|
assert helper.install_path.as_posix().startswith("/tmp/openai-agents/bin/test-helper-")
|
|
assert str(helper.install_path).startswith("/tmp/openai-agents/bin/test-helper-")
|
|
|
|
|
|
@requires_posix_shell
|
|
def test_resolve_workspace_path_helper_allows_extra_root_symlink_target(tmp_path: Path) -> None:
|
|
helper_path = _install_resolve_helper(tmp_path)
|
|
workspace = tmp_path / "workspace"
|
|
extra_root = tmp_path / "tmp"
|
|
workspace.mkdir()
|
|
extra_root.mkdir()
|
|
target = extra_root / "result.txt"
|
|
target.write_text("scratch output", encoding="utf-8")
|
|
(workspace / "tmp-link").symlink_to(extra_root, target_is_directory=True)
|
|
|
|
result = subprocess.run(
|
|
[
|
|
str(helper_path),
|
|
str(workspace),
|
|
str(workspace / "tmp-link" / "result.txt"),
|
|
"0",
|
|
str(extra_root),
|
|
"0",
|
|
],
|
|
check=False,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
|
|
assert result.returncode == 0
|
|
assert result.stdout == f"{target.resolve(strict=False)}\n"
|
|
assert result.stderr == ""
|
|
|
|
|
|
@requires_posix_shell
|
|
def test_resolve_workspace_path_helper_rejects_extra_root_when_not_allowed(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
helper_path = _install_resolve_helper(tmp_path)
|
|
workspace = tmp_path / "workspace"
|
|
extra_root = tmp_path / "tmp"
|
|
workspace.mkdir()
|
|
extra_root.mkdir()
|
|
target = extra_root / "result.txt"
|
|
target.write_text("scratch output", encoding="utf-8")
|
|
(workspace / "tmp-link").symlink_to(extra_root, target_is_directory=True)
|
|
|
|
result = subprocess.run(
|
|
[
|
|
str(helper_path),
|
|
str(workspace),
|
|
str(workspace / "tmp-link" / "result.txt"),
|
|
"0",
|
|
],
|
|
check=False,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
|
|
assert result.returncode == 111
|
|
assert result.stdout == ""
|
|
assert result.stderr == f"workspace escape: {target.resolve(strict=False)}\n"
|
|
|
|
|
|
@requires_posix_shell
|
|
def test_resolve_workspace_path_helper_rejects_extra_root_symlink_to_root(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
helper_path = _install_resolve_helper(tmp_path)
|
|
workspace = tmp_path / "workspace"
|
|
root_alias = tmp_path / "root-alias"
|
|
workspace.mkdir()
|
|
root_alias.symlink_to(Path("/"), target_is_directory=True)
|
|
|
|
result = subprocess.run(
|
|
[
|
|
str(helper_path),
|
|
str(workspace),
|
|
"/etc/passwd",
|
|
"0",
|
|
str(root_alias),
|
|
"0",
|
|
],
|
|
check=False,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
|
|
assert result.returncode == 113
|
|
assert result.stdout == ""
|
|
assert result.stderr == (
|
|
f"extra path grant must not resolve to filesystem root: {root_alias}\n"
|
|
)
|
|
|
|
|
|
@requires_posix_shell
|
|
def test_resolve_workspace_path_helper_rejects_nested_read_only_extra_grant_on_write(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
helper_path = _install_resolve_helper(tmp_path)
|
|
workspace = tmp_path / "workspace"
|
|
extra_root = tmp_path / "tmp"
|
|
protected_root = extra_root / "protected"
|
|
workspace.mkdir()
|
|
protected_root.mkdir(parents=True)
|
|
target = protected_root / "result.txt"
|
|
target.write_text("scratch output", encoding="utf-8")
|
|
(workspace / "tmp-link").symlink_to(extra_root, target_is_directory=True)
|
|
|
|
result = subprocess.run(
|
|
[
|
|
str(helper_path),
|
|
str(workspace),
|
|
str(workspace / "tmp-link" / "protected" / "result.txt"),
|
|
"1",
|
|
str(extra_root),
|
|
"0",
|
|
str(protected_root),
|
|
"1",
|
|
],
|
|
check=False,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
|
|
assert result.returncode == 114
|
|
assert result.stdout == ""
|
|
assert result.stderr == (
|
|
f"read-only extra path grant: {protected_root}\n"
|
|
f"resolved path: {target.resolve(strict=False)}\n"
|
|
)
|
|
|
|
|
|
@requires_posix_shell
|
|
def test_resolve_workspace_path_helper_allows_nested_read_only_extra_grant_on_read(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
helper_path = _install_resolve_helper(tmp_path)
|
|
workspace = tmp_path / "workspace"
|
|
extra_root = tmp_path / "tmp"
|
|
protected_root = extra_root / "protected"
|
|
workspace.mkdir()
|
|
protected_root.mkdir(parents=True)
|
|
target = protected_root / "result.txt"
|
|
target.write_text("scratch output", encoding="utf-8")
|
|
(workspace / "tmp-link").symlink_to(extra_root, target_is_directory=True)
|
|
|
|
result = subprocess.run(
|
|
[
|
|
str(helper_path),
|
|
str(workspace),
|
|
str(workspace / "tmp-link" / "protected" / "result.txt"),
|
|
"0",
|
|
str(extra_root),
|
|
"0",
|
|
str(protected_root),
|
|
"1",
|
|
],
|
|
check=False,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
|
|
assert result.returncode == 0
|
|
assert result.stdout == f"{target.resolve(strict=False)}\n"
|
|
assert result.stderr == ""
|