Back to skill

Security audit

Image Metadata Cleaner

Security checks for vulnerabilities and agentic risk

Overview

This image metadata cleaner is mostly coherent, but its optional report file can overwrite other writable files without the same confirmation protections promised for image outputs.

Install only if you are comfortable with a local image-processing script that writes files. Use dry-run first, avoid custom --manifest paths unless you trust the destination, do not run it on untrusted images in a privileged environment, and prefer a pinned/locked Pillow install.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/strip.py:226
Finding
Manifest Output Can Silently Overwrite Arbitrary Writable Files<![CDATA[ ## Vulnerability Details **File Location**: `scripts/strip.py:226-231` and `scripts/strip.py:369-377` **Vulnerability Type**: Unrestricted file overwrite through a user-controlled manifest path **Risk Level**: Medium ### Vulnerable Code ```python def write_manifest(path: Path, reports: list[FileReport]) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text( json.dumps([asdict(report) for report in reports], indent=2), encoding="utf-8", ) ``` ```python manifest_path = None if args.manifest: manifest_path = Path(args.manifest) if not manifest_path.is_absolute(): if batch: manifest_path = output_root / manifest_path else: first_output = reports[0].output manifest_path = (Path(first_output).parent if first_output else src.parent) / manifest_path if not args.dry_run: write_manifest(manifest_path, reports) ``` ### Technical Analysis The value accepted by `--manifest` directly controls the destination passed to `Path.write_text()`. This method opens the destination for writing and truncates an existing file. The manifest-writing code does not perform any of the protections applied to image outputs: - It does not reject an existing destination unless overwrite permission was explicitly provided. - It does not prevent the manifest from targeting the input image or another user file. - It does not reject symbolic links. - It does not use exclusive file creation. - It accepts absolute paths and resolves relative paths into writable output locations. Consequently, any existing file writable by the process can be replaced with the generated JSON report. If the destination is a symbolic link, the write can follow that link and overwrite its target. This issue does not independently elevate privileges: the process must already have permission to write the selected target. However, it violates the documented expectation that destructive overwr ...[truncated 1323 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Refuse to overwrite an existing manifest by default and require an explicit `--overwrite` option. 2. Open new manifest files using exclusive creation mode: ```python with path.open("x", encoding="utf-8") as handle: json.dump(data, handle, indent=2) ``` 3. Reject symbolic-link destinations before writing. Where supported, use operating-system flags such as `O_NOFOLLOW` to reduce time-of-check/time-of-use risks. 4. Resolve and validate the manifest destination against an explicitly permitted output directory when arbitrary destinations are unnecessary. 5. Reject a manifest path that resolves to the input image or an image output. 6. Write through a securely created temporary file in the same destination directory and atomically replace the final path only when overwrite was explicitly authorized. 7. Add tests covering existing files, symbolic links, input-file collisions, output-file collisions, absolute paths, and concurrent destination creation. ]]>

T08 · Insecure Dependencies

Note
Location
SKILL.md:40
Finding
Unpinned Pillow Dependency Is Dynamically Resolved at Runtime<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:40`, `SKILL.md:45`, `SKILL.md:60`, and `pyproject.toml:8-10` **Vulnerability Type**: Unlocked third-party dependency resolution **Risk Level**: Low ### Vulnerable Configuration and Commands ```bash uv run --with "pillow>=10.0" scripts/strip.py "<path>" --dry-run ``` ```bash uv run --with "pillow>=10.0" scripts/strip.py "<path>" --manifest ``` ```bash pip install "pillow>=10.0" && python scripts/strip.py "<path>" --manifest ``` ```toml dependencies = [ "pillow>=10.0.0", ] ``` ### Technical Analysis The documented execution flow dynamically resolves any Pillow release satisfying the lower-bound constraint. The project does not provide a lockfile, an upper version boundary, package hashes, or an explicit trusted package index. As a result, the dependency installed and executed can change without any modification to the audited project. A future compromised, malicious, or behaviorally incompatible package artifact could therefore become part of the effective execution path after this version of the skill has already been reviewed. The package name itself is legitimate and no malicious dependency was observed in the supplied project. The risk arises from mutable, unverified dependency resolution rather than a confirmed malicious Pillow release. ### Attack Path 1. A user follows the documented `uv run --with` or `pip install` command. 2. The package resolver contacts its configured package index and selects a current release satisfying `pillow>=10.0`. 3. The selected artifact is installed without verification against a project-controlled lockfile or cryptographic hash. 4. `scripts/strip.py` imports Pillow: ```python from PIL import Image, ImageOps, UnidentifiedImageError ``` 5. Code from the resolved dependency executes in the Python process. 6. If the selected artifact or package source has been compromised, its code receives the same operating-system privileges and environment a ...[truncated 730 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin Pillow to a reviewed exact version instead of using only a minimum constraint. 2. Generate and commit a dependency lockfile so installations are reproducible. 3. Record and verify cryptographic hashes for downloaded package artifacts. 4. Configure installation commands to use an explicitly trusted package index. 5. Install dependencies in an isolated virtual environment with only the permissions needed for image processing. 6. Use automated dependency scanning and update the pinned version through a reviewed process. 7. If supporting a version range is necessary, test the complete supported range and apply a reviewed upper bound to prevent unexpected major-version adoption. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (3)

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill instructs the agent to write sanitized image copies and manifests, but it does not declare any explicit tool scope such as allowed-tools or permissions. That mismatch means an agent may infer broader file-write capability than is necessary, increasing the risk of unintended writes, overwrites, or use of file operations outside the intended directory structure. The privacy-focused context lowers suspicion of malicious intent, but the missing scope still weakens containment.

Vague Triggers

Medium
Confidence
89% confidence
Finding
The manifest trigger list includes generic phrases such as "clean metadata", "image privacy", "metadata cleanup", and especially "clean image", which do not clearly constrain invocation to metadata stripping. Without narrower scope or exclusion examples, these phrases could match unrelated everyday requests about editing or improving images.

Unverifiable Dependency: pillow has 16 known advisory(ies) (CVE-2016-2533 (Pillow buffer overflow in ImagingPcdDecode); CVE-2023-50447 (Arbitrary Code Execution in Pillow); CVE-2021-27922 (Pillow Uncontrolled Resource Consumption) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
90% confidence
Finding
The dependency is only lower-bounded as "pillow>=10.0.0", so installs may resolve to any future or repository-available version without demonstrating that a patched, vetted release is consistently used. Because this skill processes untrusted image files, a vulnerable Pillow build could expose the host to parser bugs such as denial of service or, in some versions, potentially code execution when crafted images are opened.

Static analysis

No suspicious patterns detected.