{"record":{"id":"bf209940ce3ef815","repo":"NousResearch/hermes-agent","slug":"unsafe-ssh-target-user-contains-control-character","errorCode":null,"errorMessage":"Unsafe SSH target: user contains control characters.","messagePattern":"Unsafe SSH target: user contains control characters\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/ssh-connection.ts","lineNumber":61,"sourceCode":"\n// eslint-disable-next-line no-control-regex -- deliberately reject control chars in ssh targets\nconst _CONTROL_CHAR_RE = /[\\x00-\\x1f\\x7f]/\n\nfunction validateSshTarget(host, user, port) {\n  if (!host || typeof host !== 'string') {\n    throw new Error('Unsafe SSH target: host is required.')\n  }\n\n  if (host.startsWith('-')) {\n    throw new Error(`Unsafe SSH target: host must not start with a dash (\"${host}\").`)\n  }\n\n  if (_CONTROL_CHAR_RE.test(host)) {\n    throw new Error('Unsafe SSH target: host contains control characters.')\n  }\n\n  if (user && _CONTROL_CHAR_RE.test(user)) {\n    throw new Error('Unsafe SSH target: user contains control characters.')\n  }\n\n  if (user && user.startsWith('-')) {\n    throw new Error(`Unsafe SSH target: user must not start with a dash (\"${user}\").`)\n  }\n\n  const p = Number(port)\n\n  if (!Number.isInteger(p) || p < 1 || p > 65535) {\n    throw new Error(`Unsafe SSH port: ${port} (must be 1-65535).`)\n  }\n}\n\nfunction validateKeyPath(keyPath) {\n  if (!keyPath) {\n    return\n  }\n","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/electron/ssh-connection.ts#L43-L79","documentation":"Thrown by validateSshTarget() when the optional user field contains control characters (same _CONTROL_CHAR_RE class as the host). The user is interpolated into ssh command lines (user@host), so control bytes there are a command-corruption and injection hazard.","triggerScenarios":"A truthy cfg.user matching /[\\x00-\\x1f\\x7f]/ — pasted usernames with invisible characters, or values sourced from a corrupted or crafted config.","commonSituations":"Paste from a source with hidden formatting characters; a username auto-filled from an ssh URL whose percent-encoding decoded to a control byte.","solutions":["Sanitize or re-type the username; strip the control-character class before constructing the connection.","Inspect the saved remote config for embedded control bytes and fix the entry.","Add UI-level validation mirroring /^[^\\x00-\\x1f\\x7f]*$/ for the user field."],"exampleFix":"// before\nnew SshConnection({ host, user: rawUser, port })\n\n// after\nconst user = rawUser.replace(/[\\x00-\\x1f\\x7f]/g, '').trim()\nnew SshConnection({ host, user, port })","handlingStrategy":"validation","validationCode":"const CONTROL = /[\\x00-\\x1f\\x7f]/\nif (user && (typeof user !== 'string' || CONTROL.test(user))) {\n  rejectConfig('SSH user contains control characters')\n}","typeGuard":"function isControlFreeUser(u: unknown): u is string {\n  return typeof u === 'string' && !/[\\x00-\\x1f\\x7f]/.test(u)\n}","tryCatchPattern":"try {\n  validateSshTarget(host, user, port)\n} catch (e) {\n  if (e instanceof Error && e.message === 'Unsafe SSH target: user contains control characters.') {\n    user = String(user).replace(/[\\x00-\\x1f\\x7f]/g, '').trim()\n    if (!user) { user = '' } // empty user is allowed by the validator\n    validateSshTarget(host, user, port)\n  } else throw e\n}","preventionTips":["Trim and strip control bytes from usernames derived from pasted 'user@host' strings.","Decode URL-encoded usernames carefully — some encodings decode to control bytes.","Apply the shared control-character validation in the settings form, not only at connect time."],"tags":["ssh","security","validation","sanitization"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}