T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- tool.py:11
- Finding
- Workspace Isolation Bypass Through Symbolic Links## Vulnerability Details **File Location**: `tool.py`, lines 11–30; affected sinks include lines 61–65, 88–98, 105–109, 124–137, and 204–243 **Vulnerability Type**: Symbolic-link-based workspace containment bypass **Risk Level**: High ### Vulnerable Code ```python # SECURITY: Validate file paths to prevent path traversal def validate_file_path(file_path, must_exist=True): """Validate file path for security - must be within workspace""" if not file_path: return None # Resolve to absolute path abs_path = os.path.abspath(file_path) workspace_root = os.path.abspath(os.getcwd()) # SECURITY: Enforce workspace containment - file MUST be inside current working directory if not abs_path.startswith(workspace_root + os.sep) and abs_path != workspace_root: raise ValueError(f"Access denied: file must be within workspace ({workspace_root})") # SECURITY: Block sensitive system directories (defense in depth) forbidden_prefixes = ['/etc/', '/proc/', '/sys/', '/root/', '/home/'] for prefix in forbidden_prefixes: if abs_path.startswith(prefix): raise ValueError(f"Access denied: cannot access {prefix} directories") if must_exist and not os.path.exists(abs_path): raise FileNotFoundError(f"File not found: {abs_path}") return abs_path ``` Representative read sink: ```python def transcribe(file_path, model_name="base"): try: # SECURITY: Validate file path safe_path = validate_file_path(file_path, must_exist=True) whisper = get_whisper() model = whisper.load_model(model_name) result = model.transcribe(safe_path) return {"text": result["text"], "segments": result.get("segments", [])} ``` Representative write sink: ```python # SECURITY: Validate output path safe_output = validate_file_path(output_path, must_exist=Fals ...[truncated 2870 chars]
- Remediation
- ## Remediation Suggestions 1. Resolve the real filesystem locations of both the workspace and candidate path before performing containment checks: ```python from pathlib import Path workspace_root = Path.cwd().resolve(strict=True) candidate = Path(file_path).resolve(strict=must_exist) try: candidate.relative_to(workspace_root) except ValueError: raise ValueError("Access denied: path is outside the workspace") ``` 2. Reject symbolic links when they are unnecessary. Check every existing path component, not only the final component, because a parent directory can also be a symlink. 3. For output files, reject existing symbolic links and create the destination atomically using operating-system no-follow and exclusive-creation controls where available, such as `O_NOFOLLOW` and `O_EXCL`. 4. Avoid a check-then-use race. Where possible, open a validated file descriptor securely and pass that descriptor to the consumer instead of validating a path and reopening it later. 5. Use `os.path.commonpath()` or `Path.relative_to()` rather than string-prefix comparisons for containment checks. 6. Add regression tests covering final-component symlinks, symlinked parent directories, broken output symlinks, relative traversal, and attempts to replace a validated file with a symlink between validation and use.
