T09 · Insecure Skill Coding Practices
- Location
src/audit.js:137- Finding
Symbolic-Link Traversal Allows Reads Outside the Scan Root
- Content
View full analysis
Vulnerability Details
File Location:
src/audit.js:137-158
Vulnerability Type: Symbolic-link traversal and unrestricted recursive file access
Risk Level: MediumVulnerable Code
js function walk(currentPath) { if (!fs.existsSync(currentPath)) return; const stat = fs.statSync(currentPath); if (stat.isDirectory()) { // Skip node_modules and .git if (path.basename(currentPath) === 'node_modules') return; if (path.basename(currentPath) === '.git') return; const files = fs.readdirSync(currentPath); for (const file of files) { walk(path.join(currentPath, file)); } } else if (stat.isFile()) { const ext = path.extname(currentPath).toLowerCase(); if (extensions.includes(ext)) { const content = fs.readFileSync(currentPath, 'utf-8'); const fileFindings = scanFile(currentPath, content); findings.push(...fileFindings); } } }Technical Analysis
The recursive scanner uses
fs.statSync(), which follows symbolic links, and subsequently reads or traverses the resolved target. It does not usefs.lstatSync()to identify links, verify that each canonical path remains beneath the canonical scan root, or track previously visited directories.Consequently, an untrusted Skill can contain a symbolic link whose target is outside the Skill directory. If the target is a directory, the scanner recursively traverses it. If it is a file with one of the supported extensions—
.js,.ts,.mjs,.cjs,.json, or.md—the scanner reads its contents. A cyclic directory link can also cause unbounded recursion.This behavior exceeds the minimum filesystem privileges required to scan files contained within the user-selected Skill directory.
Attack Path
- An attacker places a symbolic link in a Skill package.
- The link targets a sensitive file with a supported extension or a directory outside the package.
- A user runs
skill-audit scan <skill-directory>or sca ...[truncated 1050 chars]
- Remediation
View remediation
Remediation Suggestions
- Use
fs.lstatSync()orfs.promises.lstat()before processing each entry and reject symbolic links by default. - Canonicalize the scan root once with
fs.realpathSync()and canonicalize every candidate before access. - Verify that every candidate remains beneath the canonical root using a separator-aware containment check; do not rely on simple string-prefix comparison.
- Track visited directories by canonical path or filesystem identity to prevent cycles.
- Apply maximum traversal depth, file-count, and file-size limits to reduce denial-of-service exposure.
- Handle filesystem exceptions per entry so broken links, permission failures, and race conditions do not terminate the entire scan.
- Consider opening files with platform-supported no-follow protections where available to reduce time-of-check/time-of-use risks.
- Add regression tests covering links to external files, links to external directories, broken links, cyclic links, and similarly prefixed paths outside the root.
- Use
