{"record":{"id":"d6081183501416b6","repo":"affaan-m/ECC","slug":"refusing-to-action-outside-the-install-root","errorCode":null,"errorMessage":"Refusing to ${action} outside the install root: '${targetPath}' is not within '${targetRoot}'.","messagePattern":"Refusing to (.+?) outside the install root: '(.+?)' is not within '(.+?)'\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install/claude-skill-migration.js","lineNumber":56,"sourceCode":"function comparablePath(filePath) {\n  const resolvedPath = path.resolve(filePath);\n  return process.platform === 'win32' ? resolvedPath.toLowerCase() : resolvedPath;\n}\n\nfunction samePath(leftPath, rightPath) {\n  return comparablePath(leftPath) === comparablePath(rightPath);\n}\n\nfunction assertSafeSkillPath(targetPath, targetRoot, action) {\n  const resolvedRoot = path.resolve(targetRoot);\n  const resolvedTarget = path.resolve(targetPath);\n  const relativePath = path.relative(resolvedRoot, resolvedTarget);\n  if (\n    relativePath === ''\n    || relativePath.startsWith('..')\n    || path.isAbsolute(relativePath)\n  ) {\n    throw new Error(\n      `Refusing to ${action} outside the install root: '${targetPath}' is not within '${targetRoot}'.`\n    );\n  }\n\n  let currentPath = resolvedRoot;\n  for (const segment of relativePath.split(path.sep)) {\n    currentPath = path.join(currentPath, segment);\n    let stats;\n    try {\n      stats = fs.lstatSync(currentPath);\n    } catch (error) {\n      if (error && error.code === 'ENOENT') {\n        break;\n      }\n      throw error;\n    }\n    if (stats.isSymbolicLink()) {\n      throw new Error(","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install/claude-skill-migration.js#L38-L74","documentation":"assertSafeSkillPath resolves the target path and verifies it lies strictly inside the install root (not the root itself, not escaping via .., not absolute after normalization) before any skill file operation. This is a path-traversal guard: migration/removal code must never touch files outside the Claude skills root.","triggerScenarios":"Calling assertSafeClaudeSkillOperation (or the skill migration/cleanup helpers) with a targetPath that resolves outside targetRoot — e.g. containing ../ segments, being a symlink target outside the root, or passing an absolute path in another directory.","commonSituations":"Misconfigured install root (CLAUDE config pointing elsewhere), corrupted stored skill paths containing ../, or passing a user-supplied path from a CLI argument straight into the migration API.","solutions":["Check the targetPath value in the error and correct it so it resolves inside the install root.","Verify the install root configuration points at the actual ~/.claude (or equivalent) skills directory.","If the path comes from stored state (previous install records), delete or regenerate that stale state.","Never bypass by passing raw user input; normalize/resolve relative paths against the correct root first."],"exampleFix":"// before\nawait removeLegacyClaudeSkillFiles(root, '/etc/hosts');\n// after\nawait removeLegacyClaudeSkillFiles(root, path.join(root, 'skills', 'old-skill', 'SKILL.md'));","handlingStrategy":"validation","validationCode":"const path = require('path');\nfunction isInsideRoot(root, target) {\n  const rel = path.relative(path.resolve(root), path.resolve(target));\n  return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);\n}","typeGuard":"const isSafeSkillPath = (root, target) => isInsideRoot(root, target);","tryCatchPattern":"try {\n  await assertSafeClaudeSkillOperation({ action: 'remove', targetPath, targetRoot });\n} catch (error) {\n  if (error.message.includes('outside the install root')) {\n    console.error(`Resolve ${targetPath} under ${targetRoot} before retrying`);\n    return;\n  }\n  throw error;\n}","preventionTips":["Never pass raw user/CLI-supplied paths into skill operations","Resolve relative paths against the install root before calling","Verify install-root configuration points at the real skills directory","Regenerate stale recorded paths from previous installs rather than reusing them"],"tags":["path-traversal","security","filesystem","safety-guard"],"backgroundTag":"path-traversal-blocked","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}