{"record":{"id":"eb9afd39b0ff8d7b","repo":"affaan-m/ECC","slug":"refusing-to-action-through-symlinked-claude-ski","errorCode":null,"errorMessage":"Refusing to ${action} through symlinked Claude skill path: '${currentPath}'.","messagePattern":"Refusing to (.+?) through symlinked Claude skill path: '(.+?)'\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"scripts/lib/install/claude-skill-migration.js","lineNumber":74,"sourceCode":"    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(\n        `Refusing to ${action} through symlinked Claude skill path: '${currentPath}'.`\n      );\n    }\n  }\n\n  if (pathExists(targetRoot)) {\n    assertWithinTrustedRoot(targetPath, targetRoot, action);\n  }\n}\n\nfunction describeClaudeSkillOperation(targetRoot, operation) {\n  if (!operation || operation.kind !== 'copy-file') {\n    return null;\n  }\n\n  const sourceRelativePath = normalizeSourceRelativePath(operation.sourceRelativePath);\n  if (!sourceRelativePath) {\n    return null;","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/install/claude-skill-migration.js#L56-L92","documentation":"Thrown by assertSafeSkillPath in scripts/lib/install/claude-skill-migration.js after the containment check passes. The guard walks every segment from targetRoot to targetPath, calling fs.lstatSync on each. If any segment is a symbolic link, the operation is refused. This is the Claude-skill equivalent of apply.js's symlink guard (error 206) and covers the same TOCTOU/symlink-escape class of attack but specifically for managed skill files.","triggerScenarios":"Any segment between targetRoot and the skill file is a symlink — e.g. ~/.claude/skills/<name> is a symlink installed by a dotfile manager, or a parent directory in the chain is symlinked.","commonSituations":"Dotfile-managed .claude (stow, yadm, chezmoi); Dropbox/iCloud-synced skills folder; symlinked skill installed by another tool; a malicious repo that symlinks .claude/skills.","solutions":["Run ls -la <targetRoot>/skills (and parents) to identify symlinks.","Replace each symlinked skill directory with a real directory (copy contents, remove symlink).","Configure your dotfile manager to copy rather than link .claude/skills.","Re-run the install."],"exampleFix":"# before\n~/.claude/skills/my-skill -> ~/dotfiles/.claude/skills/my-skill\n\n# after\nrm ~/.claude/skills/my-skill\ncp -r ~/dotfiles/.claude/skills/my-skill ~/.claude/skills/my-skill","handlingStrategy":"validation","validationCode":"function assertRealDirsOnly(rootDir, childPath) {\n  const rel = path.relative(rootDir, childPath);\n  let cur = rootDir;\n  for (const seg of rel.split(path.sep)) {\n    cur = path.join(cur, seg);\n    let s;\n    try { s = fs.lstatSync(cur); } catch (e) { if (e.code === 'ENOENT') break; throw e; }\n    if (s.isSymbolicLink()) throw new Error(`symlink in path: ${cur}`);\n  }\n}\nfor (const op of plan.operations) assertRealDirsOnly(plan.targetRoot, op.destinationPath);","typeGuard":null,"tryCatchPattern":"try {\n  applyInstallPlan(plan);\n} catch (err) {\n  if (/through symlinked Claude skill path/.test(err.message)) {\n    console.error('Replace skill symlinks with real directories under', plan.targetRoot);\n  }\n  throw err;\n}","preventionTips":["Use copy-based dotfile management for .claude/skills (avoid stow/yadm linking).","Periodically run find ~/.claude -type l to catch stray symlinks.","Never install skills into a sync-managed (Dropbox/iCloud) directory."],"tags":["security","symlink","claude-skill","install-safety"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}