{"record":{"id":"66d56448fc768481","repo":"affaan-m/ECC","slug":"refusing-to-action-destination-parent-is-not-a","errorCode":null,"errorMessage":"Refusing to ${action}: destination parent is not a trusted directory.","messagePattern":"Refusing to (.+?): destination parent is not a trusted directory\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/install-lifecycle.js","lineNumber":297,"sourceCode":"    ? relativeParent.split(path.sep).filter(Boolean)\n    : [];\n  let currentPath = canonicalRoot;\n\n  for (const segment of pathSegments) {\n    const validatedParent = assertWithinTrustedRoot(currentPath, canonicalRoot, action);\n    const nextPath = path.join(validatedParent, segment);\n    try {\n      fs.mkdirSync(nextPath);\n    } catch (error) {\n      if (!error || error.code !== 'EEXIST') {\n        throw error;\n      }\n    }\n\n    const validatedNext = assertWithinTrustedRoot(nextPath, canonicalRoot, action);\n    const nextStat = fs.lstatSync(validatedNext);\n    if (!nextStat.isDirectory() || nextStat.isSymbolicLink()) {\n      throw new Error(`Refusing to ${action}: destination parent is not a trusted directory.`);\n    }\n    currentPath = validatedNext;\n  }\n\n  return getManagedDestination(managedPath, canonicalRoot, action).managedPath;\n}\n\nfunction prepareContainedWriteDestination(destinationPath, trustedRoot, action) {\n  return ensureContainedParentDir(destinationPath, trustedRoot, action);\n}\n\nfunction getContainedExistingPath(\n  destinationPath,\n  trustedRoot,\n  action,\n  { allowFinalSymlink = false } = {}\n) {\n  const initialDestination = getManagedDestination(","sourceCodeStart":279,"sourceCodeEnd":315,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/install-lifecycle.js#L279-L315","documentation":"Thrown by ensureContainedParentDir after mkdir'ing each path segment: the freshly created (or pre-existing) parent is re-stat'd with lstat and rejected if it is not a directory OR is a symbolic link. This is a TOCTOU / symlink-hardening guard so an attacker cannot pre-place a symlink between mkdir and the subsequent write inside the trusted root. The ${action} token reflects the caller's verb (install, repair, uninstall).","triggerScenarios":"Calling any contained-write path (writeContainedFile, copyContainedFile, prepareContainedWriteDestination) where an intermediate directory segment is a symlink, a regular file, a device, or got swapped to a symlink between mkdirSync and lstatSync. Also triggers if the destination's parent chain leaves the trusted root because a segment was replaced.","commonSituations":"A user pre-created ~/.claude/skills as a symlink to a shared drive; running ./install.sh --target claude then fails because the lifecycle refuses to descend through it. Another case: a sync tool (Dropbox, iCloud) replaced an intermediate dir with a symlink. Mis-configured HOME pointing at a path where a parent is a symlink that should be a real directory.","solutions":["Inspect each segment of the destination's parent chain with `ls -la` and replace any symlink with a real directory: `rm <symlink> && mkdir -p <dir>`.","Re-run the install/repair/uninstall action once the chain contains only real directories.","If the symlink is intentional, point the installer at a target root whose parent chain is all real directories (override HOME or the target root).","Check for a concurrent process (cloud-sync, backup) racing the installer and pause it during the operation."],"exampleFix":"// before: ~/.claude/skills -> /mnt/shared/skills (symlink)\n// after:\nrm ~/.claude/skills\nmkdir -p ~/.claude/skills\n./install.sh --target claude","handlingStrategy":"validation","validationCode":"const fs = require('fs');\nconst path = require('path');\n\nfunction verifyRealDirChain(targetPath, trustedRoot) {\n  const resolved = path.resolve(trustedRoot);\n  let cur = path.resolve(targetPath);\n  while (cur.startsWith(resolved) && cur !== resolved) {\n    const st = fs.lstatSync(cur);\n    if (!st.isDirectory() || st.isSymbolicLink()) {\n      throw new Error(`Unsafe segment (not a real dir or is symlink): ${cur}`);\n    }\n    cur = path.dirname(cur);\n  }\n}\n// call before invoking install/repair/uninstall\nverifyRealDirChain('/home/user/.claude/skills', '/home/user/.claude');","typeGuard":"function isRealDirectory(stat) {\n  return stat.isDirectory() && !stat.isSymbolicLink();\n}","tryCatchPattern":"try {\n  await runInstall(target);\n} catch (err) {\n  if (/Refusing to .*: destination parent is not a trusted directory/.test(err.message)) {\n    // walk the chain, replace symlinks with real dirs, then retry once\n  } else throw err;\n}","preventionTips":["Never pre-create install destinations as symlinks; use real directories.","Pause cloud-sync clients (Dropbox/iCloud) before running the installer.","Run the installer as the same user that owns the target root so segment ownership is stable.","If sharing content across machines, sync the source repo, not the installed target."],"tags":["security","filesystem","symlink","toctou","install"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}