Guide: an isolated sandbox segment¶
Sandboxes that run untrusted code (an AI agent, a build from a stranger's repository) should reach exactly two things: each other, and a small set of gateways that decide what leaves. This guide sets that up with fields banlieue already has: no new kind, no controller, no Rust (ADR-0109).
The complete manifests are in
examples/25-isolated-sandbox-segment.yaml.
The shape¶
routable network (egress-nat)
──────────────┬─────────────────┬─────────────────┬────────────
│ egress (static, │ │
│ default route) │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ gateway-1 │ │ gateway-2 │ │ gateway-3 │ forwarding OFF:
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘ each connection is
│ sandbox │ │ terminated and
──────────────┴─────────────────┴─────────────────┴────── re-opened
sandbox segment: layer 2 only, no router,
no DHCP, no resolver
───────┬──────────┬──────────┬──────────┬──────────────────
┌──┴──┐ ┌──┴──┐ ┌──┴──┐ ┌──┴──┐
│ sbx │ │ sbx │ │ sbx │ │ sbx │ one NIC each, a static address
└─────┘ └─────┘ └─────┘ └─────┘ from the pool, no default route
A sandbox cannot route anywhere because nothing on its segment routes. The only way out is an application-layer connection to a gateway, which authorizes it and opens its own connection from its routable address. Sandbox addresses never appear on the routable network.
The pieces¶
1. The segment (host configuration)¶
banlieue names the segment; it does not create it (roadmap 19's VMNetwork
will). Create it once per host:
- libvirt: an isolated network, with no
<forward>(libvirt then rejects anything forwarded off its bridge) and no<dhcp>or<dns>. The definition is in the example's header. - vSphere: a port group on a VLAN with no SVI and no gateway, trunked
on every host a member can land on. List the zone in
perZoneonly after a two-host test passes: banlieue sees that a port group of that name exists, not that its VLAN is trunked (ADR-0109 Decision 3).
Then offer it as a network class on the Provider:
networkClasses:
- name: egress-nat # routable, for the gateways' egress only
target: { network: default }
- name: sandbox-segment
target: { network: sandbox-segment }
2. The gateways (two NICs, egress first)¶
A gateway is an ordinary VirtualMachine. Its class lists egress first,
static with a gateway (the default route), and sandbox second, on the
segment. Order matters: the first static interface is the one the
user-data placeholders and vSphere's guestinfo.network.* describe
(ADR-0109 Decision 6).
- The
egressaddress comes from theVirtualMachine'snetworkOverrides. - The
sandboxaddress is fixed in the gateway image, three gateways being three per-image constants. On vSphere banlieue can set it too (ADR-0111 publishes every static NIC). - The software (Envoy, its SPIFFE identity, the allowlist, IP forwarding
off, a listener on the
sandboxinterface only) is image content (ADR-0108, ADR-0109 Decision 5). mediatore builds one (its ADR-0028).
Run three per environment (Decision 4). A sandbox gets the ordered list of gateway addresses from its broker and fails over per connection.
3. The sandboxes (one NIC, no gateway)¶
A pool on the segment, each member drawing a static address with no
gateway, so it has no default route (Decision 7):
addressing:
interface: eth0
pool:
- 10.200.0.32-10.200.0.95 # keep clear of the gateways' sandbox addresses
prefix: 24
Addresses on Kairos guests¶
Kairos applies no cloud-init network-config from the seed, so a Kairos
gateway or sandbox writes its own networkd file from the placeholders
banlieue renders into its user-data. The example carries both stages, as
ConfigMaps referenced by userData.configMapRef. Two things found on the
first live run:
- Match the right NIC on a gateway.
Name=en*matches both interfaces; match the egress one by name (on banlieue's libvirt domains the first interface isens2). - Kairos ships its own
20-dhcp.network. Name your file10-…so it sorts first. Kairos's initramfs may still try DHCP once during early boot; on a segment without DHCP that request goes unanswered.
A cloud-init image needs neither stage: it gets its addresses from the
seed's network-config (ADR-0114).
Checking it is isolated¶
| Check | How | Expect |
|---|---|---|
| No route out of the segment | on the host: virsh net-dumpxml sandbox-segment |
no <forward>, no <dhcp>, no <dns> |
| Forwarded traffic is refused | the host's libvirt firewall rules for the bridge | a reject rule for traffic forwarded off it |
| Members have no default route | a member's networkOverrides (stamped by the pool) |
an address and prefix, no gateway |
| Gateways do not route | in the gateway image | net.ipv4.ip_forward = 0 |
| Each member has its address | the member's status.addresses |
the drawn address; addressSource: static until something observes it (declared, not proven) |
What has run live¶
On a libvirt host (2026-11-05, and end to end on 2026-11-08), mediatore ran this topology: an isolated libvirt network as the segment, three two-NIC gateways, and a pool drawing static addresses through the Kairos stage above.
- Proven: a member attested through a gateway's relay, loaded a host
table that drops everything but traffic to the gateways, and reached its
egress proxy through the gateways. A request outside the claim's rules
was refused (
403 outside_rules). libvirt's isolated network refused a connection forwarded off the segment. - Proven 2026-11-08: an allowed request completing through a gateway's Envoy. A request inside the claim's rules reached the upstream, which answered 200 with the broker-minted bearer present, while a request outside them was refused (403). Releasing the claim retracted its entry and the pool recycled the member. (The 2026-11-05 run had stopped at an expired upstream credential, an environment problem.)
What banlieue does not do¶
- Create, change or delete the segment (roadmap 19).
- Run, configure or know about the gateways' software.
- Inspect images to check that forwarding is off or that only the
sandboxinterface listens: that is the image author's test. - Offer any inbound path to a sandbox. There is no SSH and no jump host; the hypervisor console, behind RBAC, is the only way in (ADR-0082).