Use it

Files in and out

Give a program copies of your files, and get back what it writes. No folder on your Mac is ever shared with the box. Files travel as bounded copies, and everything that comes back is checked before it touches your disk.

In one command#

Terminal
$ pybox exec report.py --input sales.csv --output results/
report.py
import csv, json
rows = csv.DictReader(open("/tmp/input/sales.csv"))
totals = {}
for r in rows:
    totals[r["region"]] = totals.get(r["region"], 0) + int(r["sales"])
json.dump(totals, open("/tmp/output/summary.json", "w"))

Afterwards results/summary.json exists on your Mac, and the receipt lists every file that went in and came out:

Recorded 21 October 2026 (trimmed)
From the program  untrusted: a program can print or write anything
  Printed               79 bytes on stdout, 0 bytes on stderr
  Files in              1 file(s), 41 bytes
  Files out             2 file(s), 47 bytes → results/
  Not brought back      escape

That program also tried to leave a symbolic link to /etc/passwd in its output folder. Links are never brought back, so it's listed under Not brought back.

The rules#

Going in (--input)Coming back (--output)
Where, in the box/tmp/input/. A file keeps its name; a folder keeps its folder name, with its contents insideWhatever the program writes under /tmp/output/
How manyUp to 64 files and 8 MiB in total, each way
NamesLetters, digits, ., _ and -, up to 4 folders deep, no hidden files. Names that differ only in upper and lower case count as the same, because Mac folders usually do. Anything else is refused
Links and special filesRefused: PyBox never follows a link on your MacSkipped and listed by name, with any unusual characters shown as \xNN. Empty folders are dropped
On your MacYour originals are only read. The program gets copies, made read-only in the box--output must be a new or empty folder. Files are written all at once or not at all, never following a link, never overwriting, and marked like downloads (com.apple.quarantine) so macOS checks them when opened

Limits are checked on your Mac before any box is built, so an oversized input fails immediately with no run.

How the files travel#

The box still has no mounts, so files travel over the same channel the program uses. For runs that move files (and other opt-in runs), the box's first process starts a small fixed helper instead of plain python -. The exact command is part of the settings PyBox checks before starting. The helper:

  1. Reads the program and the input files from standard input, and writes the files to /tmp/input/.
  2. Runs the program as python - would: as __main__, with the same exit codes and tracebacks.
  3. When the program's main code returns, packs /tmp/output/ (regular files only, within the limits) and appends it to standard output after a marker unique to this run. Files written later by background threads or exit handlers aren't included. The marker keeps ordinary output from being mistaken for the transfer; it isn't a secret from the program.

On your Mac, PyBox splits the program's output from the files at that marker, decodes them strictly, and checks every name and size again. It writes them into a fresh hidden folder next to --output and renames that folder into place only when every file is written, so you never get half a result. For runs that move files, the box's scratch space grows from 16 MiB to 64 MiB.

What comes back is the program's work, so treat it as untrusted. The helper runs as the program's own user, so a program can read its state, change its copies of the inputs, or fake its own outputs. That can't reach your Mac's files, but it means returned files are only as trustworthy as the program. If the program never finishes (it's stopped at the deadline, or exits with os._exit), nothing comes back, and the receipt says no transfer received.

From Python and MCP#

Python
from pybox import run_container

result = run_container(open("report.py").read(), inputs=[("sales.csv", open("sales.csv", "rb").read())],
                       collect_outputs=True)
for name, data in result.output_files:   # (name, bytes), untrusted
    print(name, len(data))

Over MCP, an agent passes files and return_files. The limits are smaller there so results fit the protocol: 256 KiB of input files, and 48 KiB of returned file contents (larger files are listed with their size and SHA-256). See MCP for AI agents.

Checked against the source on 21 October 2026.

esc