stablyai/orca · critical

[verify-packaged-plugin-resources] bundled plugin path escap

Error message

[verify-packaged-plugin-resources] bundled plugin path escapes launch root

What it means

Thrown by the packaged plugin resource verifier when a plugin entry's path field, resolved against the launch root, escapes the launch directory. The check uses path.relative and path.isAbsolute to detect traversal: if the relative path is empty, equals '..', starts with '../', or is absolute, the plugin path is considered an escape attempt. This is a security guard against path traversal in the plugin index.

Source

Thrown at config/scripts/verify-packaged-plugin-resources.cjs:92

  }
  const index = readJsonFile(join(launchRoot, 'bundled-plugins.json'), 'bundled plugin index')
  readJsonFile(join(launchRoot, 'orca-marketplace.json'), 'marketplace index')
  if (index?.version !== 1 || !Array.isArray(index.plugins) || index.plugins.length === 0) {
    throw new Error('[verify-packaged-plugin-resources] bundled plugin index is empty or invalid')
  }
  const resolvedRoot = resolve(launchRoot)
  for (const entry of index.plugins) {
    if (
      typeof entry?.pluginKey !== 'string' ||
      typeof entry.path !== 'string' ||
      !/^[0-9a-f]{64}$/.test(entry.contentHash)
    ) {
      throw new Error('[verify-packaged-plugin-resources] bundled plugin entry is invalid')
    }
    const pluginRoot = resolve(launchRoot, entry.path)
    const fromRoot = relative(resolvedRoot, pluginRoot)
    if (!fromRoot || fromRoot === '..' || fromRoot.startsWith(`..${sep}`) || isAbsolute(fromRoot)) {
      throw new Error('[verify-packaged-plugin-resources] bundled plugin path escapes launch root')
    }
    const manifest = readJsonFile(join(pluginRoot, 'orca-plugin.json'), 'plugin manifest')
    if (`${manifest.publisher}.${manifest.id}` !== entry.pluginKey) {
      throw new Error(
        `[verify-packaged-plugin-resources] manifest identity does not match ${entry.pluginKey}`
      )
    }
    if (hashPackagedPluginTree(pluginRoot) !== entry.contentHash) {
      throw new Error(
        `[verify-packaged-plugin-resources] packaged bytes do not match ${entry.pluginKey}`
      )
    }
  }
  console.log(
    `[verify-packaged-plugin-resources] OK — verified ${index.plugins.length} bundled plugin(s)`
  )
}

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Inspect the path field of the offending entry in bundled-plugins.json — it must be a relative path within the launch directory (e.g., 'my-plugin/', './my-plugin').
  2. Fix the indexing script to only emit relative paths that stay within the launch root.
  3. If the path looks correct but still triggers, check for symlink resolution issues: resolve() may follow symlinks to locations outside the root.
  4. Add a sanitization step in the indexing pipeline that validates paths with the same relative/absolute check before writing them to the index.
Defensive patterns

Strategy: validation

Validate before calling

// Before writing to the index, validate plugin paths stay within launch root.
const { resolve, relative, isAbsolute, sep } = require('node:path')

function isPathWithinRoot(rootDir, testPath) {
  const resolved = resolve(rootDir, testPath)
  const rel = relative(resolve(rootDir), resolved)
  return (
    !!rel &&
    rel !== '..' &&
    !rel.startsWith(`..${sep}`) &&
    !isAbsolute(rel)
  )
}

Prevention

When it happens

Trigger: An entry in bundled-plugins.json has a path like '../../../etc/passwd', an absolute path like '/usr/local/lib', or an empty path that resolves to the root itself. The resolve(launchRoot, entry.path) call produces a path outside resolvedRoot, and the relative(resolvedRoot, pluginRoot) check catches it.

Common situations: A path traversal attack via a maliciously crafted plugin index; a misconfigured indexing script that writes absolute paths instead of relative ones; a plugin path that was manually edited to point outside the launch directory; a symlink dereference during indexing that resolved to an external path.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/8ca880db3a18c209. Report an issue: GitHub.