T09 · Insecure Skill Coding Practices
Warning
- Location
- index.js:30
- Finding
- Recursive Directory Hashing Follows Symbolic Links Outside the Requested Root<![CDATA[ ## Vulnerability Details **File Location**: `index.js:30-47` **Vulnerability Type**: Symbolic-link traversal and uncontrolled recursive traversal **Risk Level**: Medium ### Vulnerable Code ```js const results = {}; async function scan(currentDir) { const list = fs.readdirSync(currentDir); // Sort to ensure deterministic order if we were hashing the dir itself (future) list.sort(); for (const file of list) { if (file === '.git' || file === 'node_modules') continue; const fullPath = path.join(currentDir, file); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { if (recursive) await scan(fullPath); } else { const hash = await hashFile(fullPath, algo); // Store relative path const relativePath = path.relative(dirPath, fullPath); results[relativePath] = hash; } } } ``` ### Technical Analysis The recursive scanner uses `fs.statSync()` to determine whether each entry is a directory. Unlike `fs.lstatSync()`, `fs.statSync()` follows symbolic links and returns information about the link's target. Consequently, a symbolic link located beneath the user-selected directory can point to a directory outside that root. The scanner then recursively processes the external directory without checking its canonical path against the canonical path of the requested root. The implementation also does not maintain a set of previously visited directories. A symbolic link that points to an ancestor directory or otherwise creates a cycle can therefore cause repeated recursive traversal until an operating-system limit or another runtime failure is reached. Exploitation requires an attacker to create or influence entries in a directory that the user later scans. The vulnerable code does not grant new operating-system privileges: access remains limited to files readable by the Node.js process. ### Attack Path 1. An attacker gains the abili ...[truncated 1694 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Inspect directory entries with `fs.lstatSync()` or `fs.readdirSync(currentDir, { withFileTypes: true })` so symbolic links can be identified without following them. 2. Skip symbolic links by default when recursively hashing a directory. 3. If following symbolic links is an intentional feature: - Resolve the requested root once with `fs.realpathSync()`. - Resolve each candidate target with `fs.realpathSync()`. - Use `path.relative()` to verify that the resolved target remains beneath the resolved root. - Reject a target when the relative result is `..`, begins with `..${path.sep}`, or is absolute. 4. Maintain a visited-directory set using canonical paths or filesystem device/inode identities to prevent recursive cycles. 5. Handle race conditions between inspection and access. Where practical, use descriptor-based operations and avoid relying exclusively on separate check-then-use filesystem calls. 6. Add regression tests covering: - A symlink to a directory outside the selected root; - A symlink to a file outside the selected root; - A symlink to the selected directory itself; - A symlink to an ancestor directory; and - Multiple links forming a cycle. ]]>
