Secrets go only where you grant them

Your secrets, kept on this computer and given only where you grant them. A secret is any value kept private: an npm token, a database password, an API key. A file that names one is never enough to be given it.

lnk plugin add env    # the plugin that owns the example environment
mkdir link-secret-example
cd link-secret-example
cat > env.toml <<'EOF'
name = "example"
version = 1

[secrets]
sealed = ["STRIPE_KEY"]
EOF
lnk secret create STRIPE_KEY --internet-host api.stripe.com   # enter the value when asked
lnk secret add STRIPE_KEY --env example                       # give it to the example environment
lnk secret list                                               # secrets and credentials, never values
lnk secret delete STRIPE_KEY

What it needs

You need the secret plugin: lnk plugin add secret, or any lnk secret command offers it. The quickstart adds the environment plugin and declares a local environment to grant the secret to; use a value of yours when prompted. Environments explains how a run receives its sealed secrets.

Keep a secret

lnk secret create keeps a secret: its value, asked without echo or read from its input's first line, and how it is given. It is given to no one yet. Creating a secret again replaces it whole, its value, how it is given and every grant.

Grant a secret

lnk secret add gives a kept secret to what a flag names, once for each: --agent <name>, the agent of that name on this computer, --job <name> and --env <name>, the job or environment of that name, which the plugin keeping it vouches for (answering secret.principal). The grant keeps the agent's ID, or what that plugin vouched for, as it said, run in the folder lnk secret add was run in: what it's given is that plugin's to say. A job or environment no installed plugin keeps is refused. lnk secret remove stops giving it, and the secret stays kept.

An agent

Start an agent once with lnk agent start before granting it a secret, so it has an ID. An agent is given a secret sealed only, for its host (--internet-host), and is told a placeholder, never the value. An agent made later with the name gets none of it. An agent on a box is granted its secrets there (lnk box ssh <box>).

What a plugin keeps

What a plugin keeps may be given a secret plain: with no --internet-host, it's kept plain and given as that plugin gives it; with --internet-host, sealed, as an agent's is.

Seal a secret for one host

A secret given to what runs as a variable could be read and sent wherever its sandbox reaches. A sealed one is added to requests for one host on their way out through the sandbox's proxy, by this plugin's bridge outside it (lnk-secret secret bridge), and what runs never has it.

lnk secret create STRIPE_KEY --internet-host api.stripe.com
# for git over HTTPS, as HTTP Basic credentials with the user x-access-token
lnk secret create GITHUB_TOKEN --internet-host github.com --basic-user x-access-token
# in a header named for an API key, the value as it is
lnk secret create OPENAI_KEY --internet-host api.openai.com --header X-Api-Key
lnk secret add OPENAI_KEY --agent main

How a sealed secret is given (its host, header and format) is set with its value, never by the file that names it. The proxy adds it to any call to its host, on any path and with any method: whatever runs can use it there as fully as the secret allows, so seal it for the one host it's for, with the narrowest secret that host offers.

Your agents' own credentials

An agent's model credential, its fallback's and its connections' tokens are kept here too, granted to it: lnk agent start and lnk agent connect add them, as AGENT_<AGENT>_MODEL_KEY or AGENT_<AGENT>_TELEGRAM_BOT_TOKEN. When the agent is removed they go with it. When it moves (lnk agent move) they go with it too, sealed to the machine it goes to, and are kept there as they were here. A key still where Link kept it before (the agent's settings, or the Keychain's agent/<agent>/…) is never read nor removed: the agent's start names it and how to give it again.

An agent that leaves

A secret granted to an agent that leaves this computer stays here only while something else is still granted it, or while it is a model key retained for a host that supplies the agent's models. A retained key has no live agent grant and lnk secret delete still deletes it. What you added with lnk secret add --agent doesn't go with the agent: grant it again where the agent runs.

Look at one secret

lnk secret show STRIPE_KEY says how one secret is given and where it is granted, never its value. lnk issue asks it for the state of a secret named in a failure, so a bug report holds it scrubbed.

Take a secret back

lnk secret delete STRIPE_KEY takes it from this computer at once. A run already started keeps what it was given: an agent's proxy adds it until the agent next starts (lnk agent stop, then lnk agent start).

Copies elsewhere

When a move sends an agent's keys to a box or a cloud account, this computer notes the place for each key first. It does this before anything is sent, so a move cut short is noted too. lnk secret list shows where each secret has copies. lnk secret delete asks the plugin whose moves made them, the harness plugin, to reach each place (its question secret.copies).

A host that supplies its models

