feat: add session token authentication for IPC socket#3375
Conversation
The Unix socket had no authentication — any local process could connect and send commands. This adds a session token handshake: the daemon generates a random 32-byte hex token on startup, writes it to ~/.vellum/session-token (mode 0600), and requires clients to send an auth message with the token as the first message after connecting. Unauthenticated connections are closed after a 5-second timeout. Co-Authored-By: Claude <noreply@anthropic.com>
# Conflicts: # assistant/src/daemon/server.ts
There was a problem hiding this comment.
Devin Review found 2 potential issues.
🐛 1 issue in files not directly in the diff
🐛 Swift DaemonClient never sends auth handshake — all native clients will be disconnected (clients/shared/IPC/DaemonClient.swift:366-378)
The daemon now requires clients to send { type: "auth", token } as their first message within 5 seconds of connecting (assistant/src/daemon/server.ts:485-491). The TypeScript CLI (cli.ts:667-673) and sendOneMessage (index.ts:73-80) were updated to read the session token and send the auth message. However, the Swift DaemonClient was not updated — it connects and immediately starts sending pings and other messages without ever sending an auth message.
Root Cause and Impact
After connecting, the Swift client at clients/shared/IPC/DaemonClient.swift:366-378 transitions to .ready, sets isConnected = true, starts the receive loop, starts the ping timer, and runs the blob probe — all without authenticating first.
The daemon's auth timeout at server.ts:485-491 fires after 5 seconds:
const authTimer = setTimeout(() => {
if (!this.authenticatedSockets.has(socket)) {
this.send(socket, { type: 'error', message: 'Authentication timeout' });
socket.destroy();
}
}, DaemonServer.AUTH_TIMEOUT_MS);Alternatively, if the Swift client sends a ping before the timeout, the daemon sees a non-auth message from an unauthenticated socket and immediately disconnects it (server.ts:557-559).
While the Swift side added the IPCAuthMessage struct and AuthResultMessage type alias, and the ServerMessage enum can decode auth_result, the DaemonClient never reads the session token file and never sends the auth message. The handleServerMessage method has no special handling for .authResult (it falls through to default: break).
Impact: The macOS and iOS native clients are completely broken — they will be disconnected by the daemon within 5 seconds of every connection attempt, entering an infinite reconnect loop.
View 8 additional findings in Devin Review.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d038520c41
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… token for SSH-forwarded sockets - broadcast() now iterates over authenticatedSockets instead of connectedSockets, preventing unauthenticated connections from receiving broadcast messages during the 5-second auth window. - CLI connect() now gracefully handles missing session token when VELLUM_DAEMON_SOCKET is set (SSH-forwarded socket), instead of hard-failing before the IPC exchange. Addresses feedback from #3375. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… token for SSH-forwarded sockets (#3410) - broadcast() now iterates over authenticatedSockets instead of connectedSockets, preventing unauthenticated connections from receiving broadcast messages during the 5-second auth window. - CLI connect() now gracefully handles missing session token when VELLUM_DAEMON_SOCKET is set (SSH-forwarded socket), instead of hard-failing before the IPC exchange. Addresses feedback from #3375. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
|
Addressed in #3410 |
Summary
~/.vellum/vellum.sock) previously had no authentication — any local process could connect and send commands~/.vellum/session-token(mode 0600), and requires clients to send{ type: "auth", token }as the first message after connecting🤖 Generated with Claude Code