fix(desktop): create control-socket dir before opening the SSH master

OpenSSH does not create intermediate directories for ControlPath, so on a fresh
box (no prior hermes-desktop-ssh dir under \$TMPDIR) the very first connect failed
when ssh tried to create the master socket. Unit tests mock spawn and never hit
real fs, so this was invisible.

open() now mkdir -p (mode 0700 — the socket grants command execution on the
master) the control-socket directory before spawning ssh. Mirrors ssh.py.
Added a test that open() creates a non-existent control dir.
This commit is contained in:
yoniebans 2026-06-19 16:48:54 +02:00
parent 28d7472fb4
commit cc96ac617a
2 changed files with 30 additions and 0 deletions

View file

@ -36,6 +36,7 @@ const crypto = require('node:crypto')
const net = require('node:net')
const os = require('node:os')
const path = require('node:path')
const fs = require('node:fs')
const DEFAULT_CONNECT_TIMEOUT_MS = 15_000
const DEFAULT_EXEC_TIMEOUT_MS = 20_000
@ -354,6 +355,15 @@ class SshConnection {
this._opened = true
return
}
// Ensure the control-socket directory exists — OpenSSH will not create
// intermediate dirs for ControlPath, so a fresh box (no prior hermes-ssh
// socket dir under $TMPDIR) would otherwise fail before the first connect.
// 0o700: the socket grants command execution on the master; keep it private.
try {
fs.mkdirSync(path.dirname(this.controlPath), { recursive: true, mode: 0o700 })
} catch {
// best effort — a pre-existing dir or a races-with-another-conn mkdir is fine
}
const args = buildMasterArgs(this, this._connectTimeoutMs)
this._logLine(`opening control master to ${target(this.user, this.host)}:${this.port}`)
let result

View file

@ -12,6 +12,9 @@
const test = require('node:test')
const assert = require('node:assert/strict')
const { EventEmitter } = require('node:events')
const fs = require('node:fs')
const os = require('node:os')
const path = require('node:path')
const {
SSH_ERROR,
@ -247,6 +250,23 @@ test('open() is a no-op when the master is already alive', async () => {
assert.deepEqual(ops, ['check'], 'alive master → no second spawn to open it')
})
test('open() creates the control-socket directory if it does not exist', async () => {
const dir = path.join(os.tmpdir(), `hermes-ssh-test-${process.pid}-${Date.now()}`)
assert.ok(!fs.existsSync(dir), 'precondition: control dir absent')
const spawnFn = scriptedSpawn(args => (args.includes('check') ? { code: 255 } : { code: 0 }))
const conn = new SshConnection({ host: 'box', user: 'me' }, { spawnFn, controlDir: dir })
try {
await conn.open()
assert.ok(fs.existsSync(dir), 'open() created the control-socket directory before spawning ssh')
} finally {
try {
fs.rmSync(dir, { recursive: true, force: true })
} catch {
/* ignore */
}
}
})
test('open() surfaces a classified auth error', async () => {
const spawnFn = scriptedSpawn(args => {
if (args.includes('check')) return { code: 255 }