affaan-m/ECC · error · Error
Path traversal rejected
Error message
Path traversal rejected: ${relPath} What it means
resolveTarget in scripts/hooks/observe-runner.js resolves a relative path against a root directory and rejects any target that escapes the root. This is a security guard preventing path traversal (e.g. `../`) from making the hook act on files outside the intended directory.
Solutions
- Ensure the target path is relative to and inside the hook's root directory
- Remove `..` segments or leading absolute paths from the configured path
- If the file legitimately lives elsewhere, move it into the project root or update rootDir
- Verify symlink resolution — the canonical location must be inside root
Example fix
// before resolveTarget(projectRoot, '../../shared/config.json') // after resolveTarget(projectRoot, 'config/shared.json')
Defensive patterns
Strategy: validation
Validate before calling
const path = require('path');
function isInsideRoot(rootDir, relPath) {
const root = path.resolve(rootDir);
const target = path.resolve(rootDir, relPath);
return target === root || target.startsWith(root + path.sep);
}
// call resolveTarget only if isInsideRoot returns true Type guard
const isSafeRelPath = (p) => typeof p === 'string' && p.length > 0 && !p.includes('..') && !path.isAbsolute(p); Try / catch
try {
const target = resolveTarget(rootDir, relPath);
} catch (err) {
if (String(err.message).startsWith('Path traversal rejected:')) {
console.error(`Refusing out-of-root path: ${relPath}`);
return null;
}
throw err;
} Prevention
- Store root-relative paths only in hook configuration
- Never forward raw user input as a file path to hooks
- Re-check paths after symlinks change; resolve() canonicalizes
- Add unit tests asserting traversal attempts throw
When it happens
Trigger: Passing a relPath like `../../etc/passwd`, an absolute path outside rootDir, or a path whose canonical (symlink/..-resolved) form lands outside the root.
Common situations: Hook configuration referencing files outside the project, user-supplied paths in hook payloads, symlinks resolving outside the project after path.resolve canonicalization.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- Path traversal rejected
- artifact path escapes output directory
- File path contains unsafe shell characters
- Invalid ECC repo root: missing package.json at
- output path contains an invalid component
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/7b98421b4bb7582a.
Report an issue: GitHub.
Appendix: 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 8321021c54)