Back to skill

Security audit

StylePilot

Security checks for vulnerabilities and agentic risk

Overview

StylePilot is a coherent local wardrobe assistant, but its image import can copy arbitrary readable local files into persistent storage, so users should review it before installing.

Review this skill before installing. It appears local-only, but only use the image import with photo files you intentionally selected, and avoid passing paths to private documents, keys, profiles, or other sensitive files. A safer version should reject non-image extensions, validate actual image content, use non-deterministic destination names, and provide clear delete/export controls for stored wardrobe data.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
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: Medium

Vulnerable 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 .jpg and 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

  1. An attacker or untrusted integration obtains the ability to invoke the local add command.

  2. 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

  1. 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 ''
    
  2. 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.

  3. 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.

  4. Restrict source locations where feasible. Accept images only from an approved upload or temporary directory rather than arbitrary filesystem paths supplied through the CLI.

  5. 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.

  6. 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.

  7. Add regression tests. Verify that text files, renamed non-image files, malformed images, oversized files, directories, and symbolic-link edge cases are rejected.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (9)

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · tests/test_wardrobe_smoke.py (reported line 26)May include surrounding context.

python
def main():
    with tempfile.TemporaryDirectory() as td:
        env = os.environ.copy()
        env["WARDROBE_DB_PATH"] = os.path.join(td, "wardrobe.db")

        run_cmd(["init", "--json"], env)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill describes shell execution, local file writes, and likely environment access, but does not declare any explicit tool scope such as allowed tools or permissions. This creates unnecessary ambiguity around what the agent may execute, increasing the risk of over-broad command execution or file modification if the skill is invoked in a permissive runtime.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill instructs local storage of user clothing photos and metadata, but does not provide a clear user-facing notice about what data is retained, where it is stored, how long it is kept, or how users can delete it. Because photos and wardrobe metadata can reveal sensitive personal information such as lifestyle, travel habits, and appearance, silent retention creates a meaningful privacy risk.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The file's natural-language documentation and comments are written in Chinese, which imposes a specific language context without any visible opt-in or explanation that the skill is region-specific. Under the stated policy, forcing a specific language without user choice or justification is a natural-language policy violation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The module docstring and all user-facing descriptions are written only in Chinese, framing the skill as a personal wardrobe assistant for Chinese-language use. There is no indication that users may choose another language or that the locale restriction is required for a region-specific purpose, which matches the language/locale policy violation criteria.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · tests/test_wardrobe_smoke.py (reported line 13)May include surrounding context.

python
def run_cmd(args, env):
    proc = subprocess.run(
        ["bash", RUN_SH] + args,
        cwd=ROOT,
        env=env,

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

This code creates a data directory and selects a persistent SQLite database path, which causes local file-system writes as part of normal execution. Although the module has brief comments and docstrings, they describe purpose rather than warn users that running the skill will create and persist data on disk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The init_db function creates an images directory and initializes the SQLite schema, which modifies the local filesystem and persists user-related data. The existing comments are implementation notes in code, but they do not clearly disclose to users that executing this function will create storage locations and save data locally.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

This code uses Chinese-only natural-language literals for item names, categories, notes, and a Chinese comment, which reflects a fixed language choice in the skill's exercised interface. Under the policy, forcing a specific language without user opt-in can be a locale/language policy violation when no alternative or opt-in is documented in the file.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.