Choose What It Can Reach

The programs a sandboxed program runs, the hosts, machines and models it reaches, the tools it calls and the pages it opens, each a flag of lnk sandbox run and a key of a spec (Sandbox).

Choose the programs it may run

With a spec, nothing runs that [commands] allow doesn't list, and the program given to run must be on it, or the run is refused before it starts. Without a spec, any program runs unless --command names them.

# every program it runs, to add
lnk sandbox learn --spec app.toml -- npm test
lnk sandbox run --write . --command sh --command git -- sh -c 'git status'
# only what the spec allows in its commands
lnk sandbox run --spec app.toml -- npm test

A program refused

A program started from inside that isn't listed gets "Permission denied", and the sandbox says which:

lnk sandbox: refused /usr/lib/git-core/git-remote-https: add it to [commands] allow

learn

learn runs the command once under the spec's other grants with every program open, and prints the entries to add to [commands] allow. It writes nothing. A helper a program starts only now and then is found only if this run starts it.

An entry

An entry is a name found on PATH, a path, a folder ending in / for every program in it, or * for any. A script needs itself and what it starts with, so #!/usr/bin/env node needs env and node. An entry in a folder it may write, or in /tmp, is refused.

An interpreter

Listing an interpreter such as sh, node or python3 lets it run any code it's given. The list limits which programs run, not what they do.

What it needs

On Linux this needs Landlock, which is in Linux 5.13 and newer. On macOS, naming a refused program and learn need an admin user, because they read what Seatbelt reports in the system log.

The program gets the internet without the rest of your network. Its only way out is Link's filtering proxy, set as its HTTPS_PROXY, which refuses this machine, your network and a cloud's metadata service. Every agent with [internet] runs this way, unless it has [network] mode = "host".

lnk sandbox run --internet-host '*' -- python3 fetch.py
lnk sandbox run --internet-host api.anthropic.com --internet-host '*.telegram.org' -- ./bot
lnk sandbox run --internet-host github.com --tcp github.com:22 --write . -- git clone git@github.com:me/repo.git
# mongodb+srv://, Linux
lnk sandbox run --internet-host '*.abc.mongodb.net' --tcp '*.abc.mongodb.net:27017' -- node app.js
lnk sandbox run --internet-host '*' --upstream socks5://127.0.0.1:1080 -- curl -sI https://example.com

--internet-host and --tcp

--internet-host names a host it may reach, or * for any, and *.telegram.org covers the names under it but not telegram.org itself. --tcp <host:port> opens an endpoint beyond HTTPS on 443 and HTTP on 80, such as github.com:22 for git over SSH, and its host must be one it may reach.

--upstream and --per-minute

They change where its traffic leaves from and how many calls it may make. The upstream is direct, a SOCKS5 proxy (socks5://127.0.0.1:<port>, or socks5://<a socket's absolute path>), or an HTTP proxy on this machine's loopback (http://127.0.0.1:<port>).

An HTTP proxy upstream

It's for a machine whose traffic leaves only through a filtering proxy of its own network: that one is handed each host's name, which it resolves, so this machine looks up no name the sandbox asks for.

A program that ignores HTTPS_PROXY

It has no network. Python follows it, and Node gets NODE_USE_ENV_PROXY=1. Git over SSH works once its endpoint is named with --tcp, using the sandbox's own key in its ~/.ssh.

Names on Linux

On Linux the sandbox also gets DNS from its proxy, so a --tcp host with a port from 1024 is reached by its name, and psql "host=db.example.com" and mongodb+srv:// work as they are (its DNS).

Other endpoints, and a Mac

Other endpoints, and every one on a Mac, are reached only by a client that takes a ProxyCommand, like ssh. On a Mac, psql and database drivers can't reach a --tcp endpoint.

Reach your network

--lan-host lets it reach machines of your network on any port, through its proxy. It takes a name, an address, a range, or * for the private ranges, and your network is reached from here whatever --upstream says. Security says which addresses a host reaches, and what it never reaches whatever is listed.

lnk sandbox run --lan-host nas.local -- curl http://nas.local:5000/
lnk sandbox run --lan-host 192.168.1.0/24 --internet-host '*' -- ./sync

Let it use a model

--model <port> makes a local model runtime such as Ollama, LM Studio or llama.cpp reachable for inference only. That means chat, generation, embeddings and the models' list, and pulling, creating or deleting a model gets a 403. On Linux the program keeps the runtime's address. On macOS it reaches the runtime at $LNK_LOCAL_MODEL_<port>, on its proxy.

# Ollama, inference only
lnk sandbox run --model 11434 --write . -- python3 chat.py

A hosted model's key, or a secret

The proxy can also add a hosted model's key, or a service's secret such as a Telegram bot's token, so the program never holds it: each is a route of the proxy's, served outside the sandbox by the plugin it's for (lnk-model, lnk-secret), as Security explains.

For an agent

lnk agent secret wire sets this up for an agent, and every agent on Link Harness, or on a harness whose adapter says it takes the proxy's address, has it by default.

Calling it

The program calls http://127.0.0.1:<proxy port>/_link/model with a placeholder key, and adds /v1 for OpenAI and OpenRouter. The proxy port is 40781 on Linux. It works with Anthropic, OpenAI and OpenRouter. A service's secret is reached the same way, as Security explains.

Call a tool that runs outside

A tool is an MCP server that runs outside the sandbox, with your logins, desktop and SSH agent, or a server at a URL reached from outside with its headers, which no sandbox sees. Use one for what the sandbox keeps out, such as gh or aws with your credentials. A sandbox reaches only the tools it names, at $LNK_TOOL_<NAME>, so $LNK_TOOL_GH for gh.

lnk sandbox tool add gh --allow list_issues --allow get_issue -- github-mcp-server stdio
lnk sandbox tool add docs --url https://example.com/mcp --header-file docs.headers  # a file of its Authorization header
# one server for every sandbox naming it
lnk sandbox tool add notes --shared -- notes-mcp serve
lnk sandbox tool calls gh                  # its calls
lnk sandbox tool list                      # every tool, and which calls ask
lnk sandbox run --tool gh -- my-agent
lnk sandbox tool remove gh

Asked first

Every call asks you first, except those you --allow by name, and --ask none asks for none. Removing a tool ends its sessions.

--shared

A tool with --shared runs one server for every session, where any other starts one each, and stops once none has been open for the sandbox's shared_idle: a team's memory is one.

Open a page in your browser

With --browser-host, a program can open an https:// page in your browser, such as a login, on the hosts given (* for any). Every page asks you first and shows its whole URL, because it opens with your cookies and logins. $BROWSER in the sandbox does it, so Python's webbrowser, gh and xdg-open work as they are.

# asks you, then opens it
lnk sandbox run --browser-host github.com -- python3 -m webbrowser https://github.com/login

A login's answer

A login that answers on localhost (its redirect_uri) gets that port until the answer arrives, or for five minutes at most. With no desktop, such as on a box, nothing opens, and the program's own fallback, a URL to copy or a code to type, still works.