T09 · Insecure Skill Coding Practices
- Location
bin/install.js:203- Finding
Node Installer Follows Attacker-Controlled Symbolic Links During Cleanup and Installation
- Content
View full analysis
Vulnerability Details
File Location:
bin/install.js, lines 203–257
Vulnerability Type: Symbolic-link traversal causing writes and deletion outside the selected installation directory
Risk Level: HighTechnical Analysis
The Node.js installer derives the destination as
<selected-directory>/yotta-skill-creatorbut does not reject symbolic links at that destination or among existing destination entries.The
cleanResidue()function initially callsfs.statSync(target), which follows a symbolic link. If the linked directory containsSKILL.md, the function treats it as a legitimate prior installation. It then constructs paths under that link and recursively deletes fixed entries such aspackage.json,.github,.git,bin, andnode_modules.After cleanup,
installTo()callsfs.mkdirSync()and copies package contents into the same destination without verifying its canonical path or rejecting symbolic links. Consequently, an attacker-controlled link can redirect cleanup and installation operations outside the directory selected by the user.Relevant code:
javascript function cleanResidue(target) { const removed = []; let stat = null; try { stat = fs.statSync(target); } catch (_) { return removed; } if (!stat.isDirectory()) return removed; if (!fs.existsSync(path.join(target, 'SKILL.md'))) return removed; for (const name of TOP_SKIP) { const p = path.join(target, name); let entry = null; try { entry = fs.lstatSync(p); } catch (_) { continue; } if (entry.isSymbolicLink()) continue; fs.rmSync(p, { recursive: true, force: true }); removed.push(name); } const walk = (dir) => { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const p = path.join(dir, entry.name); if (entry.isSymbolicLink()) continue; if (entry.isDirectory()) { if (CACHE_DIRS.has(entry.name)) { fs.rmSync(p, { recursive: true, force: true }); removed.push(p ...[truncated 3514 chars]- Remediation
View remediation
Remediation Suggestions
- Use
fs.lstatSync()on the installation base and final target before cleanup or copying, and reject either path if it is a symbolic link. - Resolve the canonical installation base with
fs.realpathSync()and verify that the canonical target remains strictly beneath that base. - Inspect every existing destination path component with
lstatSync()before writing or deleting; do not follow symbolic links encountered at any depth. - Apply the same symbolic-link protections already implemented in
install.shtobin/install.js. - Avoid modifying an existing destination in place. Build the installation in a newly created temporary directory under a trusted parent and atomically rename it into place after validation.
- Before every recursive deletion, revalidate that the canonical path is beneath the canonical installation target.
- Add regression tests covering a symbolic-link target, symbolic-link parent, and nested symbolic-link entries, and verify that installation fails without modifying the linked location.
- Use
