Purpose
-------
Execution Sandbox (Layer 4, outer wrapper) for the ``oct-mcp``
six-layer pipeline. Provides environment sanitisation, working-directory
pinning, timeout enforcement, and output-size limiting for subprocess
execution.
Phase 5C adds pluggable OS-level sandbox *backends*:
* :class:`NativeBackend` — existing behavior (subprocess + env
sanitization). Always available; default on Windows.
* :class:`BubblewrapBackend` — Linux only. Wraps every command in
``bwrap --unshare-net --die-with-parent``. Requires ``bwrap`` in PATH.
* :class:`SandboxExecBackend` — macOS only. Prepends
``sandbox-exec -p "(deny network*)(allow default)"``. Requires
``sandbox-exec`` in PATH.
Use :func:`make_sandbox_backend` to select the appropriate backend based
on :class:`~oct.mcp.config.McpConfig`.
Responsibilities
----------------
- Strip sensitive environment variables before forking (``PYTHONPATH``,
common secret-named vars).
- Pin the subprocess working directory to the validated project root.
- Enforce per-tool timeouts and kill the process on expiry.
- Cap combined stdout+stderr output to ``max_output_bytes``.
- Never use ``shell=True``.
- Return a :class:`SandboxResult` — never raise for subprocess errors.
Diagnostics
-----------
Domain: MCP
Levels:
L1 — errors: process launch failures, unexpected errors
L2 — lifecycle: process start, process end, backend selection
L3 — command details (argv)
L4 — deep trace: per-line output, env keys
Contracts
---------
- ``shell=False`` is enforced unconditionally in all backends — no
caller path can override it.
- :meth:`SandboxExecutor.run` never raises. Any ``OSError`` or
``subprocess.SubprocessError`` is caught, logged at level 1, and
returned as a ``SandboxResult`` with a non-zero exit code.
- The sanitised environment always preserves ``PATH``, ``HOME``,
``LANG``, ``TERM``, ``USER``, ``USERNAME``, ``LOGNAME``,
``VIRTUAL_ENV``, ``CONDA_DEFAULT_ENV``, and
``OCT_MCP_SESSION_ID`` / ``OCT_MCP_REQUEST_ID`` correlation IDs.
- :func:`make_sandbox_backend` never raises — it always returns a
usable backend (falls back to :class:`NativeBackend`).