T09 · Insecure Skill Coding Practices
- Location
scripts/cropper.py:188- Finding
COCO Annotation Filename Path Traversal Allows Unauthorized Local Image Access
- Content
View full analysis
Vulnerability Details
File Location:
scripts/cropper.py, lines 188-224
Vulnerability Type: Path traversal caused by an untrusted COCOfile_name
Risk Level: MediumVulnerable Code
python for img_id, img_info in images.items(): img_name = img_info.get('file_name', f'{img_id}.jpg') img_path = images_dir / img_name if not img_path.exists(): continue width = img_info.get('width', 0) height = img_info.get('height', 0) if width == 0 or height == 0: with Image.open(img_path) as img: width, height = img.size # Parse annotations annotations = parse_coco_annotation(coco_data, img_id) if not annotations: continue if args.objects: for i, bbox in enumerate(annotations): class_id, x1, y1, x2, y2 = bbox if args.min_size: if (x2 - x1) < args.min_size or (y2 - y1) < args.min_size: continue cropped = crop_image(str(img_path), bbox, args.padding, width, height) base_name = Path(img_name).stem output_name = f"{base_name}_{i}.{args.format}" output_path = output_dir / output_name cropped.save(output_path, quality=args.quality) total_cropped += 1 else: cropped = crop_image(str(img_path), annotations[0], args.padding, width, height) base_name = Path(img_name).stem output_path = output_dir / f"{base_name}_crop.{args.format}" cropped.save(output_path, quality=args.quality) total_cropped += 1Technical Analysis
The COCO
file_nameproperty is read from an externally supplied JSON annotation and appended directly toimages_dir. The application does not reject absolute paths, normalize traversal components, resolve symbolic links, or verify ...[truncated 1972 chars]- Remediation
View remediation
Remediation Suggestions
Resolve and validate every annotation-derived path before accessing it:
python images_root = Path(args.images).resolve() raw_name = img_info.get("file_name", f"{img_id}.jpg") relative_name = Path(raw_name) if relative_name.is_absolute(): raise ValueError(f"Absolute image path is not allowed: {raw_name}") img_path = (images_root / relative_name).resolve() try: img_path.relative_to(images_root) except ValueError: raise ValueError(f"Image path escapes the image directory: {raw_name}") if not img_path.is_file(): continueThe containment check must occur after resolution so that both
..traversal and symbolic-link escapes are rejected. Consider additionally allowing only expected filename suffixes, rejecting unexpected nested paths if dataset semantics do not require them, and logging rejected records without exposing sensitive absolute paths. Add regression tests covering absolute paths,../traversal, nested traversal, and symlinks that point outside the image root.
