Decisions

Why the sandbox works the way it does. Why its spec is what it is: Specs Decisions. Why its wall and its proxy work as they do is on their own pages: the wall's and the proxy's.

The plugin and what it closes

Why there is one sandbox, a plugin of its own that every harness asks for each command it runs.

Why is the sandbox a plugin of its own?

One sandbox serves everything: lnk-sandbox. Every harness runs in it with a spec of its own (its folders, programs and network), and so can any program or person. What one harness needs that another doesn't is its spec, never a second sandbox. It is a plugin the parts share, like lnk cloud, not a part of its own.

How does a harness use the sandbox?

It asks lnk sandbox wrap for each command it runs, the installer, configure and the harness itself, and runs the command it gets back rather than running through the sandbox plugin. The background service then doesn't depend on the plugin at run time, and the caller keeps what runs, where, and with which environment. The sandbox adds what no spec chooses: Link's settings closed.

How does the sandbox get installed?

With every harness: harness needs sandbox, and lnk plugin add follows needs through. A harness plugin installed before the sandbox was its own adds it on first use.

The wall, the way out and the bridges

Why the sandbox is three packages, and which plugin owns each bridge behind its proxy.

Why three packages: the wall, the proxy and the bridges?

The sandbox grew by adding each thing an agent needed to the one process every agent ran, until it knew models, held secrets and spoke tools' protocols.

Each a package

