T09 · Insecure Skill Coding Practices
- Location
camsnap.py:20- Finding
Symlink Race Can Bypass the Output Directory Restriction
- Content
View full analysis
Vulnerability Details
File Location:
camsnap.py, lines 20–40, 59–68, and 84
Vulnerability Type: Time-of-check to time-of-use (TOCTOU) symlink race
Risk Level: LowVulnerable Code
python def _validate_output_path(output_path: str) -> str: """Resolve and validate an output path, guarding against traversal.""" resolved = Path(output_path).resolve() # Block path traversal outside cwd tree cwd = Path.cwd().resolve() try: resolved.relative_to(cwd) except ValueError: raise ValueError( f"Output path escapes working directory: {output_path}" ) ext = resolved.suffix.lower() if ext and ext not in ALLOWED_EXTENSIONS: raise ValueError( f"Unsupported image extension '{ext}'. Allowed: {sorted(ALLOWED_EXTENSIONS)}" ) if not ext: # Default to .jpg when no extension given resolved = resolved.with_suffix(".jpg") return str(resolved)python if output_path: output_path = _validate_output_path(output_path) else: output_dir = _validate_output_path(output_dir) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") output_path = os.path.join(output_dir, f"snapshot_{timestamp}.jpg") # Ensure parent directory exists os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True)python success = cv2.imwrite(output_path, frame)Technical Analysis
The script resolves the supplied path and verifies that it is beneath the current working directory. However, validation and image creation are separate filesystem operations. The validated pathname is later passed to
os.makedirs()andcv2.imwrite(), both of which resolve path components again.A local attacker who can modify a destination directory within the working directory can replace a validated path component with a symbolic link after
_validate_output_path()returns ...[truncated 1754 chars]- Remediation
View remediation
Remediation Suggestions
- Avoid validating a pathname and then reopening it through ordinary pathname resolution.
- Open a trusted descriptor for the working directory and perform descriptor-relative traversal and file creation.
- On supported platforms, use
openat-style operations withO_NOFOLLOWfor each relevant path component and reject symbolic links. - Verify that every intermediate component is a directory owned or controlled by the expected principal.
- Create the destination file atomically with restrictive permissions and without following a final symbolic link.
- Because
cv2.imwrite()accepts a pathname rather than an existing file descriptor, encode the image in memory withcv2.imencode()and write the resulting bytes through the securely opened descriptor. - If descriptor-relative operations are unavailable, use a private output directory that untrusted users cannot modify and revalidate the parent immediately before writing. Revalidation alone reduces but does not eliminate the race.
- Add tests that replace validated path components with symlinks and confirm that writes outside the working directory are rejected.
