Security
What's protected: what runs as lnk and Link's plugins is what Link
released, a plugin from outside Link is what its maker released under
the key you trusted, and relay servers run only signed builds.
Which program runs
lnk <group> runs lnk-<plugin> from lnk's own folder first, then
from the PATH, the way git does. So whoever can write to a folder on
the user's PATH can already replace what lnk tunnel open runs. Empty
and relative PATH entries, such as the current folder, are skipped:
lnk drops them from the PATH it runs with and hands its plugins, so
neither runs a tar or systemctl from the folder it was started in.
What installs plugins
Every way a plugin gets installed checks a signed release first.
lnk up,lnk plugin add, a missing plugin's install offer andlnk upgradeinstall Link's plugins only from a release whoseSHA256SUMSis signed by the keys built intolnk, and check each archive against it.lnk plugin addtakes the release oflnk's own version, so plugins and core match.- The install offer asks first, and only in a terminal.
- An agent's start installs Link's
run,fileandcodetools (lnk-link-run,lnk-file-tool,lnk-link-code) the same way, without asking, when its harness keeps to Link's files folder and they aren't besidelnk: from the signed release oflnk's own version. install.shinstallslnkalone.- Link's plugins come only from Link's own releases. Prebuilt programs exist for macOS (Apple Silicon, Intel) and Linux (x86_64, arm64) only; other platforms build from source.
Plugins from outside Link
A plugin Link doesn't build is checked against its maker's key, which
you trust once and lnk pins.
lnk plugin add --url <url>takes a GitHub repository's latest release (https://github.com/<owner>/<repo>only), itsSHA256SUMSsigned with that repository's own key in thelink-releasenamespace, and each archive checked against it. Link's keys never vouch for it.- The first
addtrusts the key the release itself offers (release-key.pub), after showing its fingerprint and asking, or--yes. Whoever controls the repository on that first day is trusted: compare the fingerprint with one its maker publishes elsewhere. lnkpins the repository and the key in~/.lnk/settings.toml(600). Every lateraddandlnk upgradeof it verifies against the pinned key alone: a release signed by another key is refused, and a key is only replaced byremove, thenaddagain.- It must describe itself as a plugin (
--link-describe), never a program of its own, before it's installed, and its name can't be one of Link's plugins'. - An outside plugin runs as you, outside any sandbox, like any program
you install: the harness plugin asks it what to run, and
lnkasks its version. Link's sandbox confines the harness it installs and runs, not the adapter's own answers. - On a box,
lnk agent moveadds it with--key, the key this computer pinned, so the box trusts nothing the release offers. lnk upgrade, andaddagain (as a box does on every move), take its latest release and install it only when it is newer than the one installed; one whose version, or the installed one's, can't be compared is refused. A release whose signedVERSIONfile says it is no newer is neither downloaded nor run.- A
SHA256SUMSline naming anything but letters, digits,.,_and-is dropped, so the names a release not yet trusted lists reach the terminal only as plain text.
Uninstall
An uninstall deletes only inside Link's two folders, and shows what goes before it asks.
lnk uninstalldeletes only inside~/.lnkand~/Link. A path a plugin names outside them, with..or relative, is left out. A path whose folder leads out of them through a link is left out too.- Before asking, it lists every path each picked part deletes.
- Before asking, it runs only Link's own plugins. Another program named
lnk-*inlnk's folder is listed, then told to uninstall and removed once you say yes. It's never asked to delete a part. lnk plugin removekeeps a plugin that fails to stop what it runs, and shows what the plugin said, so its services never outlive the program that stops them.
Releases and updates
How a release is signed, who checks it, and what keeps an older one from coming back.
Signing
Every release's SHA256SUMS is signed with the release key
(ssh-keygen -Y sign, namespace link-release). The public key is in
scripts/release/release-signers. It is built into lnk and the relay,
embedded in install.sh and the relay's bootstrap.sh, which installs
it on servers at /etc/link/release-signers; a test checks the copies
match. No release carries the key.
Who checks
The relay updater, the relay's bootstrap.sh, lnk upgrade and
install.sh install nothing without a valid signature, so someone with
the releases repo or its token can't push code.
lnk upgradeuses its own SSHSIG verifier,link_plugin::release.lnk upgrade --check, whichlnk upandlnk agent statusstart in the background at most once a day, reads the latest release'sVERSIONonly through its signedSHA256SUMS, so a notice can't name a version Link didn't release. It asks the releases host for three small files and sends nothing butlnk's version, as its user agent;LNK_UPDATE_NOTICE=offturns it off.install.shusesssh-keygen, and refuses to run without it unlessLNK_INSECURE=1.
The installer
curl https://local.link/install.sh | sh gets the script from the
relay's own signed binary, not from the releases repo, so the releases
repo can't swap it.
Downgrades
No signed release older than the one installed is put back, unless you ask for that version.
lnk upgraderefuses a release older than itself unless that version is requested, and refuses a version it can't parse.lnk upgrade <version>installs only that version: thelnkit downloads must report it, so an older signed release moved under the tag is refused.- Releases carry a
VERSIONfile inside the signedSHA256SUMS. A release without one is refused bylnk upgrade,lnk box createandinstall.shalike (lnk plugin addasks for its own version). lnk upgradechecks theVERSIONfile against the version asked for and thelnkit holds;lnk plugin addchecks it against its own version;lnk box createchecks it aslnk upgradedoes, the latest no older than a releasedlnkmaking the box, and has the box install the version it checked.install.shchecks theVERSIONfile againstLNK_VERSION, and thelnkit installs must report that version. When the relay served the script, it never installs alatestolder than the relay (the relay fills in its own version).- Right after a release, the relay can be up before its release is:
install.shthen says to try again in a few minutes. lnk plugin addalso runs each plugin's--version, which must match, and must answer when a release has noVERSIONfile.- The relay updater,
link-relay-update, refuses a relay whose--version(inside the signed binary) is older than the installed one's, or that it can't read, and remembers it so later runs skip it. - The relay updater also refuses one older than the floor bootstrap keeps in
/etc/link/min-relay-version, the operator's checkout's version, whether or not a relay is installed yet.LINK_ALLOW_DOWNGRADE=1is the operator's way to roll back on purpose. - The relay updater still installs a same-version build, since a
release may be rebuilt without a version bump. So an earlier build of
the same version can still be put back;
nothing older than the installed relay or the floor can. A server
bootstrapped without
--min-versionand with no relay yet has no floor, and takes any signed release.
Download limits
Downloads give up after 15 s connecting or 60 s without data. They stop
past 64 KiB for SHA256SUMS, its signature and VERSION, and past
200 MiB for an archive.
CI
The release's workflows keep the signing key away from third-party code:
- Workflow actions are pinned to commit hashes.
- Jobs that run third-party actions get no secrets.
- Signing and publishing run in separate jobs that use only
actions/*. - Default permissions are read-only.
- A publish fails if the signature doesn't verify against
scripts/release/release-signers.
Key rotation
The release key is rotated by the operator, on the relay's setup page: Rotate the release signing key.
Gaps
What these checks don't cover.
- The first
lnk plugin add --url <url>trusts whatever key the repository serves that day; nothing inlnkvouches for an outside plugin's key. - An outside plugin can go back to an older release signed by the same
key only if its maker publishes it as the latest;
lnk upgraderefuses a version older than the installed one. - An outside plugin's release without a
VERSIONfile, or whoseVERSIONsays newer than its program does, has its program run (contract, then--version) before it's refused as older. It is signed by the pinned key, so this runs nothing its maker couldn't run already. - The release key is a GitHub Actions secret; see Known limitations.
- A copy of
install.shthe relay didn't serve, such as the one in each release or in a checkout, has no relay version to compare with. Solatestpointed at an older signed release that has aVERSIONfile is installed.