{"record":{"id":"b18e6a1c1da9111d","repo":"NousResearch/hermes-agent","slug":"unsafe-ssh-target-host-must-not-start-with-a-dash","errorCode":null,"errorMessage":"Unsafe SSH target: host must not start with a dash (\"${host}\").","messagePattern":"Unsafe SSH target: host must not start with a dash \\(\"(.+?)\"\\)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/ssh-connection.ts","lineNumber":53,"sourceCode":"import net from 'node:net'\nimport os from 'node:os'\nimport path from 'node:path'\n\nconst DEFAULT_CONNECT_TIMEOUT_MS = 15_000\nconst DEFAULT_EXEC_TIMEOUT_MS = 20_000\nconst DEFAULT_FORWARD_TIMEOUT_MS = 15_000\nconst CONTROL_PERSIST_SECONDS = 300\n\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).`)","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/electron/ssh-connection.ts#L35-L71","documentation":"Thrown by validateSshTarget() when the SSH host begins with a dash. A leading dash makes argument parsers treat the value as an option flag once placed on the ssh command line, so it is rejected as an argument-injection hazard before any ssh invocation is built.","triggerScenarios":"host values like '-oProxyCommand=...', '-v', or any string starting with '-'. These arise from malformed user input, a mis-parse of a combined user@host string, or a deliberately crafted config value.","commonSituations":"User pastes an ssh option into the host field; a config editor stores a whole ssh command line in the host field; attacker-controlled config attempting option injection through the host slot.","solutions":["Correct the host to a plain hostname or IP (no leading dash).","Validate at the settings UI: reject host fields starting with '-' before save.","If the user intended ssh options, use the supported dedicated fields (port, keyPath, user), never the host string."],"exampleFix":"// before\nnew SshConnection({ host: '-oProxyCommand=evil', ... })\n\n// after\nnew SshConnection({ host: 'example.com', port: 22, ... })","handlingStrategy":"validation","validationCode":"if (typeof host !== 'string' || host.length === 0 || host.startsWith('-')) {\n  rejectConfig('SSH host must be a plain hostname and must not start with a dash')\n}","typeGuard":"function isDashSafe(value: unknown): value is string {\n  return typeof value === 'string' && value.length > 0 && !value.startsWith('-')\n}","tryCatchPattern":"try {\n  validateSshTarget(host, user, port)\n} catch (e) {\n  if (e instanceof Error && e.message.includes('host must not start with a dash')) {\n    // not sanitizable: reject the config entry and ask for a corrected host\n    invalidateRemoteConfig('host looks like an ssh option, not a hostname')\n    return\n  }\n  throw e\n}","preventionTips":["Reject leading-dash values in host/user/keyPath at the config UI layer.","Expose supported ssh options as dedicated fields (port, keyPath) instead of letting users paste flags.","Treat a leading-dash host in stored config as evidence of tampering and audit the config source."],"tags":["ssh","security","argument-injection","validation"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}