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.

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.