The sandbox
A script has no IO, no network, no clock, no environment and no foreign functions by default, and only the caller of Sandbox.load can grant more, in a block that names exactly what is granted.
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
Draft. It runs in the VM: a program torb run runs loads and applies a script, and so does the toolchain for a
project.trb; a native binary runs its scripts in the VM of the script host it links, and the value crosses into
that VM and back through the derived Encode and Decode of the receiver
(Receiver Scripts and the Sandbox).
A receiver script is untrusted code from outside the program. What it can touch is not a
setting inside the script or its own project.trb; it is a
capability the caller of Sandbox.load grants at the one place that is not the
script itself.
Example#
use Sandbox from "std/sandbox"
type ServerConfig {
var host: String = "localhost"
}
fn loadConfig(path: String) {
Sandbox.load<ServerConfig>(path) {
modules "std/text", "std/time", "std/network"
files readOnly: "./config"
environment "APP_*"
network connect: "api.example.test:443"
limits steps: 1_000_000, memory: 64.megabytes(), time: 2.seconds()
}
}64.megabytes() is a ByteSize and 2.seconds() a Duration, and neither needs an import: std/number owns Int64,
so its megabytes is in scope everywhere, and the prelude re-exports seconds from std/time.
Syntax#
Sandbox.load<Value>(path, capabilities: (var self: SandboxCapabilities) => Void = {}): Result<Script<Value>, SandboxError>
Sandbox.loadText<Value>(text, name, capabilities: (var self: SandboxCapabilities) => Void = {}): Result<Script<Value>, SandboxError>
SandboxCapabilities.modules(...names: String)
SandboxCapabilities.files(readOnly: String = "", readWrite: String = "")
SandboxCapabilities.environment(...patterns: String)
SandboxCapabilities.network(connect: String = "", listen: String = "")
SandboxCapabilities.limits(steps: Int = 1_000_000, memory: ByteSize = 64.megabytes(), time: Duration = 2.seconds(), workers: Int = 1)Rules#
Left at the defaults, a script has no IO, no network, no clock, no environment and no foreign functions. The file scope is the prelude, the receiver and what the script imports - and a receiver script's
uselines are what themodulescapability decides, not the script's own choice: an import the grant does not name fails the load.Every capability is granted at the call site of
Sandbox.load, never in the script or itsproject.trb. A script that could grant itself a capability would not be a sandbox; the trailing block onloadis a receiver closure overSandboxCapabilities, configured the same way any other builder is.modulesnames additional packages of the standard library the script mayuse. A script has no other way to reach one, because a receiver script's imports are checked against exactly this list.filesnames the file system roots a script may reach, one side at a time.readOnlyandreadWriteeach default to nothing; a side left out stays closed, so a script grantedreadOnlyalone cannot write anywhere.networknames the hosts and ports a script may reach, and the addresses it may listen on. Each pattern ishost:port: the host a name,*.and a name for every name below it,*,localhostor an address (IPv6 in brackets), the port a number or*. Aconnectpattern lets the script resolve the names it matches and connect to the addresses it matches or that a matched name resolved to; alistenpattern lets it listen on or bind an address. Anything else - an address the script writes itself included - stops the script, as does asking for the system's name servers, and a malformed pattern fails the load. TLS and HTTP add nothing: an HTTPS request is a resolve and a connect. The script needsmodules "std/network"as well.limitsguards against a script that never stops.steps,memoryandtimeeach have a default (1,000,000 steps, 64 megabytes, 2 seconds), so a script that does not ask for more still cannot run forever.memoryis aByteSize, in either family of units:64.megabytes()is 64,000,000 bytes,64.mebibytes()67,108,864.Script.applyreports a limit or a panic as a value, not as a crash of the host. The one panic in the language that can be recovered from is the one inside a script the host's own VM is interpreting - a sandbox whose failures aborted the caller would not be a sandbox.Foreign functions can never be granted to a script. There is no capability for them in
SandboxCapabilities, because aforeigndeclaration reaches outside the process the sandbox itself runs in.The VM enforces all of it, and only the VM runs a script. A program
torb runruns loads scripts under these capabilities and limits - from a literal path, from one it computes or from text it holds - and so does the toolchain for aproject.trb(torb manifest, see project.trb), and so does a native binary thattorb build --embed-vmbuilt, and so does every other native binary, in the VM of the script host it links. A relative root is read against the directory the program runs in.
What this is not#
A capability is not a permission the script requests. There is no line inside a .trb script that asks for a
file or an environment variable; every capability is decided once, by the code that calls Sandbox.load, before the
script is even read.
use Sandbox from "std/sandbox"
type ServerConfig {
var host: String = "localhost"
}
fn loadConfig(path: String) {
Sandbox.load<ServerConfig>(path) {
files readOnly: "./config"
}
}use Sandbox from "std/sandbox"
type ServerConfig {
var host: String = "localhost"
}
fn loadConfig(path: String) {
Sandbox.load<ServerConfig>(path) {
grant "files", "./config"
}
}
// error: Cannot find `grant` hereRelated#
- Receiver scripts - what is loaded and checked before a capability ever matters.
- Foreign functions - the one capability a sandbox can never grant.
- Packages - the same "visible from the imports alone" idea, for a whole dependency graph.