Back to skill

Security audit

AI 衣橱搭配

Security checks for vulnerabilities and agentic risk

Overview

This outfit skill appears purpose-built rather than malicious, but it gives a remote API too much control over what the local machine downloads, processes, and opens.

Install only if you trust the aicloset API operator and are comfortable sending outfit context plus an API key to that service. Prefer running it in a constrained environment without access to sensitive local files or internal network services, and avoid using it on shared multi-user systems until URL validation, safer temporary-file handling, and opt-in preview behavior are added.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate_outfit.py:85
Finding
Unrestricted Retrieval and Processing of Server-Controlled URLs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_outfit.py`, lines 85–90 and 103–109 **Vulnerability Type**: Unrestricted URL retrieval, client-side SSRF, and unsafe processing of remote files **Risk Level**: Medium ### Vulnerable Code ```python def download(url, dest): try: request.urlretrieve(url, str(dest)) return True except Exception: return False def magick(*args): subprocess.run(["magick"] + list(args), check=True, capture_output=True) # ===== Compose each outfit ===== outfit_desc = [] for i, outfit in enumerate(outfits): ar = outfit.get("canvas_content", {}).get("aspect_ratio", 0.731) cw = int(CANVAS_H * ar) out_path = OUTDIR / f"outfit_{i+1}.png" magick("-size", f"{cw}x{CANVAS_H}", "xc:#FFFFFF", str(out_path)) products = sorted(outfit.get("product_list", []), key=lambda p: p.get("z_index", 0)) names = [] for p in products: names.append(p.get("class_name", "单品")) img_url = p.get("cutout_image", "") if not img_url: continue item_path = OUTDIR / "item_tmp.png" if not download(img_url, item_path): continue ``` ### Technical Analysis The `cutout_image` value originates from the remote outfit API response and is passed directly to `urllib.request.urlretrieve()` without validation. The implementation does not restrict: - The permitted URL scheme. - The source hostname. - Redirect destinations. - Loopback, private, link-local, or reserved IP addresses. - Local-resource schemes such as `file:`. - Response content type. - Download size or image dimensions. Consequently, a compromised or malicious API endpoint can cause the client to make requests to resources that are not part of the intended image service. This creates a client-side SSRF primitive and may permit access to local files supported by the URL handler. The downloaded data is subsequently supplied to ImageMagick. An attacker can therefore also ...[truncated 1933 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Permit only `https` URLs from an explicit allowlist of trusted image-hosting domains. 2. Reject URLs containing credentials, unexpected ports, or unsupported schemes. 3. Resolve the destination hostname and reject loopback, private, link-local, multicast, unspecified, and reserved addresses for both IPv4 and IPv6. 4. Disable automatic redirects or validate every redirect target using the same scheme, hostname, and resolved-address rules. 5. Stream downloads into an exclusively created temporary file while enforcing a strict byte limit. 6. Require an expected image MIME type and verify the downloaded file using an image parser before invoking ImageMagick. 7. Enforce maximum image dimensions, frame counts, decode time, memory consumption, and disk usage. 8. Run ImageMagick with a restrictive `policy.xml`, disable unnecessary coders and delegates, and keep the installed version patched. 9. Consider processing untrusted images in an isolated sandbox without access to sensitive files, internal networks, or unnecessary system privileges. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate_outfit.py:23
Finding
Predictable Temporary Directory Enables Symlink-Based File Clobbering<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_outfit.py`, lines 23–24 and 107–109 **Vulnerability Type**: Insecure temporary-file creation and symbolic-link following **Risk Level**: Medium ### Vulnerable Code ```python OUTDIR = Path(tempfile.gettempdir()) / f"aicloset_outfit_{int(time.time())}" OUTDIR.mkdir(parents=True, exist_ok=True) ``` ```python item_path = OUTDIR / "item_tmp.png" if not download(img_url, item_path): continue ``` The destination is ultimately opened by the following function: ```python def download(url, dest): try: request.urlretrieve(url, str(dest)) return True except Exception: return False ``` ### Technical Analysis The output directory name is based solely on the current Unix timestamp in seconds. A local attacker can predict the path and create it before the Skill runs. The use of `exist_ok=True` causes the script to accept an attacker-created directory without checking its ownership, permissions, or whether it is a symbolic link. The downloaded product image always uses the fixed filename `item_tmp.png`. `request.urlretrieve()` writes to that path using ordinary file-opening behavior, which follows symbolic links. If an attacker pre-creates the predictable directory and places `item_tmp.png` as a symbolic link to another file, the Skill can overwrite the target with its own user privileges. The same predictable directory also contains generated output files, increasing the general risk of local filesystem races and output manipulation. ### Attack Path 1. A local attacker determines or predicts when the Skill will run. 2. The attacker pre-creates `/tmp/aicloset_outfit_<timestamp>` for the expected timestamp and configures it so the victim process can write within it. 3. The attacker creates `item_tmp.png` inside that directory as a symbolic link to a file writable by the Skill user. 4. The Skill computes the same timestamp-based path. 5. `mkdir(parents=True, exist ...[truncated 1011 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create the output directory with `tempfile.TemporaryDirectory()` or `tempfile.mkdtemp()` rather than constructing a timestamp-based name. 2. Ensure the directory is created atomically with permissions restricted to the current user. 3. Do not use `exist_ok=True` for security-sensitive temporary directories. 4. Verify that the resulting path is a real directory owned by the current user and is not a symbolic link. 5. Create each downloaded file with a unique unpredictable name. 6. Open temporary files atomically using exclusive creation and no-follow semantics where supported, such as `O_CREAT | O_EXCL | O_NOFOLLOW`. 7. Pass an already secured file descriptor to the download implementation instead of reopening a pathname. 8. Apply equivalent protections to generated output files so existing links cannot redirect ImageMagick output. 9. Remove the temporary directory through a managed cleanup context after the image has been delivered. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (10)

Tainted flow: 'req' from os.environ.get (line 50, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
result = None
for attempt in range(2):
    try:
        with request.urlopen(req, timeout=30) as resp:
            result = json.loads(resp.read().decode("utf-8"))
        break
    except Exception as e:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill invokes external scripts and relies on environment variables, shell execution, file access, and network access, but it does not declare any explicit tool scope or allowed-tools boundary. That increases the blast radius of execution because hosts and reviewers cannot easily constrain what the skill is permitted to do, especially given the instruction that the agent must execute a local Python script.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill description says it will use date, city, weather, and style preferences to call an AI wardrobe API, but it does not clearly warn users that their provided context will be transmitted to a third-party external service. This is a privacy and consent issue, particularly in chat environments where users may assume recommendations are generated locally.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The module docstring and subsequent user-facing messages are written only in Chinese, indicating the skill is designed to communicate in a fixed language. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale limitation is explicitly justified, which it is not here.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
return False

def magick(*args):
    subprocess.run(["magick"] + list(args), check=True, capture_output=True)

# ===== 合成每套搭配 =====
outfit_desc = []
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The skill auto-opens a generated image using platform-specific desktop commands even though its stated purpose is only to generate and present outfit recommendations. This broadens the skill's behavior into host interaction without explicit consent, which is particularly risky for agent frameworks, remote sessions, and automation pipelines where desktop launch behavior is unexpected and can cause misuse or disruption.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# ===== 跨平台打开图片 =====
system = platform.system()
if system == "Darwin":
    subprocess.run(["open", str(overview_path)])
elif system == "Windows":
    os.startfile(str(overview_path))
elif system == "Linux":
Confidence
91% confidence
Finding
The skill automatically launches the generated file with the local desktop opener, causing side effects on the host outside the stated task of generating outfit recommendations. In agent or server environments, auto-opening local files can trigger unwanted application execution, leak activity to a GUI session, or be abused as an unexpected capability escalation from content generation to host interaction.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
elif system == "Windows":
    os.startfile(str(overview_path))
elif system == "Linux":
    subprocess.run(["xdg-open", str(overview_path)])
Confidence
91% confidence
Finding
This Linux-specific xdg-open call automatically opens the generated image on the host, which is unnecessary for the core skill purpose and creates an avoidable local side effect. In desktop environments, xdg-open delegates to system handlers and may launch external applications, making the skill more capable than advertised and riskier in shared or automated execution contexts.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
The natural-language changelog states that the script is immediately marked ready and provides a 'friendly prompt' in Chinese-facing wording, with the entire file written only in Chinese and no indication that language is configurable or limited to a China-specific deployment. Under the language/locale policy, this suggests a fixed locale presentation without documented user opt-in or justification.

Missing User Warnings

Low
Confidence
88% confidence
Finding
The skill states that in CLI mode the script will automatically open generated images using a system preview command, but this behavior is not prominently disclosed as a side effect before execution. Automatic opening of local files can surprise users, disrupt workflows, and in some environments trigger unintended application launches or handling of untrusted content.

Static analysis

No suspicious patterns detected.