T05 · Unauthorized Access and Privilege Escalation
Warning
- Location
- guard.py:116
- Finding
- Scan Directory Boundary Bypass Through Symbolic Links<![CDATA[ ## Vulnerability Details **File Location**: `guard.py:82-83` and `guard.py:116-122` **Vulnerability Type**: Symbolic-link traversal outside the authorized scan root **Risk Level**: Medium ### Vulnerable Code ```python def scan_file(filepath: Path) -> List[Dict[str, Any]]: """Scan a single file for security patterns.""" findings = [] try: content = filepath.read_text(errors="ignore") except Exception: return findings ``` ```python for filepath in scan_path.rglob("*"): if filepath.is_file() and filepath.suffix in scan_extensions: if any(skip in filepath.parts for skip in skip_dirs): continue findings = scan_file(filepath) # Make paths relative for f in findings: f["file"] = str(filepath.relative_to(scan_path)) all_findings.extend(findings) files_scanned += 1 ``` ### Technical Analysis The scanner recursively identifies files under a user-selected directory using `Path.rglob()`. It then checks candidates with `Path.is_file()` and reads them with `Path.read_text()`. Both operations follow symbolic links. The implementation does not resolve each candidate to its canonical path and verify that the resolved target remains inside `scan_path`. Consequently, a symbolic link located within the selected scan directory can point to a readable file outside that directory, causing the scanner to access data beyond its declared authorization boundary. This behavior contradicts the statement in `SKILL.md` that the Skill does not access files outside the specified scan directory. The behavior also exceeds the minimum filesystem access required for scanning a selected project. The scanner does not print complete file contents and does not upload scan results. However, it can disclose derived information such as matched credential types, finding categories, and line numbers from an external file. The static pre-scan warning concerning SSH keys does not indi ...[truncated 1437 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Resolve each candidate path and enforce containment before reading it. Reject symbolic links unless following them is an explicitly documented feature. For Python 3.9 compatibility, containment can be validated as follows: ```python scan_path = Path(path).resolve() for filepath in scan_path.rglob("*"): try: if filepath.is_symlink(): continue resolved = filepath.resolve(strict=True) resolved.relative_to(scan_path) except (OSError, RuntimeError, ValueError): continue if not resolved.is_file() or resolved.suffix not in scan_extensions: continue findings = scan_file(resolved) ``` Additional hardening should include: 1. Reject both file symlinks and directory symlinks. 2. Perform the containment check immediately before opening the file to reduce time-of-check/time-of-use risk. 3. Consider opening files through a directory file descriptor with platform-specific no-follow protections where strong adversarial filesystem guarantees are required. 4. Add tests covering symlinks to files and directories outside the scan root. 5. Document the precise scan boundary and any intentionally supported link behavior. 6. Return a warning when a candidate is skipped because it resolves outside the authorized root. ]]>
