{"record":{"id":"83a75456af74fca1","repo":"affaan-m/ECC","slug":"refusing-to-install-ecc-file-through-symlinked-pat","errorCode":null,"errorMessage":"Refusing to install ECC file through symlinked path: '${currentPath}'.","messagePattern":"Refusing to install ECC file through symlinked path: '(.+?)'\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install/apply.js","lineNumber":266,"sourceCode":"  if (!operation || typeof operation.destinationPath !== 'string') {\n    throw new Error('Refusing to apply install operation: missing destination path.');\n  }\n\n  const targetRoot = plan && plan.targetRoot;\n  assertWithinTrustedRoot(operation.destinationPath, targetRoot, 'install ECC file');\n\n  const resolvedRoot = path.resolve(targetRoot);\n  const resolvedTarget = path.resolve(operation.destinationPath);\n  const relativePath = path.relative(resolvedRoot, resolvedTarget);\n  const segments = relativePath ? relativePath.split(path.sep) : [];\n  for (const segmentIndex of Array.from({ length: segments.length + 1 }, (_value, index) => index)) {\n    const currentPath = segmentIndex === 0\n      ? resolvedRoot\n      : path.join(resolvedRoot, ...segments.slice(0, segmentIndex));\n    try {\n      const stats = fs.lstatSync(currentPath);\n      if (stats.isSymbolicLink()) {\n        throw new Error(\n          `Refusing to install ECC file through symlinked path: '${currentPath}'.`\n        );\n      }\n    } catch (error) {\n      if (error && error.code === 'ENOENT') {\n        break;\n      }\n      throw error;\n    }\n  }\n}\n\nfunction readPreviousInstallState(plan) {\n  if (!fs.existsSync(plan.installStatePath)) {\n    return null;\n  }\n  return readInstallState(plan.installStatePath);\n}","sourceCodeStart":248,"sourceCodeEnd":284,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install/apply.js#L248-L284","documentation":"As part of assertSafeInstallOperation, the installer walks each path segment from the resolved target root and lstat-checks it; if any existing segment is a symbolic link, installing through it is refused. This prevents an attacker (or a misconfigured environment) from redirecting ECC file writes outside the trusted target root via a symlinked directory or file. The error names the offending path prefix.","triggerScenarios":"Any component of destinationPath between the target root and the file itself is a symlink — e.g. ~/.claude/agents is a symlink to a dotfiles repo, or a subdirectory inside the target root was replaced with a link; applying a plan whose destination traverses such a segment.","commonSituations":"Users who symlink ~/.claude (or subfolders) into a version-controlled dotfiles directory; target root inside a symlinked project path; a previous tool replaced a directory with a link.","solutions":["Replace the symlinked segment with a real directory (e.g. remove the link, mkdir the real directory) or point targetRoot at the actual directory.","Install directly into the physical location the symlink points to, passing that as targetRoot.","Use a non-symlinked install target (or the target's native mechanism) for dotfiles management.","Find the offending segment with 'namei -l <destinationPath>' or 'ls -la' along the path and unlink it."],"exampleFix":"# before: ~/.claude/agents -> ~/dotfiles/agents\nrm ~/.claude/agents && mkdir ~/.claude/agents\n# or install into the real location:\nplanInstallTargetScaffold({ targetRoot: os.path.expanduser('~/dotfiles/claude') })","handlingStrategy":"validation","validationCode":"const fs = require('fs');\nconst path = require('path');\nfunction hasSymlinkSegment(dest, root) {\n  const rel = path.relative(path.resolve(root), path.resolve(dest));\n  let cur = path.resolve(root);\n  for (const seg of rel.split(path.sep)) {\n    cur = path.join(cur, seg);\n    try { if (fs.lstatSync(cur).isSymbolicLink()) return cur; } catch { /* ENOENT ok */ }\n  }\n  return null;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await applyInstallPlan(plan);\n} catch (e) {\n  if (e.message.includes('symlinked path')) {\n    const p = e.message.match(/'(.+)'/)?.[1];\n    console.error(`Replace symlink ${p} with a real directory, or set targetRoot to the physical path.`);\n  }\n  throw e;\n}","preventionTips":["Avoid symlinked ~/.claude subdirectories; use the target's native mechanisms (junctions on Windows, mounts, or install into the real path).","Point targetRoot at the physical directory rather than a path that traverses links.","Audit the install path with 'namei -l' or lstat checks before running the installer in dotfiles-managed environments."],"tags":["filesystem","symlink","security"],"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"}