Skip to main content
Outcome: an analysis bench where an untrusted tool processes an untrusted input and returns only a structured verdict. Level: intermediate · Time: ~15 minutes · Pattern: multi-tenant platform.

When to use this

Two things you did not write meet in one place: an untrusted tool and an untrusted input. Malware analysis, a file-format parser fed a hostile sample, an unaudited third-party scanner, a linter someone attached to a support ticket. The VM boundary already means a crash or a rm -rf cannot reach the host. What it does not decide on its own is whether that tool can phone home. A parser that reads your sample and posts it somewhere has not escaped anything — it simply used the network it was given. This guide stacks the three controls that close that gap:

Architecture

The sample goes in explicitly, the verdict comes out explicitly, and there is no third path — no network, no host filesystem, no shared kernel.

Prerequisites

  • BoxLite installed and a working virtualization host — see Installation.
  • An image containing whatever tool you intend to run. This guide uses alpine:latest with file.

Build it

When the tool genuinely needs the network — to refresh a rule database, say — swap the total block for a narrow allowlist:
Every other host then resolves to 0.0.0.0: the lookup succeeds and leads nowhere. Parameter tables are on Network access and Secrets and hardening.

Run it

Note the ownership in the listing: the tool ran as uid 1000, not root. The curl line inside the sample is inert — with mode="disabled" there is no interface to use.

Trust and limits

  • What the boundary covers. The tool runs on its own kernel and filesystem with its own resource budget, as a non-root user, with no network. Its only outputs are the exit code and the streams you read.
  • Isolation and containment are different properties. The microVM stops damage; the network policy stops exfiltration. A default box has full outbound access — that is convenient for most use cases here and wrong for this one. Set the network explicitly here.
  • user= labels the process, it is not a full identity boundary. It stops casual writes outside the work area. It is one layer among three, not a substitute for the network policy or the VM.
  • security= is not a top-level keyword. It must be wrapped: advanced=AdvancedBoxOptions(security=...). Passing it directly raises TypeError. The presets are development(), standard(), and maximum(); there is no minimum().
  • Blocked is not the same as failed. With egress narrowed, a request to a blocked host resolves to 0.0.0.0 and then fails to connect — you will see a connection error, not a DNS error. Do not read that as “the tool is broken”.
  • exec returns failure as data. A scanner exiting non-zero is a normal result here. Read exit_code; nothing is raised.

Troubleshooting

Next steps

  • Apply the same three controls elsewhere. Any use case here that runs model-generated code can adopt them — start with Build a code interpreter.
  • Harden a CI pipeline. Review untrusted pull requests needs github.com and a package mirror allowed, and nothing else.
  • Keep credentials out of the box. When a tool must authenticate, inject the value at the proxy instead of inside the sandbox — Secrets and hardening.