Add owner-mediated bwrap execution boundary

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a06def-6490-7033-8448-2eab2d12ed44
This commit is contained in:
tegwick 2026-09-04 22:12:19 +02:00
parent 877676d1f1
commit d79e3fe358
23 changed files with 1321 additions and 86 deletions

View file

@ -2,13 +2,18 @@
from __future__ import annotations
from fastapi import FastAPI, HTTPException
import hmac
import os
from fastapi import FastAPI, Header, HTTPException
from sandboxer.core.manager import SandboxManager
from sandboxer.models import (
ExpireActionResult,
ExtendTtlRequest,
SandboxCreateRequest,
SandboxExecRequest,
SandboxExecResult,
SandboxStatus,
SnapshotRecord,
SnapshotRestoreRequest,
@ -18,6 +23,16 @@ app = FastAPI(title="sand-boxer", version="0.0.0")
_manager = SandboxManager()
def _authorize_exec(authorization: str | None) -> None:
"""Require an explicit owner-service capability on the high-risk exec route."""
expected = os.environ.get("SANDBOXER_EXEC_TOKEN")
if not expected:
raise HTTPException(status_code=503, detail="owner execution API is not configured")
scheme, _, supplied = (authorization or "").partition(" ")
if scheme.lower() != "bearer" or not hmac.compare_digest(supplied, expected):
raise HTTPException(status_code=401, detail="invalid owner execution credential")
@app.post("/v1/sandboxes", response_model=SandboxStatus)
def create_sandbox(request: SandboxCreateRequest, host: str | None = None) -> SandboxStatus:
try:
@ -42,6 +57,25 @@ def get_sandbox_reachability(sandbox_id: str) -> dict:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@app.post("/v1/sandboxes/{sandbox_id}/exec", response_model=SandboxExecResult)
def execute_in_sandbox(
sandbox_id: str,
request: SandboxExecRequest,
authorization: str | None = Header(default=None),
) -> SandboxExecResult:
_authorize_exec(authorization)
try:
return _manager.execute(sandbox_id, request)
except KeyError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
except PermissionError as exc:
raise HTTPException(status_code=403, detail=str(exc)) from exc
except RuntimeError as exc:
raise HTTPException(status_code=409, detail=str(exc)) from exc
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@app.get("/v1/sandboxes", response_model=list[SandboxStatus])
def list_sandboxes() -> list[SandboxStatus]:
return _manager.list()
@ -117,4 +151,4 @@ def extend_sandbox_ttl(sandbox_id: str, request: ExtendTtlRequest) -> SandboxSta
@app.post("/v1/sandboxes/expire", response_model=list[ExpireActionResult])
def expire_sandboxes(apply: bool = False) -> list[ExpireActionResult]:
return _manager.expire(apply=apply)
return _manager.expire(apply=apply)