Skip to content

Commit ec6a74e

Browse files
committed
fix(examples): deterministic sandbox env whitelist and reopen guard
1 parent 5797a7d commit ec6a74e

1 file changed

Lines changed: 19 additions & 5 deletions

File tree

  • examples/skills_code_review_agent/review

examples/skills_code_review_agent/review/sandbox.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
import sys
1010
from dataclasses import dataclass
1111

12-
from trpc_agent_sdk.code_executors import (WorkspacePutFileInfo, WorkspaceRunProgramSpec,
12+
from trpc_agent_sdk.code_executors import (BaseWorkspaceRuntime, WorkspacePutFileInfo,
13+
WorkspaceRunProgramSpec,
1314
WorkspaceStageOptions,
1415
create_container_workspace_runtime,
1516
create_local_workspace_runtime)
1617

1718
SKILL_WS_DIR = "skills/code-review"
1819
DIFF_WS_PATH = "work/inputs/changes.diff"
19-
_PYTHON3_DIR = os.path.dirname(shutil.which("python3") or sys.executable)
20-
SANDBOX_ENV = {"PATH": f"{_PYTHON3_DIR}:/usr/local/bin:/usr/bin:/bin",
20+
SANDBOX_ENV = {"PATH": "/usr/local/bin:/usr/bin:/bin",
2121
"HOME": "/tmp", "LANG": "C.UTF-8"}
2222

2323
RUNTIME_LOCAL = "local"
@@ -69,7 +69,7 @@ class SandboxRunOutcome:
6969
class SandboxSession:
7070
"""One workspace: staged code-review skill + staged diff + script runs."""
7171

72-
def __init__(self, runtime, skill_root: str, timeout_sec: float = 60.0,
72+
def __init__(self, runtime: BaseWorkspaceRuntime, skill_root: str, timeout_sec: float = 60.0,
7373
max_output_bytes: int = 262144):
7474
self._runtime = runtime
7575
self._skill_root = skill_root
@@ -79,6 +79,8 @@ def __init__(self, runtime, skill_root: str, timeout_sec: float = 60.0,
7979
self._exec_id = ""
8080

8181
async def open(self, exec_id: str) -> None:
82+
if self._ws is not None:
83+
raise RuntimeError("SandboxSession is already open; call close() first")
8284
self._exec_id = exec_id
8385
manager = self._runtime.manager(None)
8486
self._ws = await manager.create_workspace(exec_id, None)
@@ -97,10 +99,21 @@ def _truncate(self, text: str):
9799
return text.encode("utf-8", errors="replace")[:self._max_output].decode(
98100
"utf-8", errors="replace"), True
99101

102+
def _effective_env(self) -> dict:
103+
"""Whitelist stays PATH/HOME/LANG only; host python3 dir is appended as a
104+
dev fallback for hosts without /usr/bin/python3 (e.g. Nix); harmless
105+
nonexistent entry inside containers."""
106+
env = dict(SANDBOX_ENV)
107+
python3_dir = os.path.dirname(shutil.which("python3") or sys.executable)
108+
if python3_dir and python3_dir not in env["PATH"].split(":"):
109+
env["PATH"] = f"{env['PATH']}:{python3_dir}"
110+
return env
111+
100112
async def run_script(self, script_name: str,
101113
args: tuple = (DIFF_WS_PATH,)) -> SandboxRunOutcome:
102114
"""Run one skill script under `env -i` (environment whitelist)."""
103-
env_args = [f"{k}={v}" for k, v in SANDBOX_ENV.items()]
115+
env = self._effective_env()
116+
env_args = [f"{k}={v}" for k, v in env.items()]
104117
argv = ["-i", *env_args, "python3",
105118
f"{SKILL_WS_DIR}/scripts/{script_name}", *args]
106119
spec = WorkspaceRunProgramSpec(cmd="env", args=argv, env={}, cwd=".",
@@ -134,3 +147,4 @@ async def close(self) -> None:
134147
await self._runtime.manager(None).cleanup(self._exec_id, None)
135148
except Exception: # noqa: BLE001 - best-effort cleanup
136149
pass
150+
self._ws = None

0 commit comments

Comments
 (0)