Security · lockssh
lockssh security model
lockssh lets an AI agent run commands on a server over SSH. Its promise is narrow: the agent never holds the SSH key or password, never opens an SSH connection, and runs only what the human allowed, with the output masked before it leaves the console.
Parties #
| Party | Holds | Can |
|---|---|---|
| The human | the console's terminal, lockhub (lox run) |
approve the policy and its loosenings, decide tickets, trust a host key, unmask |
The console (lockssh account, own container) |
the key or password, the SSH connection, the approved policy, the audit log | run an argv on the host, mask its output |
The agent (lockssh-clients, or an --allowed-uids uid) |
a socket in a read-only volume | ask the console; propose a policy change |
| The server | the SSH account's rights | run what it is sent |
The boundary between the agent and the console is the operating system's: separate accounts, the kernel's peer credentials on the socket, separate containers. The lockkit console framework holds it; lockssh adds the SSH side.
What lockssh enforces #
- The key never reaches the agent. It lives in the console account's home volume (mode 0600, refused otherwise), and a passphrase or password is asked by the console, read from the keychain or from Vault, wiped after use. Nothing the agent can read (the socket volume, the project, a result, an error, the audit log) carries it; errors are scrubbed.
- No shell syntax from the agent. The agent sends an argv; the console quotes every argument in POSIX single quotes. The server's login shell reads one simple command: argv[0] with argv[1:] as its arguments. No PTY, no stdin, no port, agent or X11 forwarding, no subsystem.
- Only allowed commands run unasked. A command runs at once only when an
allow rule of the approved policy matches it (and its auto rule is
auto). Anything else is refused, or, on a profile withexec = true, waits for a human's decision on a signed ticket bound to the argv, the agent, and the policy fingerprint; it runs once. - The policy is the human's. The approved policy is the one in force; an
agent that edits
.lockssh/config.tomlloosens nothing until the human approves the diff in the console. - Bounded runs. Each command has a timeout (it is then sent SIGKILL and its session closed) and an output cap per stream.
- Masked output. Every byte of stdout and stderr goes through lockkit's
detectors before it leaves the console; values become keyed pseudonyms,
secrets
⟦secret⟧. Clear output needs the human's switch in the console and a decided ticket, never an auto rule. - Host keys. Unknown keys are trusted only by the human on the console; changed keys are refused.
- Audit. Every request, refusal, decision and run is in an append-only, hash-chained log, with the command masked, never the output.
What lockssh does not do #
- It does not know what a program does. An allow rule allows a program
with arguments; a program that runs other programs or writes files from
its arguments (
find -exec,awk,less,vim,tar,sudo,env,sh -c,xargs,gitwith hooks) turns a narrow rule into a shell. Allow such programs only with literal arguments, never with*or**. decisions.md lists them, and the policy review warns on such a rule; the list is a safety net, not a complete one. Globs never match an option (-,+) nor a./..segment, so a glob cannot choose an option the human never wrote or climb out of a directory. - It does not resolve paths on the host. A rule's path is text: a symlink under an allowed directory reads wherever it points.
- It does not confine the SSH account. What a command can do is what
the account can do. Use an account of its own with only the rights the
allow list needs; a root login is refused on production and asks a typed
continueelsewhere. - It does not mask what its detectors miss. Person names, postal addresses, free-text personal data and secrets without a known shape or label reach the agent. Command output can be anything: prefer commands whose output you know (status, counts, metrics) over reading files.
- It does not prevent exfiltration by an allowed command. An allowed
command that sends data elsewhere (
curl, a mail command) does so. - It does not separate you from your own account. Whoever runs
dockercommands as you can attach to the console or read its volume; run the agent in a container of its own without the Docker socket (lea) for that boundary.
Why it is built this way #
decisions.md gives each design decision and its rationale.
Reporting #
See SECURITY.md.