NousResearch/hermes-agent · error

${caller}: illegal path traversal in "${path}"

Error message

${caller}: illegal path traversal in "${path}"

What it means

Thrown by pluginPathSuffix() in apps/desktop/src/hermes.ts:285 when a `path` passed to the plugin REST door contains a `..` segment. pluginRest() scopes every request by construction to `/api/plugins/<pluginId>`; the `..` rejection is what prevents a relative path from normalizing up into another plugin's API namespace or a core route. Only the path portion (before any `?query` or `#hash`) is checked.

Source

Thrown at apps/desktop/src/hermes.ts:285

/** Options for a plugin REST call — mirrors the app's own `hermesDesktop.api`
 *  shape, minus the path (which is namespace-derived). */
export interface PluginRestOptions {
  method?: string
  body?: unknown
  /** Single-file multipart upload (see HermesApiRequest.upload). */
  upload?: { filename: string; contentType?: string; bytes: ArrayBuffer }
  timeoutMs?: number
}

// Normalize `path` to a leading-slash suffix relative to `/api/plugins/<id>`.
// The namespace is the boundary — reject `..` so a relative segment can't
// normalize out into another plugin's API or a core route. Check the path
// portion only (before any query/hash).
function pluginPathSuffix(caller: string, path: string): string {
  const suffix = path.startsWith('/') ? path : `/${path}`

  if (suffix.split(/[?#]/, 1)[0].split('/').includes('..')) {
    throw new Error(`${caller}: illegal path traversal in "${path}"`)
  }

  return suffix
}

/** The plugin REST door. Every call is scoped BY CONSTRUCTION to the plugin's
 *  own backend namespace — `path` is relative to `/api/plugins/<pluginId>`
 *  ('/board' → `/api/plugins/kanban/board`), so a plugin can't address another
 *  plugin's API or a core route through it. Profile-aware like every desktop
 *  REST call. Broader reach (core endpoints, another namespace) is the future
 *  declared-capability seam; today the namespace IS the boundary. */
export async function pluginRest<T>(pluginId: string, path: string, opts: PluginRestOptions = {}): Promise<T> {
  if (!window.hermesDesktop?.api) {
    throw new Error('Hermes desktop bridge unavailable')
  }

  const suffix = pluginPathSuffix('pluginRest', path)

View on GitHub (pinned to c896c09c42)

Solutions

  1. Keep pluginRest paths strictly inside the plugin's own namespace and always start them with '/' (e.g. '/board', '/items/42').
  2. Sanitize user/agent-supplied segments before interpolation: reject or encodeURIComponent anything containing '..' as a segment.
  3. If you genuinely need another plugin's API or a core route, do not use pluginRest — call the appropriate dedicated API helper; the namespace IS the boundary.
  4. Strip traversal at the source: normalize the path first and assert no '..' segment remains before calling.

Example fix

// before
await pluginRest('kanban', `../settings/all`)
await pluginRest('kanban', `/board/${userInput}`) // userInput = '../../x'

// after
await hermesApi('/api/settings/all')          // core route via its own helper
await pluginRest('kanban', `/board/${encodeURIComponent(userInput)}`)
Defensive patterns

Strategy: validation

Validate before calling

function safePluginPath(path: string): string {
  const suffix = path.startsWith('/') ? path : `/${path}`
  const pathOnly = suffix.split(/[?#]/, 1)[0]
  if (pathOnly.split('/').includes('..')) throw new Error(`illegal path: ${path}`)
  return suffix
}
const clean = `/items/${encodeURIComponent(userSuppliedId)}`
safePluginPath(clean)

Try / catch

try { await pluginRest(id, path) } catch (e) { if (e instanceof Error && e.message.includes('illegal path traversal')) { /* fix path construction; do NOT strip '..' blindly and retry */ } }

Prevention

When it happens

Trigger: Calling pluginRest(pluginId, '../other-plugin/board'); building a path by concatenation that yields '/a/../b'; passing a user-supplied relative path like 'items/../../admin' into a plugin REST helper; `..` appearing as a whole segment (`'a/..b'` is fine, `'a/..'` is not).

Common situations: A plugin naively joining ids into a URL: `${base}/${id}` where id contains traversal; UI code forwarding a typed route that includes parent-directory shorthand; attempting to reach a sibling plugin's endpoint or a core endpoint through the namespaced door.

Related errors


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