Decisions
Why lnk secret works the way it does.
Keeper storage and routes
Why the keeper's store and the routes that open it are made as they are, so no copy of a disk or of a route's file gives a secret away.
Why seal the keeper's store to a TPM?
A wrapping secret on the same disk would travel with a stolen copy. The TPM instead seals a small recipient to its own hardware; disk holds only its opaque blobs and encrypted store. Existing stores migrate when opened, under a lock. A TPM failure refuses the operation, while a machine without a TPM retains its reported private-file fallback.
Why bind an encrypted route to a UID and a fresh grant identity?
A proxy must carry ciphertext without holding the credential. The keeper opens it only for the socket's actual user and the current bridge proof. A fresh identity on each grant also makes retained ciphertext unusable after revocation, even if the same proof is registered again. Root may bind for a helper's user; a working user may bind only for itself.
Why does a route name a secret, never its value?
So a secret's value is in its store and nowhere else on the disk. The
bridge that owns the store adds the value as it binds the route, after
checking the grant names the route's caller, so a removed secret or
grant stops at the next bind and leaves no file to clean. A run handed
secrets by its caller (lnk sandbox run --with-fd) is granted nothing
by the store, so its values go to its proxy on a pipe, never in a file.
Why is a secret given only where it's granted?
Whoever can edit a file can name a secret in it: a dependency, an agent writing in the workdir, a pull request's branch. If naming a secret were enough to be given it, any file run on this computer could take any secret kept here. So the grant is kept with the value, on this computer, and a file only names what it's already been granted.
Why is it lnk secret, not lnk auth?
lnk auth signs in to accounts, and a secret may be any value, not
only a credential, granted to an agent. So secrets
are a command and a plugin of their own.
Why add?
A secret's value is made somewhere else (by npm, Stripe or GitHub), and
Link takes it on. Adding one again replaces it whole, so what lnk secret list shows is what you last said, never a mix of two adds.
Why does how a secret is given live with its value?
A sealed secret's host is where its value may go. If whatever names
it set its host, whoever edits that could send it elsewhere. So the
host, the header and the format are set with the value, by lnk secret add, and nothing else changes them.
Why is an agent given a secret sealed only?
An agent's harness does what its model decides: a value in its environment is the model's to read and to send wherever its sandbox reaches. Sealed, the agent's proxy adds the value to calls for its one host, outside the sandbox, and the agent holds a placeholder, so it can use the secret and never take it.
And a plain value?
Every secret an agent needs so far is an HTTPS call to one host, which sealing covers. Should one need a plain value, it would be handed to the harness by a stage of Link's own between its background service and its sandbox, never in the service's definition, which other users of the computer can read.
Why is an agent's grant bound to its ID?
An agent's name is free again once it's removed. A grant to the name would hand a removed agent's secrets to the next agent given it, so the grant keeps the agent's ID, which no other agent ever has.
Why does another plugin vouch for what it keeps?
The secret plugin knows agents, the plugins built on it know what else
is given a secret, and it names none of them: it asks a question
(secret.principal) that only a plugin built on it answers, saying
which of its own a <kind>:<name> is, and keeps the id it says. That
plugin asks for what's granted to that id again when it gives it, so
a grant to one thing never reaches another of the name, and a kind no
installed plugin keeps is refused as it's added.
Why are an agent's keys refused where Link kept them before?
Moving them would mean code that reads every old place, kept for as long as anyone might still have one. So a key still there is named when the agent starts, with how to give it again, and nothing removes it: you remove it there when you choose.
Copies a move gives
Why the copies a move sends to a box or a cloud account are noted, and
how remove reaches them.
Why is a copy noted before it's sent?
A move sends an agent's keys, sealed, to the machine it goes to. A
note written after the switch would miss a switch cut short, where the
other end may have received the keys and kept them. So the move notes
the place first, on the computer running it, and the note says where a
copy may be. A note for a place that has no copy costs a remove that
finds nothing there.
Why does remove exit non-zero for a box it couldn't reach?
The point of remove is that the value is gone everywhere it went. A
box that's stopped still has its disk, and a key on that disk is still
usable. So the box stays noted and remove fails until a later run
reaches it, rather than saying it's done when it isn't.
Why is a copy noted by agent and by machine?
An agent's own keys are named after the agent, and a name is free again
once its agent is removed. Noted by name alone, the copies of a removed
agent and of a new one of its name would be one note, and one's moves
would change the other's. A box's name is free again too, once the box
is removed. So each agent's copies are noted by its ID, and a box by
its machine ID as well as its name. remove asks a box to remove only
that agent's key, and never touches a box made again under the name.
Why is a cloud account's copy left until its agent goes?
A cloud account that hosts agents keeps an agent's keys with the agent,
and Link's contract with it has no way to forget one key. So remove says where the key is and that it goes with the
agent there. This computer keeps the note until the agent is removed
there or moves away.