Now each kind of thing is a package that knows nothing of the one above it: link-sandbox-confine, the wall (files, programs, the network mode, a backend per OS); link-sandbox-proxy, the one way out (hosts, endpoints, [lan], the log, stop and pause, a table of routes and no verbs); and the bridges behind it, each in the plugin whose purpose it serves (models in lnk-model, secrets in lnk-secret, tools, open and asking you in lnk-sandbox's own).

Rebuilt whole

It was rebuilt whole, before Link's launch and its audits, so the sandbox that ships and is audited is this one, not the one it replaced.

Why does an agent with no sandbox still get a proxy?

Its tools, memory, the scheduler, the wire's log, lnk sandbox stop and counted usage don't depend on the wall, so they stay: the proxy runs on the loopback behind its URL secret, and the agent is pointed at it. Only the wall is gone, and the docs say the proxy is then advice the program may ignore. An exit stays refused with no sandbox, since nothing holds the program to it. (Not: "no sandbox" meaning nothing of Link's between the agent and the network.)

Why are [lan] and host mode two things?

[lan] names the machines on your network the proxy may reach, still through it and still logged. "No filtering proxy at all" is [network] mode = "host", its own explicit choice, shown as loudly as --no-sandbox. (Not: one switch meaning both.)

Who counts a model's tokens?

The models plugin's route already reads each answer and knows each provider's format, so it takes out each answer's token counts and hands them to lnk-measure, which owns their type, the count, where it's kept and lnk measure. Provider formats live in one plugin. (Not: measure parsing every provider's format from a tap of the traffic.)

Why are opening a page and asking you lnk-sandbox's?

They're the sandboxed program's one way to reach you, so they're bridges of the sandbox plugin's own, bridges/open and bridges/asks, behind the proxy like any plugin's. A connection answering a question is later one more place an answer comes from. (Not: a plugin of their own.)

Why are tools a bridge of lnk-sandbox's?

Tools that run outside call open and asking you already, and every agent's tools use the code: one more plugin would be one more to install and secure. So they're bridges/tools, and lnk sandbox tool add stays the command. (Not: a plugin of their own.)

Why is the spec's schema made from the Rust type?

Spec is the one definition: its JSON Schema (lnk sandbox spec --schema), the docs' table of keys (lnk sandbox spec --keys) and lnk sandbox permissions' sections are made from the type and its comments, and a docs check fails when the page's table drifts. (Not: a schema file with types generated from it.)

Why is developer_tools a key of [commands]?

Xcode's and the Command Line Tools' programs are programs a sandbox runs, so the choice to run them is [commands]', as [commands.macos] developer_tools = true, read only on macOS. (Not: a permission of its own beside the spec's sections.)

How do a proxy and its agent share their socket folder?

When the proxy runs as another user than its agent's, they share the folder by a group of their own, one per agent: the folder and its sockets are 0770 and 0660 with that group, set-group-ID so a socket made inside takes it, and the proxy accepts in.sock from the agent's user alone. (Not: the proxy's user owning the folder and granting the agent by ACL.)

Tools, questions and the browser

How a tool that runs outside is reached, shared and approved, and how Link asks you and opens a page.

How is a tool at a URL reached?

Through a stdio server of Link's own, lnk-sandbox sandbox mcp-remote, defined as the tool's command. Everything a tool gets, asking you for each call, its sessions, the log, is the stdio tool's, so a remote server gets it with no second path through the proxy. It runs outside, so a server's token, sent as a header, is in the tool's definition in Link's settings and never in a sandbox. A URL is https://, or http:// only on this machine, so a token never crosses a network in the clear.

Why can a tool run one server for every sandbox?

A tool's server runs once per session, so a dozen agents calling one tool run a dozen servers, each with its own memory and start. A tool with --shared runs one, under a hub that any proxy needing it starts and that stops once idle (shared_idle), so a team's memory over its folder runs once for the team, and nothing runs when no one calls it. The hub speaks the same MCP over stdio's lines as any server, and renumbers each session's requests, so a server written for one client serves many without knowing.

Opt-in

Asking you stays each proxy's, before a call reaches the hub. It's opt-in: a server that keeps a session's state between calls would share it.

How is a tool that runs outside named and approved?

lnk sandbox tool add <name> -- <command>, and every call asks you but those --allow names; --ask none asks for none. A sandbox reaches only the tools it names, as it does endpoints. A server's own word on a call, MCP's destructiveHint or readOnlyHint, is shown but never skips asking, since the server sets it itself.

A dialog on your screen, then the terminal with a code typed back, then a question waiting for lnk sandbox answer; two minutes without one is no. A tool's call with no dialog and no terminal here is refused at once, naming what allows it: nobody would see it wait. Nothing in a sandbox draws on your screen or writes Link's settings, so it can't fake a dialog or answer one; on a terminal it types but can't read the code. The queue makes a box askable at all. Silence is never yes.

How does a sandbox open a page in your browser?

Through its proxy: open makes $BROWSER in the sandbox Link's own program, which asks the proxy, outside, to open the page. There is no LaunchServices and no desktop session inside. open takes the place of open-apps, which let a program open anything on a Mac and so amounted to no sandbox. A page opens in your browser with your cookies and logins, so every one asks you and shows its whole URL, in plain ASCII so what you read is what opens. No host is allowed in advance, since a URL on any site can carry data out.

How does a login's callback reach the sandbox?

A tool's login often waits on localhost:<port> for the browser's answer. On macOS the sandbox listens on this machine's loopback, so the answer arrives as it is. On Linux the sandbox's loopback is its own: the proxy listens on the port the page's redirect_uri names, only for a page you allowed, and passes the first request in. Then it closes, or after five minutes, one port per page and never a range. A port already taken is refused rather than shared, since the answer carries the login's code.

Sessions over SSH

How a person gets into a sandbox on a box, and what their session keeps.

How does a person get into a sandbox over SSH?

Through OpenSSH, with a forced command: restrict,pty,command="lnk-sandbox sandbox enter --spec <file> --folder <dir>". A shell, a command, scp or sftp all run in the sandbox, and no forwarding gets around it. The sandbox plugin keeps only that primitive; who gets a key and which spec is theirs is the box plugin's (a box's users), since a person let in is a user of a machine, not a part of its sandbox.

Not your own computer

(Not: people SSHing into your own computer as you, each in a sandbox, confined by the sandbox alone, where on a box each person is a Linux user of their own, apart from the rest by the system too.)

Why does a session keep its terminal?

A shell needs job control and Ctrl-C, so a session's sandbox skips bubblewrap's --new-session: the terminal is their own SSH session, which nothing else reads after. lnk sandbox run keeps a new session, since the terminal it runs on is yours.

Why is a session's spec checked each time?

lnk sandbox enter reads its spec as a run would, refusals and all, at every session, so a folder that became a link since is refused. --check reads it the same way and runs nothing, which is how lnk box user add shows what a user's sandbox opens before adding them.

Launch and policy boundaries

What of the policy builds apart from what enforces it.

Policy types compile independently of their implementation

The authored policy, OS overrides, grant algebra, command policy, network predicates and system-read roots are contract/, which links no program, backend, proxy or plugin library, so credential holders check those roots without a confinement backend. Its TOML parser and path resolver are the shared packages/spec.