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
- 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').
- Fix the indexing script to only emit relative paths that stay within the launch root.
- If the path looks correct but still triggers, check for symlink resolution issues: resolve() may follow symlinks to locations outside the root.
- 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
- In the indexing script, validate every plugin path with the same isPathWithinRoot check before writing it to bundled-plugins.json.
- Never construct plugin paths from user input without sanitization; use only relative paths derived from the directory scan.
- Treat path traversal in a plugin index as a security incident — investigate whether the index was tampered with.
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
- plugin exceeds the ${MAX_PLUGIN_FILES}-entry limit
- packaged plugin contains a symlink: ${relative(root, entryPa
- plugin exceeds the ${MAX_PLUGIN_TOTAL_BYTES}-byte limit
- packaged plugin contains an unsupported entry: ${entryPath}
- [verify-packaged-plugin-resources] invalid ${label} at ${pat
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/8ca880db3a18c209.
Report an issue: GitHub.