{"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":"Error","httpStatus":null,"severity":"critical","filePath":"scripts/lib/install/apply.js","lineNumber":215,"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 buildResolvedClaudeHooks(plan) {\n  if (!plan.adapter || (plan.adapter.target !== 'claude' && plan.adapter.target !== 'claude-project')) {\n    return null;\n  }\n\n  const pluginRoot = plan.targetRoot;","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/install/apply.js#L197-L233","documentation":"Thrown by assertSafeInstallOperation in scripts/lib/install/apply.js during the segment walk. After asserting the destination is contained within targetRoot via assertWithinTrustedRoot, the guard walks every path segment from targetRoot down to the destination and lstatSyncs each. If any segment is a symbolic link, the install aborts. This blocks symlink-escape attacks where a directory under the trusted root points elsewhere on disk; it also narrows (but cannot fully eliminate) the TOCTOU window around mkdirSync, as noted in the source comment.","triggerScenarios":"Any component of the resolved destination path — from targetRoot down — is a symbolic link at the moment of the check. E.g. ~/.claude/skills -> ~/Dropbox/skills, or a compromised project where .cursor/skills is symlinked to /etc.","commonSituations":"Users who sync ~/.claude across machines via Dropbox/iCloud (which create symlinks); monorepo workspaces where the target dir is a symlinked shared path; dotfile managers that symlink ~/.claude; a maliciously crafted cloned repo (security-relevant, GHSA-class).","solutions":["Locate the symlink with find <targetRoot> -type l -ls and inspect each result.","Replace the symlink with a real directory: copy contents into place, then remove the symlink.","Re-run the install. If you need cross-machine sync, sync the directory contents rather than the directory itself.","On macOS also check ~/Library/Application Support for app-managed symlinks."],"exampleFix":"# before\n~/.claude/skills -> /Users/me/Dropbox/skills\n\n# after\nrm ~/.claude/skills\nmkdir ~/.claude/skills\nrsync -a /Users/me/Dropbox/skills/ ~/.claude/skills/","handlingStrategy":"validation","validationCode":"function assertNoSymlinksUnder(rootDir) {\n  const stack = [rootDir];\n  while (stack.length) {\n    const cur = stack.pop();\n    let s;\n    try { s = fs.lstatSync(cur); } catch (e) { if (e.code === 'ENOENT') continue; throw e; }\n    if (s.isSymbolicLink()) throw new Error(`Symlink in install path: ${cur}`);\n    if (s.isDirectory()) {\n      for (const entry of fs.readdirSync(cur)) stack.push(path.join(cur, entry));\n    }\n  }\n}\nassertNoSymlinksUnder(targetRoot);","typeGuard":null,"tryCatchPattern":"try {\n  applyInstallPlan(plan);\n} catch (err) {\n  if (/through symlinked path/.test(err.message)) {\n    console.error('Remove symlinks under', plan.targetRoot, 'then retry.');\n  }\n  throw err;\n}","preventionTips":["Keep ECC install targets (e.g. ~/.claude) as real directories, not symlinks.","Audit symlinks before install: find <root> -type l -ls.","Do not clone untrusted repos and install into them without inspection.","Prefer content-syncing (rsync) over directory-symlinking for dotfile management."],"tags":["security","symlink","install-safety","toctou"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}