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

  1. Ensure the target path is relative to and inside the hook's root directory
  2. Remove `..` segments or leading absolute paths from the configured path
  3. If the file legitimately lives elsewhere, move it into the project root or update rootDir
  4. 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

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


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)