affaan-m/ECC · error · Error

Path traversal rejected

Error message

Path traversal rejected: ${relPath}

What it means

Identical traversal guard to observe-runner.js but in scripts/hooks/plugin-hook-bootstrap.js: it resolves the requested path against the plugin root and throws if the resolved target is neither the root itself nor a descendant. Prevents bootstrap logic from being pointed at files outside the plugin directory.

Solutions

  1. Place the referenced script/file inside the plugin root and use a root-relative path
  2. Remove `..` segments or absolute paths from the hook configuration
  3. Update the plugin's rootDir if the legitimate root changed
  4. Check where symlinks resolve; the canonical path must stay under rootDir

Example fix

// before
resolveTarget(pluginRoot, '../bin/helper.js')
// after
resolveTarget(pluginRoot, 'bin/helper.js')
Defensive patterns

Strategy: validation

Validate before calling

const path = require('path');
function isInsidePluginRoot(rootDir, relPath) {
  const root = path.resolve(rootDir);
  const target = path.resolve(rootDir, relPath);
  return target === root || target.startsWith(root + path.sep);
}
// guard scriptPath/result before calling resolveTarget

Type guard

const isSafePluginPath = (p) => typeof p === 'string' && !path.isAbsolute(p) && !p.split(path.sep).includes('..');

Try / catch

try {
  const resolved = resolveTarget(pluginRoot, scriptPath);
} catch (err) {
  if (String(err.message).startsWith('Path traversal rejected:')) {
    console.error(`Hook script outside plugin root: ${scriptPath}`);
    return null;
  }
  throw err;
}

Prevention

When it happens

Trigger: A hook command or configured scriptPath containing `../` segments or an absolute path outside the plugin root; symlinked script locations that canonicalize outside the root.

Common situations: Copying hook configs between machines with different layouts, referencing a globally installed script path, nested plugins so relative paths resolve unexpectedly.

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/73c7eb6dc896869f. Report an issue: GitHub.

Appendix: source

Thrown at scripts/hooks/plugin-hook-bootstrap.js:102

  }

  const match = rootDir.match(/^\/([a-zA-Z])(?:\/(.*))?$/);
  if (!match) {
    return rootDir;
  }

  const [, driveLetter, rest = ''] = match;
  return `${driveLetter.toUpperCase()}:/${rest}`;
}

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;
}

let _cachedShell = undefined;
let _cachedBash = undefined;

function isPowerShellBin(bin) {
  const base = path.basename(bin).toLowerCase();
  return base === 'pwsh.exe' || base === 'pwsh' || base === 'powershell.exe' || base === 'powershell';
}

function findShellBinary() {
  if (_cachedShell !== undefined) return _cachedShell;

  const candidates = [];

  // Explicit override always wins — check before any platform probing.

View on GitHub (pinned to 8321021c54)