T09 · Insecure Skill Coding Practices
- Location
scripts/wardrobe.py:37- Finding
Fail-Open Image Validation Allows Arbitrary Local Files to Be Copied
- Content
View full analysis
Vulnerability Details
File Location:
scripts/wardrobe.py, lines 37–48; reachable from the CLI at lines 522–524
Vulnerability Type: Fail-open file-type validation
Risk Level: MediumVulnerable Code
python # Security check: restrict file extensions ext = os.path.splitext(src_path)[-1].lower() allowed_exts = ['.jpg', '.jpeg', '.png', '.gif', '.webp'] if ext not in allowed_exts: ext = '.jpg' os.makedirs(_IMAGES_DIR, exist_ok=True) if item_name: safe_name = hashlib.md5(item_name.encode()).hexdigest()[:8] else: safe_name = hashlib.md5(str(os.path.getsize(src_path)).encode()).hexdigest()[:8] new_name = f"{safe_name}{ext}" new_path = os.path.join(_IMAGES_DIR, new_name) try: shutil.copy2(src_path, new_path) return new_path except Exception: return ''The vulnerable function is exposed through the following CLI flow:
python if args.image and os.path.exists(args.image): img_path = store_image(args.image, args.name)Technical Analysis
The extension allowlist does not reject unsupported input. When the source has an unapproved extension, the code merely changes the destination extension to
.jpgand copies the source bytes unchanged.The implementation does not validate a file signature, MIME type, or successful decoding by an image parser. Therefore, any process-readable regular file of no more than 10 MB can be copied into
data/images/and represented as an image, regardless of its actual content.Files already carrying an allowed extension are also trusted solely based on their names. A non-image file renamed with
.jpg,.png, or another permitted suffix passes the same validation.Attack Path
-
An attacker or untrusted integration obtains the ability to invoke the local
addcommand. -
The attacker supplies the path of a process-readable local file through
--image, for example:bash ./run.sh add \ --name "Imported item" \ --category "Other" \ --image
...[truncated 1569 chars]
-
- Remediation
View remediation
Remediation Suggestions
-
Reject unsupported extensions instead of relabeling them.
python ext = os.path.splitext(src_path)[1].lower() allowed_exts = {'.jpg', '.jpeg', '.png', '.gif', '.webp'} if ext not in allowed_exts: return '' -
Validate actual file content. Open the source with a maintained image-decoding library, verify that decoding succeeds, and compare the detected format with the permitted formats. Extension checks alone are insufficient.
-
Re-encode accepted images. Decode and save the image into a normalized format rather than copying untrusted bytes unchanged. This prevents arbitrary content from being preserved merely because it has an accepted suffix.
-
Restrict source locations where feasible. Accept images only from an approved upload or temporary directory rather than arbitrary filesystem paths supplied through the CLI.
-
Use collision-resistant destination names. Replace the truncated deterministic MD5 filename with a random UUID or a cryptographically strong content identifier. Deterministic names derived from item names allow replacement of an existing destination file.
-
Protect destination creation. Create destination files atomically and prevent following pre-existing symbolic links. Set restrictive directory and file permissions appropriate for personal wardrobe data.
-
Add regression tests. Verify that text files, renamed non-image files, malformed images, oversized files, directories, and symbolic-link edge cases are rejected.
-
