T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/run.py:31- Finding
Directory Scan Follows Symbolic Links Outside the Requested Root
- Content
View full analysis
Vulnerability Details
File Location:
scripts/run.py, lines 31-39 and 142-146
Vulnerability Type: Out-of-scope local file access through symbolic links
Risk Level: MediumVulnerable Code
python def list_text_files(root: Path, limit: int = 50): results = [] for path in root.rglob("*"): if len(results) >= limit: break if path.is_file(): if path.suffix.lower() in {".md",".txt",".json",".yaml",".yml",".py",".js",".ts",".csv",".tsv",".sh"}: results.append(path) return resultspython def pattern_report(spec: dict, path: Path, limit: int) -> str: targets = [path] if path.is_file() else list_text_files(path, limit=limit) findings = [] for target in targets: text = read_text(target)Technical Analysis
The directory scanner recursively enumerates entries and accepts any entry for which
Path.is_file()returns true. Python follows symbolic links when evaluatingis_file()and when reading a path. The implementation does not reject symbolic links, resolve candidate paths, or verify that resolved files remain beneath the requested scan root.Consequently, a directory under an attacker's control can contain a symbolic link with an allowed extension that targets a readable file elsewhere on the local system. When that directory is scanned,
pattern_report()reads the external target. If its contents match one of the configured patterns, matching content and the target path are included in the generated report.Attack Path
- An attacker gains the ability to place files or symbolic links in a directory that a user or automation process will scan.
- The attacker creates a symbolic link with an accepted extension, such as
external.txt, pointing to a sensitive file outside the intended directory. - A user invokes
scripts/run.py --input <attacker-controlled-directory>.
...[truncated 749 chars]
- Remediation
View remediation
Remediation Suggestions
- Reject symbolic links during traversal by checking
path.is_symlink()before accepting a candidate. - Resolve the scan root once and resolve every candidate before reading it.
- Require every resolved candidate to remain under the resolved root, for example with
candidate.resolve().is_relative_to(root.resolve()). - Handle resolution failures and broken links as non-fatal rejected entries.
- Apply maximum file-size limits before reading to reduce denial-of-service exposure.
- Add tests covering symlinks to files both inside and outside the selected root.
- Consider reporting rejected out-of-root links without disclosing their contents.
- Reject symbolic links during traversal by checking
