std/sandbox/lib

std/sandbox/src/lib.trb

Loads .trb files as sandboxed receiver closures - the mechanism behind project.trb and configuration scripts (see "Receiver Scripts and the Sandbox" in CONCEPT.md, and docs/design/SCRIPTS.md).

A script runs in the VM, always: the step and time limits and the panic that does not end the caller are properties of an interpreter (docs/design/SCRIPTS.md section 1). A program torb run runs in the VM loads one, from a path it was compiled with or from any other, and so does a native binary torb build --embed-vm built, which embeds the VM. A native binary torb build compiled loads one through the front end and the VM it embeds (slice 8): the script is checked against Value and run in that VM, and the value crosses as text in both directions, through the derived Encode and Decode of Value, every field included - so a receiver of such a program is Encode & Decode, which a type of plain settings is by derivation.

type SandboxError

type SandboxError with Show, Error

What went wrong loading a script: a syntax or type error against the receiver type, or a capability it does not have. line is 0 when the problem is not about one place in the script (e.g. the file could not be read).

field line

line: Int

0 when the problem is not about one place in the script, such as the file not being readable at all.

field message

message: String

What went wrong, without the line number.

fn show

fn show(): String

"{line}: {message}", or just message when there is no line.

type SandboxCapabilities

type SandboxCapabilities

What a script may do, granted at the call site of Sandbox.load - never in the script or its project file (a script that could grant itself capabilities would not be a sandbox). Left at the defaults, a script has no IO, no network, no clock, no environment and no foreign functions.

field moduleNames

protected var moduleNames: List<String> = []

The modules SandboxCapabilities.modules granted, which the script's imports are held against.

field readOnlyRoots

protected var readOnlyRoots: List<String> = []

The roots files granted for reading only, in the order they were granted.

field readWriteRoots

protected var readWriteRoots: List<String> = []

The roots files granted for reading and writing, in the order they were granted.

field variablePatterns

protected var variablePatterns: List<String> = []

The environment variable name patterns environment granted.

field connectPatterns

protected var connectPatterns: List<String> = []

The host:port patterns network granted to resolve and connect to, written as connect lines of the grant.

field listenPatterns

protected var listenPatterns: List<String> = []

The address:port patterns network granted to listen on and bind, written as listen lines of the grant.

field stepLimit

protected var stepLimit: Int = 1_000_000

The most steps the script may run before it is stopped; limits sets it.

field memoryLimit

protected var memoryLimit: Int = 64_000_000

In bytes, which is how the grant carries it.

field timeLimit

protected var timeLimit: Int = 2000

Milliseconds: a Duration is no constant a default could be.

field workerLimit

protected var workerLimit: Int = 1

The most workers the script's tasks may run on at once; limits sets it.

fn modules

var fn modules(...names: String)

Additional parts of the standard library the script may use, e.g. "std/text".

fn files

var fn files(readOnly: String = "", readWrite: String = "")

File system roots the script may reach. A side that is left out stays closed.

fn network

var fn network(connect: String = "", listen: String = "")

Network patterns the script may reach, each host:port (docs/design/NETWORK.md section 10): connect what it may resolve and connect to - "api.example.test:443", "*.example.test:*", "[::1]:8080" - and listen the address it may listen on or bind, "127.0.0.1:8080". The port is a number or *, the host a name, *. and a name for every name below it, * for every host, or an address. A side that is left out stays closed, and calling it again adds patterns. The script also needs modules "std/network".

fn environment

var fn environment(...patterns: String)

Environment variable name patterns the script may read, e.g. "APP_*".

fn limits

var fn limits(steps: Int = 1_000_000, memory: ByteSize = 64.megabytes(), time: Duration = 2.seconds(), workers: Int = 1)

Guards against runaway scripts (loop {}). workers is the most workers the script's tasks may run on at once (docs/design/CONCURRENCY.md section 3): a script that could fan out over the host's cores would be a denial of service its grant does not show. The VM runs the tasks of a script on the thread of its sandbox, so a script never uses more than one whatever the number says.

type Script

type Script<Value>

A loaded script, ready to run against a Value of the caller's own. It is not a closure, because the body of the script runs inside the sandbox and can still fail there: a step, memory or time limit, or a panic - the only panic in the language that is recoverable, because the VM is interpreting it and the script has a heap of its own.

fn apply

fn apply(var value: Value): Result<Void, SandboxError>

Runs the body of the script against value, configuring it in place.

type Sandbox

native type Sandbox

Sandboxes and type checks .trb files against a receiver type.

fn load

static fn load<Value>(path: String, capabilities: (var self: SandboxCapabilities) => Void = {}): Result<Script<Value>, SandboxError>

Type checks path against Value and returns it as a Script, ready to run against a Value of the caller's own. What is wrong with the file (syntax, types, a module the script may not import) is reported here; what goes wrong while it runs is reported by Script.apply instead. The trailing block grants capabilities beyond the defaults.

A path the program was compiled with - a literal of a Sandbox.load - names a script the program carries, which torb check checked with it. Any other path is read, checked against Value and lowered when the program asks for it, relative to the working directory (docs/design/SCRIPTS.md slice 7); Value is then a type without type arguments.

Examples

type ServerConfig {
  var port: Int = 8080
}

const loaded = Sandbox.load<ServerConfig>("./config.trb") {
  files readOnly: "./config"
  limits steps: 1_000_000, memory: 16.megabytes()
}
print loaded.isOk()

fn loadText

static fn loadText<Value>(text: String, name: String, capabilities: (var self: SandboxCapabilities) => Void = {}): Result<Script<Value>, SandboxError>

Sandbox.load for a script the program holds as text, which it fetched through a Storage or built itself, and not as a file: text is type checked against Value and returned as a Script, with the same checks, grants and limits as load and the same errors. name is what the script is called in a SandboxError, and SandboxError.line counts the lines of text. It is no path and is read against nothing; the same text loaded twice is the same script, whatever it is called. As for a path only known while the program runs, Value is a type without type arguments, and the program writes nothing to a file to load the script.

std/sandbox knows no Storage: the program fetches the text and hands it over, so what the script may reach is what the caller grants and not what the program's storage reaches.

Examples

type ServerConfig {
  var port: Int = 8080
}

// the text came from a `Storage.readText`, a request or a database
const text = "port = 9100"
var config = ServerConfig()
if const Ok(script) = Sandbox.loadText<ServerConfig>(text, "server.trb") {
  const _ = script.apply config
}
print config.port