NousResearch/hermes-agent · error · Error

Unsafe remote path: contains NUL or newline.

Error message

Unsafe remote path: contains NUL or newline.

What it means

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.

Source

Thrown at apps/desktop/electron/remote-lifecycle.ts:106

function spawnLogPath(ownershipId, spawnNonce) {
  return `${ownershipDirectory(ownershipId)}/${validateSpawnNonce(spawnNonce)}.log`
}

// shell-single-quote a value for safe interpolation into a remote command.
function shq(value) {
  return `'${String(value).replace(/'/g, `'\\''`)}'`
}

function validateRemotePath(p) {
  const s = String(p || '')

  if (!s) {
    throw new Error('Remote path must not be empty.')
  }

  // eslint-disable-next-line no-control-regex -- deliberately reject NUL in remote paths
  if (/[\x00\n\r]/.test(s)) {
    throw new Error('Unsafe remote path: contains NUL or newline.')
  }

  if (s === '~' || s.startsWith('~/') || s.startsWith('/')) {
    return
  }

  throw new Error(`Remote path must be absolute or start with ~/: "${s}"`)
}

function expandRemotePath(p) {
  validateRemotePath(p)

  if (p === '~') {
    return '"$HOME"'
  }

  if (p.startsWith('~/')) {
    return '"$HOME"' + shq(p.slice(1))

View on GitHub (pinned to c896c09c42)

Solutions

  1. Strip /[\x00\n\r]/ and trim user-supplied paths before validation (note: trim alone does not remove NUL).
  2. Reject or sanitize at the UI/config layer and warn the user when the input contains invisible characters.
  3. If the stored value legitimately contains newlines it is not a path — fix the upstream producer that corrupted it.

Example fix

// before
const expanded = expandRemotePath(rawInput)

// after
const cleaned = rawInput.replace(/[\x00\n\r]/g, '').trim()
if (!cleaned) throw new TypeError('path input was empty after sanitizing')
const expanded = expandRemotePath(cleaned)
Defensive patterns

Strategy: validation

Validate before calling

function isSafeRemotePathCandidate(s: string): boolean {
  return typeof s === 'string' && !/[\x00\n\r]/.test(s)
}

const candidate = String(rawPath ?? '')
if (!isSafeRemotePathCandidate(candidate)) {
  rejectInput('path contains control characters (NUL/newline)')
}

Type guard

function isCleanRemotePath(p: unknown): p is string {
  return typeof p === 'string' && !/[\x00\n\r]/.test(p) && p.length > 0
}

Try / catch

try {
  cmd += expandRemotePath(p)
} catch (e) {
  if (e instanceof Error && e.message === 'Unsafe remote path: contains NUL or newline.') {
    throw new UserInputError(`remote path contains forbidden characters: ${JSON.stringify(p)}`)
  }
  throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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