{"record":{"id":"290629fa19bde874","repo":"NousResearch/hermes-agent","slug":"unsafe-remote-path-contains-nul-or-newline","errorCode":null,"errorMessage":"Unsafe remote path: contains NUL or newline.","messagePattern":"Unsafe remote path: contains NUL or newline\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/remote-lifecycle.ts","lineNumber":106,"sourceCode":"function spawnLogPath(ownershipId, spawnNonce) {\n  return `${ownershipDirectory(ownershipId)}/${validateSpawnNonce(spawnNonce)}.log`\n}\n\n// shell-single-quote a value for safe interpolation into a remote command.\nfunction shq(value) {\n  return `'${String(value).replace(/'/g, `'\\\\''`)}'`\n}\n\nfunction validateRemotePath(p) {\n  const s = String(p || '')\n\n  if (!s) {\n    throw new Error('Remote path must not be empty.')\n  }\n\n  // eslint-disable-next-line no-control-regex -- deliberately reject NUL in remote paths\n  if (/[\\x00\\n\\r]/.test(s)) {\n    throw new Error('Unsafe remote path: contains NUL or newline.')\n  }\n\n  if (s === '~' || s.startsWith('~/') || s.startsWith('/')) {\n    return\n  }\n\n  throw new Error(`Remote path must be absolute or start with ~/: \"${s}\"`)\n}\n\nfunction expandRemotePath(p) {\n  validateRemotePath(p)\n\n  if (p === '~') {\n    return '\"$HOME\"'\n  }\n\n  if (p.startsWith('~/')) {\n    return '\"$HOME\"' + shq(p.slice(1))","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/electron/remote-lifecycle.ts#L88-L124","documentation":"Thrown by validateRemotePath() when a remote path matches /[\\x00\\n\\r]/ — NUL or newline characters. Paths are shell-interpolated (expandRemotePath builds '\"$HOME\"' + shq(...) fragments), so these characters could terminate or split the remote command; the path is treated as unsafe injection material, not merely malformed.","triggerScenarios":"Passing a path containing NUL, \\n, or \\r to expandRemotePath() — a value read from a corrupted config file, a copy-pasted path with a trailing line break, or programmatic concatenation that introduced a newline.","commonSituations":"Copy-pasting a path from a chat/doc that includes a trailing newline; a config file with CRLF line endings leaving \\r inside the stored value; binary corruption of the config; malicious input attempting command injection through the remote path field.","solutions":["Strip /[\\x00\\n\\r]/ and trim user-supplied paths before validation (note: trim alone does not remove NUL).","Reject or sanitize at the UI/config layer and warn the user when the input contains invisible characters.","If the stored value legitimately contains newlines it is not a path — fix the upstream producer that corrupted it."],"exampleFix":"// before\nconst expanded = expandRemotePath(rawInput)\n\n// after\nconst cleaned = rawInput.replace(/[\\x00\\n\\r]/g, '').trim()\nif (!cleaned) throw new TypeError('path input was empty after sanitizing')\nconst expanded = expandRemotePath(cleaned)","handlingStrategy":"validation","validationCode":"function isSafeRemotePathCandidate(s: string): boolean {\n  return typeof s === 'string' && !/[\\x00\\n\\r]/.test(s)\n}\n\nconst candidate = String(rawPath ?? '')\nif (!isSafeRemotePathCandidate(candidate)) {\n  rejectInput('path contains control characters (NUL/newline)')\n}","typeGuard":"function isCleanRemotePath(p: unknown): p is string {\n  return typeof p === 'string' && !/[\\x00\\n\\r]/.test(p) && p.length > 0\n}","tryCatchPattern":"try {\n  cmd += expandRemotePath(p)\n} catch (e) {\n  if (e instanceof Error && e.message === 'Unsafe remote path: contains NUL or newline.') {\n    throw new UserInputError(`remote path contains forbidden characters: ${JSON.stringify(p)}`)\n  }\n  throw e\n}","preventionTips":["Strip /[\\x00\\n\\r]/ from all user-supplied remote paths at input time.","Trim pasted input; treat a path that is empty after trimming as missing.","If the value was not user-entered, treat this as a potential injection attempt and audit its source."],"tags":["security","command-injection","validation","remote"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}