Sandbox Spec

A sandbox spec is a file saying what a program may write, read and reach, and nothing else is open. lnk sandbox run --spec and lnk box user add --sandbox read it.

Every option

Every key, as a file holds it. lnk sandbox spec --keys prints them as a table, and lnk sandbox spec --schema the spec's JSON Schema, for an editor to check a spec with: both are made from the one type Link reads a spec into.

# Yours: a name to tell specs apart, and a number to bump when you change one.
name = "app"
version = 1

# Your folders. A section needs write or read.
[files]
write = ["~/code/app"]              # read and written; relative paths are from this file's folder
read = ["~/Documents/reports"]      # read only; "~" alone reads your whole home
# Of write, the folders whose .git, .envrc, .vscode and .idea it writes too, for a sandbox that commits.
write_dot_files = ["~/code/app"]
hide = ["~/code/app/secrets"]       # closed whatever else is open

# How it reaches the network: through Link's proxy, as the sections below say, the default.
# [network]
# mode = "host"                     # instead: this machine's network as it is, no proxy, nothing filtered

# Public hosts, through Link's proxy. A section needs hosts.
[internet]
hosts = ["api.openai.com", "github.com", "*.githubusercontent.com"]
# hosts = ["*"]                     # instead: any public host
tcp = ["github.com:22"]             # raw host:port beyond HTTPS and HTTP; hosts must allow each
per_minute = 600                    # the most calls a minute, at least 1

# Other machines of your network, through Link's proxy. A section needs hosts.
[lan]
hosts = ["nas.local", "192.168.1.0/24"]   # names, addresses and ranges; "*" is the private ranges

# Servers on this machine. A section needs ports or models.
[localhost]
ports = [8080]                      # ports it may call
models = [11434]                    # a model runtime's port, for inference only

# Your tools from `lnk sandbox tool add`, which run outside with your logins. A section needs names.
[tools]
names = ["gh"]

# Pages it may ask you to open in your browser, each time. A section needs hosts.
[browser]
hosts = ["github.com"]              # or ["*"] for any host

# The programs it may run. A section needs allow; one left out runs nothing.
[commands]
allow = ["sh", "node", "git", "/usr/lib/git-core/"]   # names, paths, folders ending in /, or ["*"]

# On macOS, these keys replace the section's. Every section has these tables, for macos and linux.
[commands.macos]
allow = ["sh", "node", "git", "/Library/Developer/CommandLineTools/usr/libexec/git-core/"]
developer_tools = true              # Xcode's and the Command Line Tools' programs, read only

# [files.linux]
# hide = ["~/.local/share/keyrings"]   # hide adds to the section's instead of replacing it

# [localhost.macos]
# ports = [5432]                       # only on macOS, these ports and no others

Sections

A section is a grant, and one left out stays closed: a spec with no sections reads none of your files, reaches no network and runs no program. A section needs its main key: [files] needs write or read, [internet], [lan] and [browser] need hosts, [localhost] ports or models, [tools] names and [commands] allow. An empty section, an unknown key and "*" beside other entries are refused, naming the line, and a spec file is at most 64 KiB.

SectionKeyWhat it takesFlag
topnameText: what it's called.--spec-name
topversionA whole number, yours to bump.--spec-version
browserhostsHosts a page may be on, or ["*"] for any.--browser-host
commandsallowPrograms, folders ending /, or ["*"] for any.--command
commandsdeveloper_toolstrue: Xcode's and the Command Line Tools' programs.--developer-tools
fileshideFolders closed whatever else opens them.--hide
filesreadFolders it reads; "~", your whole home.--read
fileswriteFolders it reads and writes.--write
fileswrite_dot_filesFolders of write whose outside dot files it writes too.--write-dot-files
internethostsHosts, or ["*"] for any public host.--internet-host
internetper_minuteA whole number, at least 1: the most calls a minute.--per-minute
internettcphost:port endpoints it reaches beyond HTTPS and HTTP.--tcp
lanhostsHosts, or ["*"] for the private ranges.--lan-host
localhostmodelsPorts of local model runtimes, for inference only.--model
localhostportsPort numbers, or ["*"] for every port.--port
networkmode"proxy", the default, or "host", with no proxy.--network-mode
toolsnamesTool names (lnk sandbox tool add).--tool

Each section's rules

What each section refuses, and how it reaches what it names.

[files] hide

The folders of write and read inside a hidden folder stay open. hide adds up across the default table, an OS's table and the flags, where every other key replaces the one before it.

[internet] tcp

Each endpoint names its port; *.abc.mongodb.net holds the names under it, not abc.mongodb.net, and hosts must allow each. A *. endpoint's port can't be a [localhost] port or 40781. On Linux, a spec with endpoints gets DNS from its proxy (how).

[lan] hosts

Which addresses each reaches, and what is refused whatever is listed: Security. On a cloud's machine, "*" and a range holding a whole private block are refused too, since the network there is the cloud's.

[localhost]

A port is 1 to 65535, and 40781, the port of Link's proxy, is refused in ports and in models. A model's port is reached through the proxy, for inference only, never as a raw port, and models alone needs no ports; with ports left out, only the models are reached.

[network] mode

"proxy", the default, has the program reach the network only through Link's filtering proxy, as the other sections say. "host" gives it this machine's network as it is, with no proxy and nothing filtered or logged. A spec with it is shown as reaching the network unfiltered before it runs.

Beside "host"

[internet] hosts can then only be ["*"], and [lan], [tools], [browser], tcp, per_minute and models are refused, having nothing to filter them. On macOS, [localhost] ports still names the ports it reaches; on Linux it shares this machine's loopback whole.

