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 othersSections
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.
| Section | Key | What it takes | Flag |
|---|---|---|---|
| top | name | Text: what it's called. | --spec-name |
| top | version | A whole number, yours to bump. | --spec-version |
browser | hosts | Hosts a page may be on, or ["*"] for any. | --browser-host |
commands | allow | Programs, folders ending /, or ["*"] for any. | --command |
commands | developer_tools | true: Xcode's and the Command Line Tools' programs. | --developer-tools |
files | hide | Folders closed whatever else opens them. | --hide |
files | read | Folders it reads; "~", your whole home. | --read |
files | write | Folders it reads and writes. | --write |
files | write_dot_files | Folders of write whose outside dot files it writes too. | --write-dot-files |
internet | hosts | Hosts, or ["*"] for any public host. | --internet-host |
internet | per_minute | A whole number, at least 1: the most calls a minute. | --per-minute |
internet | tcp | host:port endpoints it reaches beyond HTTPS and HTTP. | --tcp |
lan | hosts | Hosts, or ["*"] for the private ranges. | --lan-host |
localhost | models | Ports of local model runtimes, for inference only. | --model |
localhost | ports | Port numbers, or ["*"] for every port. | --port |
network | mode | "proxy", the default, or "host", with no proxy. | --network-mode |
tools | names | Tool 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.
Installed program links
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 yourPATHor 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,.vscodeor.idea: write the project's folder, and name it inwrite_dot_files. - A read or write in, or holding, a folder where programs keep their
sockets:
/tmp,/run,/var/run,/var/folders,$XDG_RUNTIME_DIRand 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, namesChecked defaults
These values come from the shipped defaults, including nested settings. Omitted optional values have no stored default at this layer.
network.mode = "proxy"