NousResearch/hermes-agent · error · Error

Unsafe SSH target: host is required.

Error message

Unsafe SSH target: host is required.

What it means

First check in validateSshTarget() in the desktop app's ssh-connection module: the SSH host must be a non-empty string. It guards SshConnection construction and target building before any value reaches an ssh command line.

Source

Thrown at apps/desktop/electron/ssh-connection.ts:49

import { spawn } from 'node:child_process'
import crypto from 'node:crypto'
import fs from 'node:fs'
import net from 'node:net'
import os from 'node:os'
import path from 'node:path'

const DEFAULT_CONNECT_TIMEOUT_MS = 15_000
const DEFAULT_EXEC_TIMEOUT_MS = 20_000
const DEFAULT_FORWARD_TIMEOUT_MS = 15_000
const CONTROL_PERSIST_SECONDS = 300

// eslint-disable-next-line no-control-regex -- deliberately reject control chars in ssh targets
const _CONTROL_CHAR_RE = /[\x00-\x1f\x7f]/

function validateSshTarget(host, user, port) {
  if (!host || typeof host !== 'string') {
    throw new Error('Unsafe SSH target: host is required.')
  }

  if (host.startsWith('-')) {
    throw new Error(`Unsafe SSH target: host must not start with a dash ("${host}").`)
  }

  if (_CONTROL_CHAR_RE.test(host)) {
    throw new Error('Unsafe SSH target: host contains control characters.')
  }

  if (user && _CONTROL_CHAR_RE.test(user)) {
    throw new Error('Unsafe SSH target: user contains control characters.')
  }

  if (user && user.startsWith('-')) {
    throw new Error(`Unsafe SSH target: user must not start with a dash ("${user}").`)
  }

View on GitHub (pinned to c896c09c42)

Solutions

  1. Require and validate host in the remote-backend form before attempting to connect.
  2. Check the config source: confirm the field the code reads (host) matches the key the config writer persists.
  3. If host comes from a URL, parse it explicitly: new URL(url).hostname.

Example fix

// before
const conn = new SshConnection({ host: cfg.server, user: cfg.user })

// after
if (!cfg.server) throw new TypeError('remote config missing server field')
const conn = new SshConnection({ host: new URL(cfg.server).hostname, user: cfg.user })
Defensive patterns

Strategy: type-guard

Validate before calling

if (!remoteCfg || typeof remoteCfg.host !== 'string' || remoteCfg.host.length === 0) {
  throw new TypeError('Remote backend config must define a non-empty host string')
}

Type guard

function isSshConfigWithHost(cfg: unknown): cfg is { host: string; user?: string; port?: number | string } {
  return Boolean(cfg && typeof (cfg as any).host === 'string' && (cfg as any).host.length > 0)
}

Try / catch

try {
  conn = new SshConnection(cfg)
} catch (e) {
  if (e instanceof Error && e.message === 'SshConnection requires a host.') {
    promptUserToCompleteRemoteConfig()
    return
  }
  throw e
}

Prevention

When it happens

Trigger: Constructing SshConnection with cfg.host undefined/null/'' or a non-string, or calling validateSshTarget directly with such a value. Typically a remote-backend config deserialized without a host field, or a connection form submitted with the host blank.

Common situations: Remote backend config missing the host key after a partial save; UI allowing submit with an empty host; code passing a URL where a bare hostname is expected so the field lookup returns ''; a config factory returning {} before the user has filled anything in.

Related errors


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/c91e433b9e725999. Report an issue: GitHub.