T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- scripts/postinstall.js:27
- Finding
- Destructive Replacement of an Existing Claude Skill Directory## Vulnerability Details **File Location**: `scripts/postinstall.js:27-41` **Vulnerability Type**: Destructive installation behavior and unauthorized replacement of user-controlled files **Risk Level**: High ### Vulnerable Code ```js if (existsSync(SKILL_LINK)) { try { const stats = lstatSync(SKILL_LINK); if (stats.isSymbolicLink()) { const currentTarget = readlinkSync(SKILL_LINK); if (currentTarget === PACKAGE_ROOT) { console.log('[ttc] Claude Code skill already configured.'); return true; } unlinkSync(SKILL_LINK); } else { rmSync(SKILL_LINK, { recursive: true }); } } catch (err) { console.log(`[ttc] Warning: ${err.message}`); } } symlinkSync(PACKAGE_ROOT, SKILL_LINK); ``` The vulnerable function is invoked automatically by the npm lifecycle configuration in `package.json:28`: ```json "postinstall": "node scripts/postinstall.js" ``` ### Technical Analysis During package installation, the script unconditionally takes control of the fixed path `~/.claude/skills/ttc`. If that path is a symbolic link to another target, the script removes the link. More seriously, if it is an ordinary file or directory, `rmSync(SKILL_LINK, { recursive: true })` recursively deletes it. The script does not establish that the existing path was created by this package, does not inspect or preserve its contents, and does not request confirmation. Installation of a transit CLI does not require deletion of pre-existing user-controlled data. This violates least-privilege and safe installer design principles. The subsequent `symlinkSync` call replaces the removed path with a link to the package root, causing this package to supersede any existing Skill registered under the same name. ### Attack Path 1. The user already has a file, directory, or different symbolic link at `~/.claude/skills/ttc`. 2. The user installs `@lucasygu/ttc` through npm without disabling lifecycle scripts. 3. npm automatically executes ...[truncated 982 chars]
- Remediation
- ## Remediation Suggestions - If `~/.claude/skills/ttc` exists and is not the exact symlink previously created by this package, stop installation without modifying it. - Never recursively delete a pre-existing Skill directory during an automatic package lifecycle hook. - Require an explicit, separately invoked command and clear confirmation before replacing an existing Skill. - If replacement is requested, atomically rename the old path to a timestamped backup rather than deleting it. - Record installation ownership in package-specific metadata and only remove resources that can be reliably attributed to this package. - Consider making Claude Skill registration opt-in rather than executing it automatically during `postinstall`. - Use atomic link creation and report name conflicts with actionable manual instructions.
