T09 · Insecure Skill Coding Practices
- Location
scripts/handle_brat_command.py:59- Finding
Symbolic-Link Bypass of Managed Output Directory Confinement
- Content
View full analysis
Vulnerability Details
File Location:
scripts/handle_brat_command.py, lines 59–61; output operations at lines 84–85, 101, and 116
Vulnerability Type: Symbolic-link path traversal and unsafe file creation
Risk Level: MediumThe relative output path is sanitized lexically, but the resulting directory is not resolved and verified against the managed base directory before it is returned:
python safe_dir = base_out_dir.joinpath(*safe_parts) if safe_parts else base_out_dir safe_dir.mkdir(parents=True, exist_ok=True) return safe_dirThe returned directory is subsequently used to construct predictable output paths and write image files:
python slug = sanitize_slug(text) stamp = datetime.now().strftime("%Y%m%d-%H%M%S") out_dir = resolve_safe_out_dir(args.out_dir) font_path = str(resolve_font_path()) png_path = out_dir / f"{stamp}-{slug}.png" webp_path = out_dir / f"{stamp}-{slug}.webp"python image_png.save(png_path, format="PNG")python image_webp.save(webp_path, format="WEBP", lossless=True, quality=95, method=6)Technical Analysis
resolve_safe_out_dir()removes explicit.and..components and restricts the characters accepted in relative path components. This prevents ordinary lexical directory traversal, but it does not prevent filesystem-level traversal through symbolic links.If an existing component beneath
scripts/outis a symbolic link to a directory outside the managed output tree,mkdir(..., exist_ok=True)accepts the linked directory and the unresolved path is returned. Pillow then follows the symbolic link when saving the generated files.The output names use a timestamp with only one-second precision and a text-derived slug. Consequently, an attacker who can modify
scripts/outmay also predict output filenames and place a file-level symbolic link at the expected destination. The save operation does not request exclusive creation or ...[truncated 1947 chars]- Remediation
View remediation
Remediation Suggestions
-
Resolve the final output directory after path construction and verify that it remains equal to or beneath the resolved managed base directory:
python safe_dir = base_out_dir.joinpath(*safe_parts) safe_dir.mkdir(parents=True, exist_ok=True) resolved_dir = safe_dir.resolve(strict=True) if resolved_dir != base_out_dir and base_out_dir not in resolved_dir.parents: raise ValueError("Output directory escapes the managed output tree") return resolved_dir -
Inspect each existing path component with
lstat()and reject symbolic links rather than relying solely on a final containment check. -
Use collision-resistant filenames, such as names containing a cryptographically random UUID, instead of relying only on a second-resolution timestamp and a predictable slug.
-
Create output files atomically with exclusive creation. On platforms that support it, use no-follow semantics such as
O_NOFOLLOWtogether withO_CREAT | O_EXCL, then save through the securely opened file descriptor. -
Restrict
scripts/outpermissions so that untrusted users cannot create, replace, or link entries within it. -
Add regression tests covering directory symlinks, file symlinks, nested symlink components, filename collisions, and attempts to redirect output outside
scripts/out.
-
