{"record":{"id":"de2a0c8fd10c542d","repo":"affaan-m/ECC","slug":"refusing-to-action-outside-the-install-root-de2a0c","errorCode":null,"errorMessage":"Refusing to ${action} outside the install root: '${target}' is not within '${root}'.","messagePattern":"Refusing to (.+?) outside the install root: '(.+?)' is not within '(.+?)'\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/path-safety.js","lineNumber":100,"sourceCode":" * Fail-closed guard: throw unless `target` is contained within `root`.\n * Returns the canonicalized target path on success.\n */\nfunction assertWithinTrustedRoot(target, root, action = 'write') {\n  if (!target || typeof target !== 'string') {\n    throw new Error(`Refusing to ${action}: missing destination path.`);\n  }\n  if (!root) {\n    throw new Error(`Refusing to ${action} '${target}': no trusted install root resolved.`);\n  }\n\n  let containment;\n  try {\n    containment = resolveContainment(target, root);\n  } catch {\n    containment = null;\n  }\n  if (!containment || !containment.contained) {\n    throw new Error(`Refusing to ${action} outside the install root: '${target}' is not within '${root}'.`);\n  }\n  return containment.realTarget;\n}\n\nmodule.exports = {\n  realpathNearestExisting,\n  isWithinRoot,\n  assertWithinTrustedRoot\n};\n","sourceCodeStart":82,"sourceCodeEnd":110,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/path-safety.js#L82-L110","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Confirm the install-state file belongs to the current project root before replaying it.","Use isWithinRoot(target, root) to filter state entries, skipping any that escape rather than throwing.","If a legitimate destination moved, re-run install to regenerate the state file against the current root.","Audit the state file for absolute paths or ../ segments and regenerate it if any are present."],"exampleFix":"// before\nfor (const op of state.ops) {\n  assertWithinTrustedRoot(op.destinationPath, root, 'write'); // throws on escape\n}\n\n// after\nfor (const op of state.ops) {\n  if (!isWithinRoot(op.destinationPath, root)) {\n    console.warn('Skipping out-of-root destination:', op.destinationPath);\n    continue;\n  }\n  assertWithinTrustedRoot(op.destinationPath, root, 'write');\n}","handlingStrategy":"type-guard","validationCode":"if (!isWithinRoot(target, root)) {\n  console.warn('Skipping out-of-root path:', target);\n  continue;\n}\nassertWithinTrustedRoot(target, root, action);","typeGuard":"const { isWithinRoot } = require('./path-safety');\n// isWithinRoot returns boolean (false on any error) instead of throwing.","tryCatchPattern":null,"preventionTips":["Filter install-state entries with isWithinRoot before asserting.","Regenerate the state file if the project root moved.","Audit recorded paths for ../ segments or absolute paths outside the root."],"tags":["security","path-traversal","symlink-escape","install-safety"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}