Secrets Contract

link-secret-contract, the library every program that keeps or gives a secret reads it through: the store, its grants, the keeper's protocol and the secret route's types. lnk-secret is its command line (Secrets).

The store

A user's secrets are kept as files, secrets.toml (mode 600) and its lock beside it: yours in Link's settings (~/.lnk/secrets.toml, path), or one the keeper keeps for a user (Store). Each value is in the Keychain where there is one, its account under secret/ (KEYCHAIN).

Reading it

list gives each secret kept for you, by name: how it's given and where, never its value, and those no longer kept but with copies left elsewhere, by their copies alone. for_agent gives what the agent of an ID is granted, its own or what lnk secret add --agent gave it; a value the Keychain should hold and doesn't is an error, naming it.

Writing it

add keeps a secret, sealed when it's sealed, given where its grant says, replacing whatever was kept by that name. keep_own keeps an agent's own keys, dropping one no longer its own (a connection disconnected), and is written only when something changed. clear removes every secret kept for you, and their store, for lnk uninstall.

An agent's own names

own_name names an agent's own secret AGENT_<NAME>_MODEL_KEY, AGENT_<NAME>_TELEGRAM_BOT_TOKEN or AGENT_<NAME>_WEBHOOK_<HOOK>_SECRET (and …_PREVIOUS while it rolls). Every agent's own secret's name starts with AGENT_ (AGENT_PREFIX), and no other's does.

Grants

Grant is where a secret is given (--to), kept with its value, and Use how an agent is given one granted to it.

An agent's

lnk secret add --agent <name> asks the plugins that keep agents where the agent of that name is (AGENT, answered with an AgentAt). agent_here gives its ID, or an error naming the box it's on, where its secrets are given it.

Another plugin's

For any other kind, lnk secret add --job <name> or --env <name> asks the plugins built on this one who it is (PRINCIPAL, its Asked the kind:name and the real folder it was given in). vouched gives the answer, or an error when no plugin keeps the kind or the one that does refuses it, and for_principal gives what's granted to it, plain or sealed.

Copies a move gives

copied notes a place (Place, a box or an account by its name) for each of an agent's own keys before a move's switch sends them there, never a secret lnk secret add gave it. gone_from takes the note off once the agent has left that place, and forget_agent takes back every grant to an agent that leaves or is removed.

Models supplied by a host

retain_own parks selected own uses in the keeper while the agent uses a hosting account's models. forget_agent removes its live grants but keeps those values; keep_own restores them for the same stable ID. remove deletes retained values too. copied_without omits uses the host never receives, so they are never listed as copied there.

Following a remote move

gone_from_without preserves those uses at the source after the agent leaves it. Copy records keep each own use so a controller can follow connection tokens while leaving model keys at their source box. Older records without a use refuse an excluded-use move before any copy changes; moving on the source machine, or first moving here, replaces the ambiguous record.

Removing a secret

remove notes every copy as left before any is tried, and takes each reached off after, so nothing is forgotten if it's cut short; with an agent, only that agent's own key goes. It reaches copies by asking the plugins whose moves gave them (COPIES, answered per copy with a Reached); with none answering, each is unreached.

Listed for lnk secret list

held gives the secrets kept for you as lnk secret list shows them: where each is kept, where it's given and where a move left a copy, never its value. One no longer kept here is listed by the copies left elsewhere. A store that can't be read, or a keeper that doesn't answer, is one entry saying so.

The keeper

The keeper runs as lnk-secrets (KEEPER_USER), which holds secrets and runs nothing else, and answers on /run/lnk-secrets/keeper.sock (SOCKET) and, for sealed routes, /run/lnk-secrets/secret.sock (ROUTE_SOCKET). here says whether there's a keeper on this machine, and running_as_keeper whether this process is it.

Its protocol

A call to whoever holds your secrets is a Request, answered with a Reply; Holder is who holds them, the keeper or your own file. serve and serve_with_routes are the keeper's side. A debug build's tests point it at a keeper of their own with LNK_TEST_KEEPER (TEST_KEEPER).

At rest

at_rest::has_tpm says whether there's a TPM. at_rest::write seals first, then atomically replaces and syncs the file and its directories; without a TPM the file is the keeper's private fallback, never the working user's. at_rest::read opens a private file, and migrates plaintext when asked.

The secret route

The route adds a sealed secret to its service's calls on their way out of a sandbox, served at /_link/secret/ on the proxy (PREFIX) by lnk secret bridge (bridge). route builds one from the secrets handed with their values and those granted by name, none named twice.

How a secret is given

Sealing is how a sealed secret is given, set with its value: the one host it's sent to, over HTTPS, the header it goes in, and that header's value. SecretRule is where a secret may go and how, which a plugin that owns the service gives (a connection plugin's rule --json) and the proxy enforces, knowing nothing of the service: its paths (SecretPath, SecretRest) and header (SecretHeader).

Names on the proxy

proxy_name gives a sealed secret's name on the proxy (stripe-key for STRIPE_KEY), which check_secret_name holds to 1 to 40 lowercase letters, digits and -, not starting with -. sealed_url_var gives the variable what runs calls its service at, LNK_SEALED_<NAME>_URL, and url that address on a proxy's port.

Placeholders

placeholder gives what a program holds in place of a secret: a tag, none of the secret, after the part a rule says isn't secret, which a client may read (secret_prefix).

Bindings

binding::Unit is one agent at a time, and only while bound: a key kept from a binding that slept proves nothing, and the next agent bound gets a new one. binding::Keyring wraps the bridge's keyring so a binding's keeper grants are revoked before its keys are removed.