import { type Server } from 'node:net'; import { MAX_MESSAGE_BYTES, type AgentBackend } from './ssh-server'; /** * The socket `SSH_AUTH_SOCK` points at. * * `ssh-server.ts` speaks the protocol and had nowhere to speak it: the message * handler was complete, tested, and nothing ever listened. An agent protocol * with no socket is a parser. * * ## Framing is a stream problem, not a message problem * * A unix socket delivers bytes, not messages. `ssh` pipelines requests, and a * single `data` event can carry two of them or half of one — so the buffer is * drained in a loop until `readMessage` says it needs more. Handling one * message per event works in every manual test and fails the moment a real * client is fast. * * ## The DIRECTORY is the access control, not the socket * * Anything that can open this socket can sign with every key the vault holds. * There is no authentication inside the protocol and there is not meant to be: * the filesystem is the boundary, exactly as OpenSSH's own agent does it. * * Chmodding the socket alone is not enough, and measuring it showed why — the * file appears when `listen` binds and the mode is only corrected in the * callback afterwards, so it exists world-connectable for a moment first. A * brief window is still a window on a machine where anything can retry. * * So the containing directory is created 0700 BEFORE binding. Traversal is * denied for everybody else regardless of what the socket's own bits say, which * is why `ssh-agent` puts its socket in a private directory too. The socket is * chmodded as well, because two boundaries cost nothing. */ export interface SshListenOptions { socketPath: string; backend: AgentBackend; /** Reported per connection failure. Never the key, never the data. */ onError?: (error: Error) => void; } export declare function startSshAgent(options: SshListenOptions): Server; export { MAX_MESSAGE_BYTES };