fix: address self-review findings for Vercel Sandbox salvage
- Add vercel_sandbox to hardline blocklist container bypass test - Add vercel_sandbox to skills_tool remote backend parametrize test - Deduplicate runtime set: doctor.py and setup.py now import _SUPPORTED_VERCEL_RUNTIMES from terminal_tool.py - Add docstring to _run_bash explaining timeout/stdin_data discards - Always stop sandbox during cleanup (unconditional, matching Modal/Daytona) - Update security.md: container bypass text, production tip, comparison table - Update environment-variables.md: TERMINAL_ENV list, Vercel auth vars, TERMINAL_VERCEL_RUNTIME - Update inline comments in cli.py and config.py to include vercel_sandbox
This commit is contained in:
parent
5a1d4f6804
commit
13c238327e
2
cli.py
2
cli.py
@ -503,7 +503,7 @@ def load_cli_config() -> Dict[str, Any]:
|
||||
"ssh_user": "TERMINAL_SSH_USER",
|
||||
"ssh_port": "TERMINAL_SSH_PORT",
|
||||
"ssh_key": "TERMINAL_SSH_KEY",
|
||||
# Container resource config (docker, singularity, modal, daytona -- ignored for local/ssh)
|
||||
# Container resource config (docker, singularity, modal, daytona, vercel_sandbox -- ignored for local/ssh)
|
||||
"container_cpu": "TERMINAL_CONTAINER_CPU",
|
||||
"container_memory": "TERMINAL_CONTAINER_MEMORY",
|
||||
"container_disk": "TERMINAL_CONTAINER_DISK",
|
||||
|
||||
@ -500,7 +500,7 @@ DEFAULT_CONFIG = {
|
||||
"modal_image": "nikolaik/python-nodejs:python3.11-nodejs20",
|
||||
"daytona_image": "nikolaik/python-nodejs:python3.11-nodejs20",
|
||||
"vercel_runtime": "node24",
|
||||
# Container resource limits (docker, singularity, modal, daytona — ignored for local/ssh)
|
||||
# Container resource limits (docker, singularity, modal, daytona, vercel_sandbox — ignored for local/ssh)
|
||||
"container_cpu": 1,
|
||||
"container_memory": 5120, # MB (default 5GB)
|
||||
"container_disk": 51200, # MB (default 50GB)
|
||||
|
||||
@ -868,11 +868,13 @@ def run_doctor(args):
|
||||
# Vercel Sandbox (if using vercel_sandbox backend)
|
||||
if terminal_env == "vercel_sandbox":
|
||||
runtime = os.getenv("TERMINAL_VERCEL_RUNTIME", "node24").strip() or "node24"
|
||||
if runtime in {"node24", "node22", "python3.13"}:
|
||||
from tools.terminal_tool import _SUPPORTED_VERCEL_RUNTIMES
|
||||
if runtime in _SUPPORTED_VERCEL_RUNTIMES:
|
||||
check_ok("Vercel runtime", f"({runtime})")
|
||||
else:
|
||||
check_fail("Vercel runtime unsupported", f"({runtime}; use node24, node22, or python3.13)")
|
||||
issues.append("Set TERMINAL_VERCEL_RUNTIME to node24, node22, or python3.13")
|
||||
supported = ", ".join(_SUPPORTED_VERCEL_RUNTIMES)
|
||||
check_fail("Vercel runtime unsupported", f"({runtime}; use {supported})")
|
||||
issues.append(f"Set TERMINAL_VERCEL_RUNTIME to one of: {supported}")
|
||||
|
||||
disk = os.getenv("TERMINAL_CONTAINER_DISK", "51200").strip()
|
||||
if disk in ("", "0", "51200"):
|
||||
|
||||
@ -666,11 +666,14 @@ def _prompt_vercel_sandbox_settings(config: dict):
|
||||
print_info(" Filesystem persistence uses Vercel snapshots.")
|
||||
print_info(" Snapshots restore files only; live processes do not continue after sandbox recreation.")
|
||||
|
||||
from tools.terminal_tool import _SUPPORTED_VERCEL_RUNTIMES
|
||||
|
||||
current_runtime = terminal.get("vercel_runtime") or "node24"
|
||||
runtime = prompt(" Runtime (node24, node22, python3.13)", current_runtime).strip() or current_runtime
|
||||
if runtime not in {"node24", "node22", "python3.13"}:
|
||||
supported_label = ", ".join(_SUPPORTED_VERCEL_RUNTIMES)
|
||||
runtime = prompt(f" Runtime ({supported_label})", current_runtime).strip() or current_runtime
|
||||
if runtime not in _SUPPORTED_VERCEL_RUNTIMES:
|
||||
print_warning(f"Unsupported Vercel runtime '{runtime}', keeping {current_runtime}.")
|
||||
runtime = current_runtime if current_runtime in {"node24", "node22", "python3.13"} else "node24"
|
||||
runtime = current_runtime if current_runtime in _SUPPORTED_VERCEL_RUNTIMES else "node24"
|
||||
terminal["vercel_runtime"] = runtime
|
||||
save_env_value("TERMINAL_VERCEL_RUNTIME", runtime)
|
||||
|
||||
|
||||
@ -258,7 +258,7 @@ _SCHEMA_OVERRIDES: Dict[str, Dict[str, Any]] = {
|
||||
"terminal.vercel_runtime": {
|
||||
"type": "select",
|
||||
"description": "Vercel Sandbox runtime",
|
||||
"options": ["node24", "node22", "python3.13"],
|
||||
"options": ["node24", "node22", "python3.13"], # sync with _SUPPORTED_VERCEL_RUNTIMES in terminal_tool.py
|
||||
},
|
||||
"terminal.modal_mode": {
|
||||
"type": "select",
|
||||
|
||||
@ -241,7 +241,7 @@ def test_container_backends_still_bypass(clean_session):
|
||||
|
||||
Hardline only protects environments with real host impact (local, ssh).
|
||||
"""
|
||||
for env in ("docker", "singularity", "modal", "daytona"):
|
||||
for env in ("docker", "singularity", "modal", "daytona", "vercel_sandbox"):
|
||||
r1 = check_dangerous_command("rm -rf /", env)
|
||||
assert r1["approved"] is True, f"container {env} should still bypass"
|
||||
r2 = check_all_command_guards("rm -rf /", env)
|
||||
|
||||
@ -932,7 +932,7 @@ class TestSkillViewPrerequisites:
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"backend",
|
||||
["ssh", "daytona", "docker", "singularity", "modal"],
|
||||
["ssh", "daytona", "docker", "singularity", "modal", "vercel_sandbox"],
|
||||
)
|
||||
def test_remote_backend_becomes_available_after_local_secret_capture(
|
||||
self, tmp_path, monkeypatch, backend
|
||||
|
||||
@ -578,6 +578,17 @@ class VercelSandboxEnvironment(BaseEnvironment):
|
||||
timeout: int = 120,
|
||||
stdin_data: str | None = None,
|
||||
):
|
||||
"""Run a bash command in the Vercel sandbox.
|
||||
|
||||
``timeout`` is not forwarded to the Vercel SDK (which does not expose
|
||||
a per-exec timeout parameter); the base class ``_wait_for_process``
|
||||
enforces timeout by killing the sandbox via ``cancel_fn``.
|
||||
|
||||
``stdin_data`` is intentionally discarded here because
|
||||
``_stdin_mode = "heredoc"`` causes the base class ``execute()`` to
|
||||
embed any stdin payload into the command string before calling this
|
||||
method.
|
||||
"""
|
||||
del timeout
|
||||
del stdin_data
|
||||
|
||||
|
||||
@ -132,6 +132,10 @@ For native Anthropic auth, Hermes prefers Claude Code's own credential files whe
|
||||
| `TINKER_API_KEY` | RL training ([tinker-console.thinkingmachines.ai](https://tinker-console.thinkingmachines.ai/)) |
|
||||
| `WANDB_API_KEY` | RL training metrics ([wandb.ai](https://wandb.ai/)) |
|
||||
| `DAYTONA_API_KEY` | Daytona cloud sandboxes ([daytona.io](https://daytona.io/)) |
|
||||
| `VERCEL_TOKEN` | Vercel Sandbox access token ([vercel.com](https://vercel.com/)) |
|
||||
| `VERCEL_PROJECT_ID` | Vercel project ID (required with `VERCEL_TOKEN`) |
|
||||
| `VERCEL_TEAM_ID` | Vercel team ID (required with `VERCEL_TOKEN`) |
|
||||
| `VERCEL_OIDC_TOKEN` | Vercel short-lived OIDC token (development-only alternative) |
|
||||
|
||||
### Langfuse Observability
|
||||
|
||||
@ -164,7 +168,7 @@ These variables configure the [Tool Gateway](/docs/user-guide/features/tool-gate
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `TERMINAL_ENV` | Backend: `local`, `docker`, `ssh`, `singularity`, `modal`, `daytona` |
|
||||
| `TERMINAL_ENV` | Backend: `local`, `docker`, `ssh`, `singularity`, `modal`, `daytona`, `vercel_sandbox` |
|
||||
| `TERMINAL_DOCKER_IMAGE` | Docker image (default: `nikolaik/python-nodejs:python3.11-nodejs20`) |
|
||||
| `TERMINAL_DOCKER_FORWARD_ENV` | JSON array of env var names to explicitly forward into Docker terminal sessions. Note: skill-declared `required_environment_variables` are forwarded automatically — you only need this for vars not declared by any skill. |
|
||||
| `TERMINAL_DOCKER_VOLUMES` | Additional Docker volume mounts (comma-separated `host:container` pairs) |
|
||||
@ -172,6 +176,7 @@ These variables configure the [Tool Gateway](/docs/user-guide/features/tool-gate
|
||||
| `TERMINAL_SINGULARITY_IMAGE` | Singularity image or `.sif` path |
|
||||
| `TERMINAL_MODAL_IMAGE` | Modal container image |
|
||||
| `TERMINAL_DAYTONA_IMAGE` | Daytona sandbox image |
|
||||
| `TERMINAL_VERCEL_RUNTIME` | Vercel Sandbox runtime (`node24`, `node22`, `python3.13`) |
|
||||
| `TERMINAL_TIMEOUT` | Command timeout in seconds |
|
||||
| `TERMINAL_LIFETIME_SECONDS` | Max lifetime for terminal sessions in seconds |
|
||||
| `TERMINAL_CWD` | Working directory for all terminal sessions |
|
||||
|
||||
@ -115,7 +115,7 @@ The following patterns trigger approval prompts (defined in `tools/approval.py`)
|
||||
| `gateway run` with `&`/`disown`/`nohup`/`setsid` | Prevents starting gateway outside service manager |
|
||||
|
||||
:::info
|
||||
**Container bypass**: When running in `docker`, `singularity`, `modal`, or `daytona` backends, dangerous command checks are **skipped** because the container itself is the security boundary. Destructive commands inside a container can't harm the host.
|
||||
**Container bypass**: When running in `docker`, `singularity`, `modal`, `daytona`, or `vercel_sandbox` backends, dangerous command checks are **skipped** because the container itself is the security boundary. Destructive commands inside a container can't harm the host.
|
||||
:::
|
||||
|
||||
### Approval Flow (CLI)
|
||||
@ -311,7 +311,7 @@ terminal:
|
||||
- **Ephemeral mode** (`container_persistent: false`): Uses tmpfs for workspace — everything is lost on cleanup
|
||||
|
||||
:::tip
|
||||
For production gateway deployments, use `docker`, `modal`, or `daytona` backend to isolate agent commands from your host system. This eliminates the need for dangerous command approval entirely.
|
||||
For production gateway deployments, use `docker`, `modal`, `daytona`, or `vercel_sandbox` backend to isolate agent commands from your host system. This eliminates the need for dangerous command approval entirely.
|
||||
:::
|
||||
|
||||
:::warning
|
||||
@ -328,6 +328,7 @@ If you add names to `terminal.docker_forward_env`, those variables are intention
|
||||
| **singularity** | Container | ❌ Skipped | HPC environments |
|
||||
| **modal** | Cloud sandbox | ❌ Skipped | Scalable cloud isolation |
|
||||
| **daytona** | Cloud sandbox | ❌ Skipped | Persistent cloud workspaces |
|
||||
| **vercel_sandbox** | Cloud microVM | ❌ Skipped | Cloud execution with snapshot persistence |
|
||||
|
||||
## Environment Variable Passthrough {#environment-variable-passthrough}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user