[tools] names and developer_tools

[tools] names are letters, digits, - and _. [commands] developer_tools, true, reads Xcode's and the Command Line Tools' programs, so git and compilers run; it means nothing but on macOS.

[commands] allow

A name is found on PATH when the run starts. A path is from the spec's folder, and one ending in / allows every program in that folder. lnk-sandbox and, on Linux, the program loader are always allowed. lnk sandbox learn lists the entries a command needs. An empty allow, or a section missing allow, is invalid.

A name found on PATH grants the executable's real path, following installed links such as Homebrew's python3. Link keeps that path for the launch, so changing the link after discovery cannot redirect the grant. Explicit paths still refuse links a non-root user owns.

What it refuses

An entry in a folder it may write, or in /tmp, is refused, and so is a folder holding one of those ("/", or ~/tools/ beside write = ["~/tools/out"]).

Landlock

On Linux a finite effective list, after OS overrides, needs Landlock. A spec without [commands] grants no programs and also needs Landlock. When the required capability is unavailable, the launch is refused with the observed reason. allow = ["*"] removes executable restrictions and needs no Landlock, while the sandbox still enforces its file and network policy. A command line without a spec keeps its own defaults.

Memory files

With an executable allowlist, Linux seals memory files non-executable when its kernel and listener support that. Otherwise it refuses memfd_create, so applications using it need a fallback.

Values

What a path, "*" and a host mean in every section.

Paths

~/ is your home folder; anything else relative is from the spec's own folder. .. is refused.

"*"

In [internet] hosts, any public host; in [lan] hosts, the private ranges; in [browser] hosts, any host; in [commands] allow, any program; in [localhost] ports, every port, only with [network] mode = "host". It's refused in [files] and [tools].

Hosts

A name with a dot (api.openai.com), *.github.com for the names under a name but not the name itself, or an IP address.

Dot files that run outside

At the top of each folder in write, a project's dot files that run outside the sandbox are read only (each, and how). write_dot_files lists the folders of write where they're written too, for a sandbox that commits (Let it commit). A folder in it that isn't in write is refused. Left out, none is.

One OS's table

[<section>.macos] and [<section>.linux] hold the section's keys for one OS. Each key given replaces the section's there, but hide, which adds up. A section with only another OS's table is closed here.

Flags and a file

A flag giving a key replaces the file's key, but --hide, which adds to it. Without --spec, the flags are the whole spec, and any program runs unless --command names them.

Refused in a file

A spec from a file may be anyone's, so these are refused in it, with the flags given beside it:

  • A folder in Link's settings or agents' folders, or a write of one holding them.
  • A write of your home folder itself, a dot folder in it (~/.ssh, ~/.config), ~/bin, ~/Library, a folder on your PATH or one holding it, or in the system's folders (The system's folders).
  • A write of the folder holding the spec, which would let the program change its next run.
  • A write in a project's .git, .envrc, .vscode or .idea: write the project's folder, and name it in write_dot_files.
  • A read or write in, or holding, a folder where programs keep their sockets: /tmp, /run, /var/run, /var/folders, $XDG_RUNTIME_DIR and the temp folder, so / too. What answers on a socket there, a session's bus or Docker's, runs what it's asked outside the sandbox, and on Linux a socket connects read only.
  • .. in a path.

The system's folders

/bin, /sbin, /usr, /etc, /lib, /lib32, /lib64, /boot, /dev, /proc, /sys, /opt, /nix, /snap, /var, /home/linuxbrew, and macOS's /System, /Library, /Applications, /private/etc and /private/var. Homebrew's, /opt/homebrew and /usr/local, are among them.

Flags, and a run's own folders

Flags alone are yours, or Link's own, and aren't checked this way. The folders a run is given to write beside its spec's (lnk sandbox run --with-fd's write) are: each must be a folder there, named in full with no . or .., and is refused as a write in a file is. A program [commands] lists in a given folder is refused too.

More about it

How to use a spec: Describe a sandbox in a file. Why it is what it is: Decisions. Every spec Link reads, side by side: Specs.

Checked reference

The field names below come from link_sandbox_client::Spec. Optional values remain unset until the feature resolves its environment or inherited settings.

root: browser, commands, files, internet, lan, localhost, name, network, tools, version
browser: hosts, linux, macos
browser.linux: hosts, linux, macos
browser.macos: hosts, linux, macos
commands: allow, developer_tools, linux, macos
commands.linux: allow, developer_tools, linux, macos
commands.macos: allow, developer_tools, linux, macos
files: hide, linux, macos, read, write, write_dot_files
files.linux: hide, linux, macos, read, write, write_dot_files
files.macos: hide, linux, macos, read, write, write_dot_files
internet: hosts, linux, macos, per_minute, tcp
internet.linux: hosts, linux, macos, per_minute, tcp
internet.macos: hosts, linux, macos, per_minute, tcp
lan: hosts, linux, macos
lan.linux: hosts, linux, macos
lan.macos: hosts, linux, macos
localhost: linux, macos, models, ports
localhost.linux: linux, macos, models, ports
localhost.macos: linux, macos, models, ports
network: linux, macos, mode
network.linux: linux, macos, mode
network.macos: linux, macos, mode
tools: linux, macos, names
tools.linux: linux, macos, names
tools.macos: linux, macos, names

Checked defaults

These values come from the shipped defaults, including nested settings. Omitted optional values have no stored default at this layer.

network.mode = "proxy"