sandbox: the network grant, with the VM's tasks (NETWORK slice 10) #7
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
A host can grant a sandboxed receiver script the network:
Sandbox.load<T>(path) { modules "std/network"; network connect: "api.example.test:443", listen: "127.0.0.1:8080" }. Inside a sandbox the runtime then lets a script resolve, connect and listen only as the grant names, and stops the script with aSandboxErrorfor anything else. VM tasks run inside a sandbox now (see below), so the check can be tested by a script that tries, which is the reason NETWORK.md put the grant off until then.Source
Pinned at
a022d2a8af1d27cb5a07f2a79be68a2ee5f7d12b(main):tests/conformance/vm-only/sandbox-tasks.trb. This is the "VM's tasks" the slice waited for, and it is built.runtime/sandbox.c,std/sandboxor the network natives' entry points. #119 (Unix domain sockets) and #129 (OS network addresses) are open: whoever lands second guards the new entry point the same way (see the last criterion).What to build
The grant is text, one setting per line (
runtime/sandbox.c,SandboxCapabilities.grantTextinstd/sandbox/src/lib.trb), and the native script host passes the same text across, so no new plumbing:std/sandbox:var fn network(connect: String = "", listen: String = "")onSandboxCapabilities, mirroringfiles; each call appends patterns, written asconnect\t<pattern>andlisten\t<pattern>lines. Several patterns per side are allowed by callingnetworkagain.runtime/sandbox.c: parse the two words, keep the patterns, and add the checks the network natives consult while a sandbox is open (and not set aside for a program's task, VM.md section 10): resolve by host pattern, connect by address, listen/bind by address.torb_network_*entry points ofruntime/io.ccall the checks; a refusal isTORB_SANDBOX_REFUSEDlike a refused path, i.e. a stop, never aNetworkErrorthe script could catch.project.trb(compiler/src/vm/sandbox.trb) is unchanged: no network.Acceptance criteria
Semantics, per NETWORK.md section 10 (the implementer writes the exact grammar below into that section, so the record stays the specification):
host:port; the port is a decimal number or*; the host is an exact name (compared case-insensitively, a trailing dot ignored),*alone, a*.-prefixed suffix (*.example.testmatchesa.example.testanda.b.example.test, notexample.test), or a literal IPv4/IPv6 address (IPv6 as[::1]:8080). A malformed pattern is aSandboxErrorofload(line 0), not a silent no-match.resolveof a host that noconnectpattern names stops the script. The addresses a granted host resolved to become connectable for the ports its pattern allows, for the rest of that sandbox opening.connectto a literal address needs aconnectpattern for that address and port, or a prior resolution from a granted host; so a script grantedapi.example.test:443cannot reach10.0.0.1orapi.example.test:22.listenon an address and port that nolistenpattern names stops the script. UDP follows the same two grants (this is not in section 10 and the implementer adds it there):UdpSocketbind is alisten,send(to:)andconnectare aconnect. Operations on a socket already granted (accept, receive, send, close,address) are not checked again.std/network(lookup,Resolver) is notresolve: it talks UDP/TCP to name servers, so under the rules above it is held byconnectpatterns for the name server's address and port, andsystemNameServers(the host's resolver configuration,torb_network_name_servers) stops the script in a sandbox. Both fail closed and are written into section 10; a host that wants a script to look names up grantsresolvethrough a host pattern.modules "std/network"and nonetwork, every resolve, connect and listen stops the script; withoutmodules "std/network"(orstd/http/std/tls) the script does not load, with the existing message naming the module (SCRIPTS.md section 6). TLS and HTTP add no capability: anhttpsrequest is a resolve and a connect and is held to the same patterns. Add the matching rows to the error table of SCRIPTS.md section 6 (The script may not connect to `10.0.0.1:80`: no `network connect` pattern names it, same shape for resolve and listen), with the line of the call.vm-only/sandbox-tasks.trb.Tests (gates
a, plusbas CLAUDE.md asks for runtime changes; the worker states which gates ran):runtime/tests/sandbox_test.c: pattern parsing and matching (exact,*,*.suffix, port*, IPv6 literal, malformed), and the connectable set after a resolution.tests/conformance/vm-only/that loads scripts over loopback (no outside network): grantedlistenplusconnecton127.0.0.1works and exchanges a message through tasks; an ungranted connect, listen and resolve each end the script with the expectedSandboxErrortext and line; the program goes on afterwards and the live-block counter is zero.--embed-vmbinary), for example by extendingtests/conformance/sandbox-crossing.trbor a sibling, so the grant crossing the boundary is exercised.docs/design/NETWORK.mdsection 10 and slice 10 ("Done"),docs/design/SCRIPTS.mdsection 4 (the table row no longer says "built with the VM's tasks") and section 6,docs/standard-library/sandbox.mdanddocs/language/configuration/the-sandbox.mdupdated;skills/regenerated (tools/check-commits.shand the docs gate clean).Seed
No. No syntax change, no new native (the existing
torb_network_*natives gain a check inside the runtime), no driver flag, no std name the compiler looks up by string (SandboxCapabilitiesgains a method; the compiler references the type through the generated script table, not the member). If the work turns out to need one of those, stop and file an owner decision instead of refreshing a seed.Size and plan
One PR, two Conventional Commits:
feat(runtime): hold the network natives to a sandbox's network grant(runtime/sandbox.c, the network entry points,runtime/tests/sandbox_test.c).feat(std/sandbox): grant the network to a script(SandboxCapabilities.network, conformance programs, docs and regeneratedskills/).Suggested implementer: opus, high effort (runtime and VM boundary, security-relevant lock). Dependencies: none open; the VM's tasks it waited for are in.
[loop:torbscript] intake: in scope (author orion, label loop). Implements NETWORK.md slice 10; design record is the specification, no collision with CONCEPT "Open Questions". Touches the sandbox and VM tasks (opus/high worker when implemented); seed impact to settle in refine. Release freeze for 0.1.0 (2026-10-06) holds new PRs, next: refine (queued, buffer >= 2 ready)
[loop:torbscript] claim refine by session:loop-torbscript-20261008 until 2026-10-08T00:16:12Z
[loop:torbscript] refine: rewrote the body to the definition of ready (goal, pinned sources in NETWORK.md section 10 / slice 10, SCRIPTS.md section 4 and 6, VM.md section 10, testable criteria incl. pattern grammar, UDP/DNS fail-closed, VM and native-host tests; no seed change; one PR as two commits, opus/high), label ready; the VM tasks it waited for are built, no collision with #5/#6/#155/#156, worker usage about 95k tokens, next: implement
[loop:torbscript] claim implement by session:loop-torbscript-20261008 until 2026-10-08T06:31:58Z
[loop:torbscript] implement: resumed after a gateway restart; the last push (
af21560c) already finished the plan (two commits, leak-holding refusals moved to sandbox-network-refused.trb with a .leaks file, every acceptance criterion addressed in the PR body); no further change needed, check-commits clean, gates a and b ran in forge CI only. PR #167, CI green (success on all jobs, tier A, tier B, all hosts), worker usage ~60k tokens, next: review