affaan-m/ECC · error
Path traversal rejected: ${relPath}
Error message
Path traversal rejected: ${relPath} What it means
observe-runner.js resolves a relative path against the plugin root and confirms the resolved target stays inside that root (allowing the root itself). If relPath escapes via '..' segments or an absolute override, the guard throws to block path traversal. This protects the observe.sh hook launcher from being redirected outside the ECC plugin tree.
Source
Thrown at scripts/hooks/observe-runner.js:31
return String(options.pluginRoot).trim();
}
if (process.env.CLAUDE_PLUGIN_ROOT && process.env.CLAUDE_PLUGIN_ROOT.trim()) {
return process.env.CLAUDE_PLUGIN_ROOT.trim();
}
if (process.env.ECC_PLUGIN_ROOT && process.env.ECC_PLUGIN_ROOT.trim()) {
return process.env.ECC_PLUGIN_ROOT.trim();
}
return path.resolve(__dirname, '..', '..');
}
function resolveTarget(rootDir, relPath) {
const resolvedRoot = path.resolve(rootDir);
const resolvedTarget = path.resolve(rootDir, relPath);
if (
resolvedTarget !== resolvedRoot &&
!resolvedTarget.startsWith(resolvedRoot + path.sep)
) {
throw new Error(`Path traversal rejected: ${relPath}`);
}
return resolvedTarget;
}
function toShellPath(filePath) {
const normalized = String(filePath || '');
if (process.platform !== 'win32') {
return normalized;
}
return normalized
.replace(/^([A-Za-z]):[\\/]/, (_, driveLetter) => `/${driveLetter.toLowerCase()}/`)
.replace(/\\/g, '/');
}
function findShellBinary() {
const candidates = [];
if (process.env.BASH && process.env.BASH.trim()) {View on GitHub (pinned to 01e15490f0)
Solutions
- Ensure CLAUDE_PLUGIN_ROOT / ECC_PLUGIN_ROOT point at the real ECC plugin directory
- Do not override the observe.sh relative path used by the hook
- Reinstall ECC if the plugin files were moved or the tree is corrupted
Defensive patterns
Strategy: validation
Validate before calling
const path = require('path');
function assertWithinRoot(rootDir, relPath) {
const root = path.resolve(rootDir);
const target = path.resolve(rootDir, relPath);
if (target !== root && !target.startsWith(root + path.sep)) {
throw new Error(`Path traversal rejected (pre-check): ${relPath}`);
}
return target;
} Type guard
function isPathInsideRoot(rootDir, relPath) {
const root = path.resolve(rootDir);
const target = path.resolve(rootDir, relPath);
return target === root || target.startsWith(root + path.sep);
} Try / catch
try { resolveTarget(root, rel); }
catch (err) {
if (/Path traversal rejected/.test(err.message)) {
console.error(`Refusing to resolve ${rel} outside plugin root ${root}`);
process.exit(126);
}
throw err;
} Prevention
- Never override CLAUDE_PLUGIN_ROOT / ECC_PLUGIN_ROOT to point outside the installed plugin
- Treat any hook input referencing '..' or absolute paths as untrusted
- Reinstall ECC if the plugin tree has been relocated
When it happens
Trigger: A hook payload or environment variable sets the observe.sh relative path to something like ../../etc/passwd, /etc/shadow, or any value whose resolved absolute form is not under the plugin root.
Common situations: CLAUDE_PLUGIN_ROOT or ECC_PLUGIN_ROOT overridden to point outside the installed plugin, a symlink inside the tree that resolves elsewhere, or tampered/malformed hook input from a compromised workspace.
Related errors
- Path traversal rejected: ${relPath}
- TypeScript compiler not found. Install root dev dependencies
- ${source} is missing the expected catalog marker
- Invalid ${flag}: expected a single cache path segment
- File path contains unsafe shell characters
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/7b98421b4bb7582a.
Report an issue: GitHub.