When a host supplies its own models, the model keys stay at the source machine and remain noted there, including on a computer controlling a move from a box. Connection tokens still follow the agent. Older copy records that omit a credential's use cannot distinguish those keys; Link refuses that move and asks you to run it on the machine holding the agent, or move the agent here first.

On a box

It runs lnk secret delete <NAME> there, through lnk box run, removing only that agent's own key. A box that's gone took its copy with it, and so did one made again under its name since, which is never touched. A box without the secret plugin (one readied before boxes got it) is named, with how to add it: lnk box run <box> -- plugin add secret.

A box it can't reach

A box it can't reach (stopped, or offline) stays noted. lnk secret list shows it as left there, and remove exits non-zero. Run lnk secret delete <NAME> again once the box is up, and it reaches it.

On a cloud account that hosts agents

The key is removed when the agent there is removed or moves away. remove says so, and the note stays until the agent moves away, or until remove, signed in as the account the agent went there as, finds that the account no longer lists it.

Once an agent leaves

Once an agent leaves a box or an account, the keys it took are gone from there, and so is the note. Each agent's notes are its own: an agent made later with a removed agent's name never changes them.

Where secrets are kept

On a Mac, each value is in the Keychain (link, secret/<NAME>). On Linux, they're kept by the keeper, a user of their own (lnk-secrets) that no program of yours runs as, once you've made it; until then, and if you say no, in ~/.lnk/secrets.toml, mode 600, which any program you run outside a sandbox can read. Either way the store holds how each is given and where it's granted too.

Nowhere else on the disk

Nowhere else on the disk. An agent's proxy's routes, ~/.lnk/sandbox/<agent>-<harness>.routes.json (600), name its sealed secrets, its connections' tokens and its model's key, and hold none: at each call, its bridge reads the one it adds from the store, only while it's granted the agent, so a secret or grant removed stops at the next call.

The keeper

Your first lnk secret create on a Linux computer asks once whether to make the keeper (--yes answers yes). It needs sudo once, and systemd:

lnk secret keeper    # makes lnk-secrets, puts lnk-secret in its place, starts its service

What lnk secret keeper does

It makes the user lnk-secrets, which has no login and runs nothing else; copies this lnk-secret to /usr/local/lib/lnk/lnk-secret, root's, which nobody else changes; and starts the service lnk-secrets, which keeps each user's secrets in /var/lib/lnk-secrets/<uid>/, readable by it alone, and answers on /run/lnk-secrets/keeper.sock. A caller there reaches its own user's secrets alone, and no sandbox has the socket.

Your own file, and upgrades

What you kept in your own file moves to the keeper the first time it's asked, and the file goes. Run lnk secret keeper again after an upgrade, to put the new lnk-secret in its place.

On a box

A box made by lnk box create installs and starts the keeper during setup, together with the worker launcher. Ordinary-box work runs as separate users without sudo; its proxy and the keeper stay outside that identity and its sandbox.

A TPM

With /dev/tpmrm0, setup installs tpm2-tools and gives the keeper access to that device's group. Where the device is root's alone, as on Google Cloud's Ubuntu image, setup first makes the group lnk-tpm and gives it the device, now and at every boot (/etc/udev/rules.d/70-lnk-tpm.rules); a device it can't give a non-root group is refused. Its store is sealed to that TPM; copying the disk to another TPM cannot open it. Existing plaintext keeper files migrate on read. A failing TPM never falls back to plaintext. Without a TPM the keeper's private files remain the fallback.

A sealed route

A sealed route reaches the proxy as ciphertext. The keeper also serves /run/lnk-secrets/secret.sock, where it checks the proxy's actual UID and current bridge proof before opening the grant and sending the request. Only root may bind a grant for a different proxy UID. A replaced or unbound grant stops opening, and restarting the keeper requires proxies to bind again.

A no

A no is remembered in machine.toml (keeper = false), so it isn't asked again; lnk secret keeper makes it later. With no terminal, nothing is asked: your secrets are kept in your own file, and the command says so.

Deleted and listed

lnk uninstall deletes what the keeper keeps for you with the secret plugin's part. lnk secret list lists each, never its value, and says so when the file can't be read. It lists the credentials every plugin keeps on this machine too, such as your Link login, and --agent work only the secrets given to that agent. What can read them, and how they travel, is in Credentials.

When it goes wrong

What to do when lnk secret refuses a grant, leaves a copy on a box or a cloud account, or can't reach its keeper is on Troubleshooting.

Docs

  • Interface: How a program is handed the secrets it may use, never through its environment
  • Decisions: Why lnk secret works the way it does
  • Troubleshooting: What to do when lnk secret refuses a grant, leaves a copy behind, or can't reach its keeper (Secrets)