Credentials, who mints, holds and checks each
Every credential Link mints, holds or checks: what it proves, who mints it and who holds it, where it lives, how it travels, how long it lasts, how it's revoked and what it may do. A secret is any value kept private, and a credential is a secret used to authorize, so every credential is a secret.
Whose it is
A credential is named by whose it is: a user credential, a machine credential, an agent credential, or a service credential, a service's own and shared by no user.
Each service's own
Each service lists its own secrets: the relay's are in its security, a release's in Releases.
Not built yet
These pages are the design Link's credentials are built to. Where the code doesn't do it yet, the entry says so under Not built yet, with what runs today.
The rules
Each credential is written down here: one Link handles that this page doesn't name is a bug. Below is where a credential may go, and how it gets there; how each is held is in How each is held.
Never printed
Output that describes something (an account, a machine, an agent, a
tunnel) never carries a credential, in text or --json, since a person
copies it blindly. Logs never carry one, and neither do errors. The
only commands that print one are those made to manage credentials, and
only when that is the point: lnk auth login prints a code to type in
the browser.
Handed on only for its purpose
A credential goes to another program only when this page says so, and
then plainly: an access token to the plugin that asked for it, through
a pipe, never a terminal. lnk auth account, lnk auth relay
and lnk auth token print an access token so; lnk iam machine add prints no credential, since the new machine's own key
never leaves it.
Never on a command line
Where every user of the machine sees it. A secret comes from standard
input, a file, a variable or the Keychain, and no flag takes one:
flags.rs refuses a command whose flag named like a secret takes a
value, a file's path aside.
What travels is short and narrow
A credential that lasts never leaves where it's kept; what crosses a network is made for one audience, one purpose and minutes.
Sealed when it crosses
A secret that must reach another machine crosses only sealed (Sealed,
HPKE: X25519, HKDF-SHA256, ChaCha20-Poly1305) to a key the receiving end
makes for that one transfer and says first. A machine drops its
receiver when that receiving process ends.
A hosting account's receiver
A hosting account keeps an independent per-move receiver for restart, deletes it after commit where possible, and refuses reads after 24 hours even before its sweep removes it.
A move's switch
This covers a move's switch (lnk agent receive), which refuses one
unsealed. A computer in the middle or a log reads no switch secrets; a
captured switch requires its receiver, independently of Home's lasting
encryption key.
Its records
The complete switch is encrypted in authenticated 64 KiB records, including its manifest, harness home and linked connection state. An authenticated final record is required before the switch succeeds; missing, altered or reordered records fail. The preliminary copy still carries files over SSH or the hosting account's TLS, without the switch's harness state and connection links.
For a machine not there yet
What must wait for a machine that isn't there yet, a connection's token you connect in the browser, is sealed instead to a key the machine keeps for it, its sealing key.
Short bearer tokens
Short bearer tokens made to be shown, to one audience over TLS, are the exception, by purpose.
Carried only by what's made to carry it
A secret travels over SSH (a move's stream) or TLS (a service, a grant read once), and to a program on a pipe or standard input: never on a command line, through the relay, in a bucket or in a log.
No child carries one it wasn't given
A program Link runs gets Link's environment but every variable named
like a secret's (a word of it TOKEN, KEY, SECRET, PASSWORD,
PASS, AUTH or CREDENTIALS, or …DATABASE_URL; not one ending
_FILE, _PATH, _DIR or _SOCK) and every one Link read a secret
from; a variable read as a program starts, on one thread, leaves its
environment.
Link's own programs
Link's own programs (lnk, a plugin, a tool) are handed every secret
the program that started them holds, on a pipe (Secrets'
Interface).
A program that reads its environment
A program that reads one from its environment and nowhere else gets it
there, each place listed by link-secret-value's audit with why: a
cloud's command line (aws, gcloud, rclone) its sign-in, a tool's
server (lnk sandbox tool) its environment as you gave it, and the
sandbox's own stages the environment their caller gave the program.
How each is held
What every one of Link's programs does with a secret it holds, and where a secret is kept.
Held by a process safe to hold them
Every one of Link's programs makes itself so first, in main
(process::guard): it writes no core dump and isn't dumpable, so no
other process of its user reads its memory, its environment or its
descriptors (the kernel makes its /proc/<pid> root's). A program it
runs is dumpable again once it starts.
Its memory out of swap
It locks its memory out of swap where it may lock all of it: its limit
on locked memory unlimited (the relay's unit sets it, and the relay
runs no program of anyone else's) or CAP_IPC_LOCK held (a service run
as root), never by raising a limit a program it runs would inherit.
LNK_SECRET_DUMPABLE
A debug build with LNK_SECRET_DUMPABLE set is left as it is, for a
debugger; a release build never reads it.
Held in its type, by where it may go
In Rust, every secret is in one of three types of the
link-secret-value library (which link_plugin re-exports), and a
wrong way doesn't compile.
OpaqueSecret
One Link uses and nobody reads (an agent's proof secret, a binding's, a service's signing secret, what a holder's placeholders are made with): it signs and checks, and there's no way to its bytes.
Secret
A value another program or service must be given (a model's key, a
connection's token, a password): read only by asking for it by name
(expose), at a place link-secret-value's audit lists for its folder;
copied only by saying so (duplicate).
Sealed
Either of them encrypted to one recipient's key (Sealed when it crosses): the only form that crosses to another machine.
What none of them does
None prints what it holds: Debug writes <redacted>, and none has
Display, so what takes one (format!("{}"), a header) doesn't
compile; nor Clone, nor Serialize. Its memory is zeroed when it's
dropped, and it's compared in constant time.
Where it's written as its value
A Secret is written as its value only where it's kept, a file of mode
600 or the keyring, by bare, each place in the audit's list.
The audit
Every folder's tests run the audit over its own code: a new place a
secret is read, written as its value, or given to a child fails until
it's listed, which a review sees. None of the old ways passes at all (a
copy of an exposed value, one on a command line or put in a child's
environment by hand, a redacting Debug written by hand, zeroize
outside the library).
A field named like a secret
A field named like a secret holds one of the three types, or is in the audit's list with what it holds instead (a public key, a name).
Accounts are their ids
An account's id, acct- and 32 hex digits, never changes; its username
can, at will. Whatever remembers who someone is keeps the id, or an
email the account service has verified, never a username.
Kept where only its keeper reads it
At rest a secret is in the Keychain on a Mac, and on Linux in files of
a user of its own, lnk-secrets, which the work never runs as; a box's
are sealed to its TPM where it has one (At rest).
Always findable
lnk secret list lists every credential this machine holds: what it is,
where it's kept and what it may do, never its value. So nobody digs
through hidden folders for their own.
Where each is listed from
lnk secret list asks lnk uninstall --parts --json, where each of
Link's plugins keeping something says so by part (Plugin
Contracts): each
part names the credentials it holds, as {"what", "of", "kept", "may"},
never a value. So the list is what the plugins installed here hold,
each saying its own, and none reads another's files.
Accounts, harness and cloud
The accounts plugin lists this machine's key, or a relay operator's token; the harness plugin each agent's key while it's bound; the cloud plugin each cloud account's keys.
Box, bucket and measure
The box plugin lists Link's SSH key for boxes and the box SSH secret that signs its certificates; the bucket plugin each bucket's keys and encryption password; the measure plugin an export's headers.
Secret
The secret plugin lists each secret lnk secret keeps, where (the
Keychain, the keeper or your file), and where it's granted: agents'
model and fallback keys and their connections' tokens among them.
A harness from outside Link
OpenClaw's, Hermes's and DeepSeek Harness's adapters list what their
harness keeps in each agent's home: OpenClaw's gateway token, Hermes's
API server key and its Signal relay's secret, DeepSeek Harness's copy of
the endpoint's key. An adapter finds the agents' homes for its harness
with link_harness_contract::harness::homes, and lists them as the
agents part's credentials; the folders are the harness plugin's.
Who proves who they are
What each kind of caller proves itself with, and to whom. Apart from these are held credentials: yours, for other services (model keys, connection tokens, cloud accounts, bucket keys), which Link keeps and uses but never mints.
A person
Their sign-in to the account service, in a browser, which is the service's own, proves them to the account service.
A machine
Its machine key, signing for access tokens, proves it to the account service, then to the relay and services with access tokens.
A visitor to a tunnel
An access token from their sign-in, then the relay's visitor pass, proves them to the relay.
A relay's user, an agent and a service
A relay's own user proves themselves to that relay with an operator token, an agent to its bridges with its agent key, and a service to another service with a signing key of its own, as its own docs say.
Credential kinds
The credential catalogue gives every kind a stable identifier and a link to its owning explanation. Credential views use these types while keeping their existing display labels; the inventory is generated from the same declaration as those runtime values.
- A bucket's encryption password (
bucket-encryption-password) - A bucket's keys (
bucket-keys) - A cloud account's key (
cloud-account-key) - A connection's token (
connection-token) - A device code (
device-code) - A harness's token (
harness-token) - A machine assertion (
machine-assertion) - A memory embedding proof (
memory-embedding-proof) - A model's key (
model-key) - A remote tool's headers (
remote-tool-headers) - A sandbox proxy's secret (
sandbox-proxy-secret) - A secret you keep (
kept-secret) - A service's signing key (
service-signing-key) - A share link (
share-link) - A storage token that refreshes itself (
storage-refresh-token) - A tool's key (
tool-key) - A tunnel or share-link password (
tunnel-password) - A visitor's pass (
visitor-pass) - A webhook's secret (
webhook-secret) - An access token (
access-token) - An agent's key (
agent-key) - An export's header (
export-header) - An operator token (
operator-token) - Link's SSH key for boxes (
box-ssh-key) - Secrets you keep, unreadable (
unreadable-secrets) - The box SSH secret (
box-ssh-secret) - This machine's key (
machine-key) - This machine's sealing key (
machine-sealing-key)
Compatibility with outside plugins
The what field remains a JSON string. Known labels resolve to their
catalogue kinds; older or outside labels remain readable and round-trip
unchanged. Link's own code constructs a credential view from an explicit
kind rather than inventing another label.
What each may do
What each credential opens at the account service, the relay, another service and an agent's bridges; a dash is nothing there. Why it works this way: Accounts' decisions. How the relay checks what it's given: Tunnels: accounts and tokens.
| Credential | Account service | Relay | A service | A bridge |
|---|---|---|---|---|
| Machine key | Access tokens, by assertion | — | — | — |
Access token, tunnel | — | Open and serve tunnels | — | — |
Access token, share | — | Share links | — | — |
Access token, visit | — | Visit tunnels that let its account in | — | — |
Access token, api | Its API, as the machine | — | — | — |
Access token, identify | — | — | Say who it is, to its audience alone | — |
| Visitor's pass | — | Visit one tunnel | — | — |
| Operator token | — | Tunnels and share links, as its user | — | — |
| Agent key | — | — | — | Its agent's calls |
Docs
- A Machine: The credentials that prove a machine is one signed in to its account: its machine key and the assertion it signs with it, the sealing key what's sent to it is sealed to, and the device code of its login
- The Access Token: The one short token, for every audience: a JWT the account service signs, naming whose it is, for whom, for what, and until when
- On the Relay: The credentials a relay takes beside access tokens: a visitor's pass, an operator token, the passwords of tunnels and share links, and a share link's address
- An Agent: What proves a call comes from an agent, and what keeps another program from acting as it: its key, its memory's embedding proof and its sandbox proxy's secret
- Held Credentials: Yours, for other services
- At Rest: Where a secret is kept on a machine, and who there can read it
- A Webhook's Secret: What proves a post from outside comes from the sender that holds the secret, and the one way every receiver of Link's checks it