NousResearch/hermes-agent · error · Error
Unsafe SSH key path: contains control characters.
Error message
Unsafe SSH key path: contains control characters.
What it means
Thrown by validateKeyPath() in ssh-connection when a truthy SSH key path matches _CONTROL_CHAR_RE (/[\x00-\x1f\x7f]/). The key path becomes an ssh -i argument, so control bytes are rejected as a command-corruption and injection hazard. An empty/falsy keyPath is allowed (validation is skipped).
Source
Thrown at apps/desktop/electron/ssh-connection.ts:81
if (user && user.startsWith('-')) {
throw new Error(`Unsafe SSH target: user must not start with a dash ("${user}").`)
}
const p = Number(port)
if (!Number.isInteger(p) || p < 1 || p > 65535) {
throw new Error(`Unsafe SSH port: ${port} (must be 1-65535).`)
}
}
function validateKeyPath(keyPath) {
if (!keyPath) {
return
}
if (_CONTROL_CHAR_RE.test(keyPath)) {
throw new Error('Unsafe SSH key path: contains control characters.')
}
if (keyPath.startsWith('-')) {
throw new Error(`Unsafe SSH key path: must not start with a dash ("${keyPath}").`)
}
}
// Token / secret redaction
const _REDACTIONS: Array<[RegExp, string]> = [
[/(HERMES_DASHBOARD_SESSION_TOKEN=)(\S+)/g, '$1<redacted>'],
[/(X-Hermes-Session-Token["']?\s*[:=]\s*["']?)([^\s"'&]+)/gi, '$1<redacted>'],
[/(Authorization["']?\s*:\s*Bearer\s+)(\S+)/gi, '$1<redacted>'],
[/([?&](?:token|ticket)=)([^\s&"']+)/gi, '$1<redacted>']
]
function redactSecrets(text) {
let out = String(text == null ? '' : text)View on GitHub (pinned to c896c09c42)
Solutions
- Sanitize the path: strip /[\x00-\x1f\x7f]/ and trim before passing it in.
- Re-type the path manually; hex-inspect the stored config to locate the offending byte.
- Normalize config line endings to LF so \r never lands inside stored values.
Example fix
// before
new SshConnection({ host, keyPath: rawKeyPath })
// after
const keyPath = rawKeyPath.replace(/[\x00-\x1f\x7f]/g, '').trim()
new SshConnection({ host, keyPath }) Defensive patterns
Strategy: validation
Validate before calling
const CONTROL = /[\x00-\x1f\x7f]/
if (keyPath && (typeof keyPath !== 'string' || CONTROL.test(keyPath))) {
rejectConfig('SSH key path contains control characters')
} Type guard
function isControlFreeKeyPath(k: unknown): k is string {
return typeof k === 'string' && !/[\x00-\x1f\x7f]/.test(k)
} Try / catch
try {
validateKeyPath(keyPath)
} catch (e) {
if (e instanceof Error && e.message === 'Unsafe SSH key path: contains control characters.') {
keyPath = keyPath.replace(/[\x00-\x1f\x7f]/g, '').trim()
validateKeyPath(keyPath)
} else throw e
} Prevention
- Store key paths with LF-only line endings in config files to avoid stray \r.
- Re-type or sanitize pasted paths; terminal escape sequences are invisible in the UI.
- Prefer absolute key paths chosen via a file picker over free-text entry.
When it happens
Trigger: cfg.keyPath containing tab, newline, escape, NUL, or DEL — pasted paths with invisible characters, config corruption, or a path stored with a trailing carriage return.
Common situations: Path pasted from a terminal that included a line-wrap escape sequence; CRLF line endings in a config file leaving \r at the end of the stored value.
Related errors
- Remote path must not be empty.
- Unsafe SSH target: host must not start with a dash ("${host}
- Unsafe SSH target: host contains control characters.
- Unsafe SSH target: user contains control characters.
- Unsafe SSH target: user must not start with a dash ("${user}
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/4699647c4c635f4b.
Report an issue: GitHub.