T09 · Insecure Skill Coding Practices
- Location
scripts/lint.mjs:91- Finding
Recursive Symbolic-Link Traversal Can Escape the Target Directory or Cause Denial of Service
- Content
View full analysis
Vulnerability Details
File Location:
scripts/lint.mjs, lines 91–99 and 133–140
Vulnerability Type: Unrestricted symbolic-link traversal during recursive directory scanning
Risk Level: MediumVulnerable Code
js function collectMarkdownFiles(dir) { const results = []; for (const entry of readdirSync(dir)) { const full = join(dir, entry); const stat = statSync(full); if (stat.isDirectory()) { if (entry.startsWith(".") || entry === "node_modules") continue; results.push(...collectMarkdownFiles(full)); } else if (extname(full) === ".md") { results.push(full); } } return results; }The recursively collected files are subsequently read by
lintFile:js const stat = statSync(resolved); if (stat.isDirectory()) { files.push(...collectMarkdownFiles(resolved)); } else { files.push(resolved); }Technical Analysis
statSync()follows symbolic links. The recursive collector does not uselstatSync()or directory entries configured to identify symbolic links, does not verify that each resolved path remains beneath the original scan root, and does not track previously visited directories.Consequently, a symbolic link located inside a scanned directory is treated as the linked target:
- A link to a directory outside the requested root causes traversal and reading of Markdown files beyond the intended scope.
- A link that points to an ancestor or otherwise creates a directory cycle causes repeated recursive calls without a depth or visited-directory limit.
- The checks for dot-prefixed directories and
node_modulesonly inspect the link's entry name, not its resolved destination, so they do not prevent this behavior.
Attack Path
- An attacker gains the ability to place files or symbolic links in a directory that a user or CI process will lint.
- The attacker creates a symbolic link with an innocuous name:
- It may point to a directory outside the intended target contai ...[truncated 1377 chars]
- Remediation
View remediation
Remediation Suggestions
-
Reject symbolic links during recursive traversal by inspecting entries with
lstatSync()orreaddirSync(dir, { withFileTypes: true }):js const stat = lstatSync(full); if (stat.isSymbolicLink()) { continue; } -
Resolve and enforce containment for every traversed path. Compare each canonical path obtained with
realpathSync()against the canonical scan root usingpath.relative(); reject paths that resolve outside the root. -
If following symbolic links is an intentional feature, track visited directories using canonical paths or device/inode pairs to prevent cycles.
-
Add recursion-depth and file-count limits to reduce denial-of-service exposure from unusually deep or large directory trees.
-
Handle per-entry filesystem errors without terminating the entire scan where appropriate, including broken links and permission failures.
-
Add regression tests covering:
- A symlink to a parent directory.
- A symlink to an external directory.
- A self-referential or mutually referential link cycle.
- Broken symbolic links.
- Valid nested directories that remain within the selected root.
-
