T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/cropper.py:188
- Finding
- COCO Annotation Filename Path Traversal Allows Unauthorized Local Image Access## Vulnerability Details **File Location**: `scripts/cropper.py`, lines 188-224 **Vulnerability Type**: Path traversal caused by an untrusted COCO `file_name` **Risk Level**: Medium ### Vulnerable 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 += 1 ``` ### Technical Analysis The COCO `file_name` property is read from an externally supplied JSON annotation and appended directly to `images_dir`. The application does not reject absolute paths, normalize traversal components, resolve symbolic links, or verify ...[truncated 1972 chars]
- 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(): continue ``` The 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.
