affaan-m/ECC · error · Error

Refusing to ${action} outside the install root: '${target}'

Error message

Refusing to ${action} outside the install root: '${target}' is not within '${root}'.

What it means

Thrown by assertWithinTrustedRoot when the target, after realpath canonicalization, does not resolve to within the trusted root. This is the core path-traversal / symlink-escape defense: it defeats ../ sequences and symlinks that point out of the install root, blocking writes outside the trusted tree.

Source

Thrown at scripts/lib/path-safety.js:100

 * Fail-closed guard: throw unless `target` is contained within `root`.
 * Returns the canonicalized target path on success.
 */
function assertWithinTrustedRoot(target, root, action = 'write') {
  if (!target || typeof target !== 'string') {
    throw new Error(`Refusing to ${action}: missing destination path.`);
  }
  if (!root) {
    throw new Error(`Refusing to ${action} '${target}': no trusted install root resolved.`);
  }

  let containment;
  try {
    containment = resolveContainment(target, root);
  } catch {
    containment = null;
  }
  if (!containment || !containment.contained) {
    throw new Error(`Refusing to ${action} outside the install root: '${target}' is not within '${root}'.`);
  }
  return containment.realTarget;
}

module.exports = {
  realpathNearestExisting,
  isWithinRoot,
  assertWithinTrustedRoot
};

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Confirm the install-state file belongs to the current project root before replaying it.
  2. Use isWithinRoot(target, root) to filter state entries, skipping any that escape rather than throwing.
  3. If a legitimate destination moved, re-run install to regenerate the state file against the current root.
  4. Audit the state file for absolute paths or ../ segments and regenerate it if any are present.

Example fix

// before
for (const op of state.ops) {
  assertWithinTrustedRoot(op.destinationPath, root, 'write'); // throws on escape
}

// after
for (const op of state.ops) {
  if (!isWithinRoot(op.destinationPath, root)) {
    console.warn('Skipping out-of-root destination:', op.destinationPath);
    continue;
  }
  assertWithinTrustedRoot(op.destinationPath, root, 'write');
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (!isWithinRoot(target, root)) {
  console.warn('Skipping out-of-root path:', target);
  continue;
}
assertWithinTrustedRoot(target, root, action);

Type guard

const { isWithinRoot } = require('./path-safety');
// isWithinRoot returns boolean (false on any error) instead of throwing.

Prevention

When it happens

Trigger: A target containing '..' that escapes the root; a symlink inside the install tree pointing to a directory outside it; a recorded destinationPath that was captured under a different root than the current trusted root; an absolute path to an unrelated location.

Common situations: A cloned/forked repo ships a crafted .cursor/ecc-install-state.json with destinations pointing outside the project; a symlink in ~/.claude pointing elsewhere; a repair replay after the project moved; an uninstall whose recorded paths predate a root change.

Related errors


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