Metadata-Version: 2.5
Name: glovebox-driver
Version: 0.7.1
Summary: Host-side Python driver for a glovebox microVM session
Project-URL: Homepage, https://github.com/AlexanderMattTurner/agent-glovebox
Project-URL: Source, https://github.com/AlexanderMattTurner/agent-glovebox
Author: AlexanderMattTurner
License-Expression: Apache-2.0
Keywords: ai-control,glovebox,microvm,sandbox
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Security
Requires-Python: >=3.11
Requires-Dist: glovebox-hosts>=0.5.0
Requires-Dist: pydantic>=2
Description-Content-Type: text/markdown

# glovebox-driver

The host-side Python that drives one [glovebox](https://github.com/AlexanderMattTurner/agent-glovebox) microVM: boot it, run commands in it, read what it let out, and tear it down. It imports no eval framework, so an integration picks the framework and this package holds the VM.

`inspect-glovebox` and `exploitbench-glovebox` are both built on it.

## What the host needs

The driver runs the `glovebox` command, which boots a Kata Containers microVM. Install glovebox, then ask whether this host qualifies:

```bash
glovebox sandbox preflight
```

Set `GLOVEBOX_BIN` when the executable is not on `PATH`.

## The modules

| Module          | Owns                                                                        |
| --------------- | --------------------------------------------------------------------------- |
| `run`           | `DisposableRun` — one whole run, from the workspace it mints to its record  |
| `cli`           | resolving the `glovebox` executable and running one `sandbox` verb          |
| `config`        | `GloveboxSandboxConfig` — what a VM may be and which hosts it may reach     |
| `session`       | `GloveboxSession` — one live microVM, from boot to teardown                 |
| `wedge`         | booting again when the HOST, not the work, wedged the first attempt         |
| `guest_exec`    | the argv for one command inside the guest, as the de-privileged user        |
| `evidence`      | reading the policy decision log the sandbox wrote                           |
| `task_image`    | a compose `healthcheck:` normalized into an argv tuple                      |
| `guest_account` | giving the de-privileged user the task's home and working directory at boot |
| `image_ref`     | splitting an OCI image reference the way docker's grammar reads it          |

## Run one

```python
from glovebox_driver import cli
from glovebox_driver.config import GloveboxSandboxConfig
from glovebox_driver.run import EvidenceRequest, disposable_run

cli.preflight()
config = GloveboxSandboxConfig(workspace="/path/to/workspace", boot_timeout=300)
records = EvidenceRequest(
    traffic="/path/to/traffic.json", host_filter="/path/to/host-filter.json"
)

with disposable_run(config, evidence=records) as run:
    print(run.exec(["ls", "-la"]).stdout.decode("utf-8"))
```

`disposable_run` owns the whole lifetime. It removes the scratch directories an earlier run left behind, stages a private copy of the workspace, boots one microVM onto it, and on the way out writes the records you asked for, tears the VM down, and removes what it staged. A run this raises past has already undone every step it took, so a caller that never receives one owes no cleanup.

Leaving `config` out mints an empty workspace under glovebox's shipped allowlist, which is what a transport probe wants.

`run.workspace` is the host directory the boot read, so stage files into it before the boot. After the boot it is a stale copy, because the boot copies it into a disk image the microVM mounts elsewhere. Reach the running workspace with `run.exec(...)` at `run.guest_workspace()`.

### Records and how a lost one is reported

Each field of `EvidenceRequest` names a file, and a field left out promises nothing. A record that could not be written is never silent: the close prints what was lost and raises `EvidenceExportFailed`, which carries the `RunRecord` naming the ones that did land. The records live on the host under `run.run_dir`, which teardown leaves standing, so a lost record's source can still be read there. A run that reported success without one would read "no red flags" when the truth is "no evidence".

### Booting from a worker thread

A caller that awaits the boot from a coroutine uses `aio.start_run`, or `aio.stage_run` and `aio.boot`, and ends the run with `aio.close`. A cancelled await returns while the boot thread runs on, so each of these keeps that thread's result and ends the microVM and the staged workspace it produced. A caller with a boot function of its own passes it to `aio.boot_with_handoff`, which gives that function a `Handoff` for `GloveboxSession.boot` and ends whatever a cancel leaves behind. Never drive a `Handoff` by hand: a published session does not name the workspace staged for it, so a caller that only disowns or claims it leaves that workspace behind.

`stage_run` is the two-phase form, for a caller that writes into the workspace before the boot. It hands back a `StagedRun` carrying `workspace`. `staged.boot()` consumes that directory, and `staged.discard()` removes it when the caller gives the boot up.

`GloveboxSession.boot` remains the layer under all of this, for a caller that owns its own workspace and teardown. It waits for a ready marker it never creates.

Apache-2.0.
