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

  1. Ensure CLAUDE_PLUGIN_ROOT / ECC_PLUGIN_ROOT point at the real ECC plugin directory
  2. Do not override the observe.sh relative path used by the hook
  3. 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

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


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/7b98421b4bb7582a. Report an issue: GitHub.