Add bounded stdin to owner execution
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a06def-6490-7033-8448-2eab2d12ed44
This commit is contained in:
parent
fd9297810c
commit
b6655d8859
15 changed files with 74 additions and 17 deletions
|
|
@ -3,6 +3,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from typing import Annotated
|
||||
|
||||
import typer
|
||||
|
|
@ -157,6 +158,9 @@ def sandbox_exec(
|
|||
max_output_bytes: Annotated[
|
||||
int, typer.Option(help="Per-stream captured output limit")
|
||||
] = 262_144,
|
||||
stdin: Annotated[
|
||||
bool, typer.Option("--stdin", help="Read bounded command stdin from this process")
|
||||
] = False,
|
||||
) -> None:
|
||||
"""Run COMMAND inside a bwrap sandbox through its owning process."""
|
||||
command = list(ctx.args)
|
||||
|
|
@ -164,19 +168,20 @@ def sandbox_exec(
|
|||
command = command[1:]
|
||||
if not command:
|
||||
raise typer.BadParameter("COMMAND is required after --")
|
||||
request = SandboxExecRequest(
|
||||
command=command,
|
||||
consumer=Consumer(
|
||||
actor=ActorType(actor),
|
||||
project=project,
|
||||
session_id=session_id,
|
||||
run_id=run_id,
|
||||
),
|
||||
credential_route_refs=credential_route_ref or [],
|
||||
timeout_seconds=timeout,
|
||||
max_output_bytes=max_output_bytes,
|
||||
)
|
||||
try:
|
||||
request = SandboxExecRequest(
|
||||
command=command,
|
||||
consumer=Consumer(
|
||||
actor=ActorType(actor),
|
||||
project=project,
|
||||
session_id=session_id,
|
||||
run_id=run_id,
|
||||
),
|
||||
credential_route_refs=credential_route_ref or [],
|
||||
timeout_seconds=timeout,
|
||||
max_output_bytes=max_output_bytes,
|
||||
stdin_text=sys.stdin.read(1_000_001) if stdin else None,
|
||||
)
|
||||
result = SandboxManager().execute(sandbox_id, request)
|
||||
except (KeyError, PermissionError, RuntimeError, ValueError) as exc:
|
||||
typer.echo(f"Error: {exc}", err=True)
|
||||
|
|
|
|||
|
|
@ -231,6 +231,8 @@ class SandboxManager:
|
|||
command_size = sum(len(arg.encode("utf-8")) for arg in request.command)
|
||||
if command_size > 65_536:
|
||||
raise ValueError("command argument vector exceeds 65536 bytes")
|
||||
if request.stdin_text is not None and len(request.stdin_text.encode("utf-8")) > 1_000_000:
|
||||
raise ValueError("command stdin exceeds 1000000 bytes")
|
||||
invalid_refs = [
|
||||
ref
|
||||
for ref in request.credential_route_refs
|
||||
|
|
@ -304,6 +306,7 @@ class SandboxManager:
|
|||
execution_context=context,
|
||||
timeout_seconds=request.timeout_seconds,
|
||||
max_output_bytes=request.max_output_bytes,
|
||||
stdin_text=request.stdin_text,
|
||||
)
|
||||
except Exception as exc:
|
||||
status.state = SandboxState.READY
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ class SandboxExtension(ABC):
|
|||
execution_context: dict[str, str],
|
||||
timeout_seconds: int,
|
||||
max_output_bytes: int,
|
||||
stdin_text: str | None = None,
|
||||
) -> dict[str, object]:
|
||||
"""Run a bounded command through an owner-mediated sandbox boundary."""
|
||||
raise NotImplementedError(f"{type(self).__name__} does not support execution")
|
||||
|
|
|
|||
|
|
@ -184,6 +184,7 @@ class BwrapExtension(SandboxExtension):
|
|||
execution_context: dict[str, str],
|
||||
timeout_seconds: int,
|
||||
max_output_bytes: int,
|
||||
stdin_text: str | None = None,
|
||||
) -> dict[str, object]:
|
||||
"""Ask the broker already inside bwrap to run an argument-vector command."""
|
||||
pid = int(handle.get("pid", "0"))
|
||||
|
|
@ -197,6 +198,7 @@ class BwrapExtension(SandboxExtension):
|
|||
"execution_context": execution_context,
|
||||
"timeout_seconds": timeout_seconds,
|
||||
"max_output_bytes": max_output_bytes,
|
||||
"stdin_text": stdin_text,
|
||||
}
|
||||
response_limit = max_output_bytes * 2 + 65_536
|
||||
chunks: list[bytes] = []
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ import subprocess
|
|||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_MAX_REQUEST_BYTES = 1_048_576
|
||||
# A validated 1 MB stdin can expand sixfold when JSON escapes control bytes;
|
||||
# leave bounded headroom for the argv, route references, and execution context.
|
||||
_MAX_REQUEST_BYTES = 7_000_000
|
||||
|
||||
|
||||
def _bounded_output(value: bytes | None, limit: int) -> tuple[str, bool]:
|
||||
|
|
@ -27,6 +29,7 @@ def _run(payload: dict, workspace: Path) -> dict[str, object]:
|
|||
max_output_bytes = int(payload["max_output_bytes"])
|
||||
credential_refs = payload.get("credential_route_refs", [])
|
||||
context = payload.get("execution_context", {})
|
||||
stdin_text = payload.get("stdin_text")
|
||||
child_env = {
|
||||
"HOME": str(workspace),
|
||||
"LANG": "C.UTF-8",
|
||||
|
|
@ -39,12 +42,16 @@ def _run(payload: dict, workspace: Path) -> dict[str, object]:
|
|||
command,
|
||||
cwd=workspace,
|
||||
env=child_env,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
start_new_session=True,
|
||||
)
|
||||
try:
|
||||
stdout_raw, stderr_raw = process.communicate(timeout=timeout_seconds)
|
||||
stdin_bytes = stdin_text.encode("utf-8") if stdin_text is not None else None
|
||||
stdout_raw, stderr_raw = process.communicate(
|
||||
input=stdin_bytes, timeout=timeout_seconds
|
||||
)
|
||||
exit_code = process.returncode
|
||||
except subprocess.TimeoutExpired:
|
||||
timed_out = True
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ class ExtensionBackend(Protocol):
|
|||
execution_context: dict[str, str],
|
||||
timeout_seconds: int,
|
||||
max_output_bytes: int,
|
||||
stdin_text: str | None = None,
|
||||
) -> dict[str, object]: ...
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ class SandboxExecRequest(BaseModel):
|
|||
command: list[str] = Field(min_length=1, max_length=256)
|
||||
consumer: Consumer
|
||||
credential_route_refs: list[str] = Field(default_factory=list, max_length=32)
|
||||
stdin_text: str | None = Field(default=None, max_length=1_000_000)
|
||||
timeout_seconds: int = Field(default=900, ge=1, le=3600)
|
||||
max_output_bytes: int = Field(default=262_144, ge=1, le=1_048_576)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue