Back to skill

Security audit

Dressup Playable Maker

Security checks for vulnerabilities and agentic risk

Overview

This skill mostly matches a playable-ad generator, but it has under-disclosed ad redirection and unsafe HTML generation risks that users should review before installing.

Review and edit generated ads before publishing. Do not run the generator on untrusted asset folders or copied command lines, and verify that the CTA URL matches your own campaign because the current template defaults to a specific app store destination.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/generate.py:47
Finding
Arbitrary JavaScript Injection Through Crafted Asset Filenames<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate.py:47-49, 64-69`; resulting unsafe DOM sinks in `assets/template/index.html:124-132, 176-178` **Vulnerability Type**: JavaScript injection caused by missing output encoding **Risk Level**: High ### Vulnerable Code ```python # scripts/generate.py:47-49 images = sorted([f for f in os.listdir(cat_dir) if f.endswith(('.png', '.jpg', '.jpeg'))]) config['categories'].append({ 'name': category, 'assets': [f'assets/{category}/{img}' for img in images] }) ``` ```python # scripts/generate.py:64-69 # Generate array string assets_array = ', '.join([f"'{a}'" for a in assets]) # Replace in HTML (find variable like hairAssets = [...]) pattern = rf"({cat_name}Assets\s*=\s*\[)[^\]]*(\])" replacement = rf"\1{assets_array}\2" html = re.sub(pattern, replacement, html, flags=re.IGNORECASE) ``` The generated values subsequently reach HTML-parsing sinks: ```javascript // assets/template/index.html:124-132 container.innerHTML = ""; assets.forEach((src, index) => { const btn = document.createElement("button"); btn.className = "item-btn " + category; btn.setAttribute("data-index", index); btn.innerHTML = "<img src='" + src + "'>"; btn.onclick = () => selectItem(category, src, index); }); ``` ```javascript // assets/template/index.html:176-178 if (category === "hair") document.getElementById("layer-front-hair").innerHTML = "<img src='" + src + "'>"; else if (category === "dress") document.getElementById("layer-dress").innerHTML = "<img src='" + src + "'>"; else if (category === "shoes") document.getElementById("layer-shoes").innerHTML = "<img src='" + src + "'>"; ``` ### Technical Analysis The generator treats filenames from the caller-controlled input directory as trusted JavaScript source text. It checks only whether each filename ends in `.png`, `.jpg`, or `.jpeg`. It does not restrict metacharacters or escape the filename before placing it inside a single-quoted JavaScript stri ...[truncated 2031 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Serialize JavaScript data with a real serializer instead of constructing source strings: ```python assets_array = json.dumps(assets, ensure_ascii=False) ``` 2. Replace the entire array expression using a replacement function so that backslashes in serialized data are not interpreted as regular-expression replacement references: ```python pattern = rf"({re.escape(cat_name)}Assets\s*=\s*)\[[^\]]*\]" html = re.sub( pattern, lambda match: match.group(1) + json.dumps(assets, ensure_ascii=False), html, flags=re.IGNORECASE, ) ``` 3. Restrict asset filenames to an explicit safe format, for example: ```python SAFE_FILENAME = re.compile(r"^[A-Za-z0-9._-]+$") ``` Reject any filename that does not match rather than silently copying it. 4. Check extensions case-insensitively and verify actual file types using image decoding or magic-byte inspection. Extension validation alone does not establish that a file is an image. 5. Remove the `innerHTML` image sinks. Construct elements through DOM APIs: ```javascript const image = document.createElement("img"); image.src = src; btn.replaceChildren(image); ``` 6. Add regression tests using filenames containing quotes, backslashes, newlines, angle brackets, Unicode separators, and regular-expression replacement metacharacters. Confirm that generated output remains syntactically valid and that none of those values can create script or markup nodes. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/generate.py:78
Finding
Arbitrary HTML and JavaScript Injection Through the Primary Color Argument<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate.py:78-81, 91-92, 110-115` **Vulnerability Type**: HTML injection through an unvalidated CSS value **Risk Level**: High ### Vulnerable Code ```python # scripts/generate.py:78-81 # Inject custom CSS if provided if custom_css: css_block = f"<style>{custom_css}</style>" html = html.replace('</head>', f'{css_block}</head>') ``` ```python # scripts/generate.py:91-92 parser.add_argument('--scale', type=float, default=1.1, help='Character scale factor') parser.add_argument('--primary-color', default='#ff69b4', help='Primary theme color') ``` ```python # scripts/generate.py:110-115 # Generate custom CSS custom_css = f""" :root {{ --character-scale: {args.scale}; --primary-color: {args.primary_color}; }} """ ``` ### Technical Analysis The `--primary-color` command-line value is unrestricted text. It is interpolated into a CSS block, after which the complete block is concatenated directly into the HTML document. HTML parsing rules, rather than CSS parsing rules, determine where a `<style>` element ends. Consequently, a value containing a closing style tag can leave the CSS context and introduce arbitrary HTML, including a script element. For example, a value shaped like the following would cross the intended context boundary: ```text </style><script>/* attacker-controlled JavaScript */</script><style> ``` The `--scale` argument is parsed as a Python float and therefore does not expose the same direct injection primitive. The vulnerable input is specifically `--primary-color`. ### Attack Path 1. An attacker controls or influences a build command, CI configuration, wrapper script, or copied generation instructions. 2. The attacker supplies a crafted `--primary-color` value containing a closing `style` tag and attacker-controlled HTML. 3. `main()` interpolates that value into `custom_css`. 4. `update_template()` wraps the text in another `<style>` element and ins ...[truncated 664 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate `--primary-color` against an explicit set of supported CSS color formats. If only hexadecimal colors are required, use a strict full-string match: ```python COLOR_RE = re.compile(r"^#[0-9A-Fa-f]{6}(?:[0-9A-Fa-f]{2})?$") if not COLOR_RE.fullmatch(args.primary_color): parser.error("--primary-color must be a six- or eight-digit hexadecimal color") ``` 2. Prefer a mature CSS color parser if RGB, HSL, named colors, or other formats must be supported. Accept a value only if parsing succeeds and then emit a canonical representation. 3. Do not attempt to fix this issue solely with generic HTML escaping. Validate according to the destination grammar and reject structural characters that are unnecessary for a color value. 4. Keep configuration separate from HTML source. Serialize validated configuration as JSON and apply the value through a DOM style property: ```javascript document.documentElement.style.setProperty("--primary-color", validatedColor); ``` 5. Add negative tests for `</style>`, quotes, semicolons, CSS functions, comments, newlines, and malformed color values. 6. Treat generation arguments in CI and automation as untrusted configuration unless they come from a reviewed, access-controlled source. ]]>

other

Warning
Location
assets/template/index.html:16
Finding
Generated Advertisements Contain a Fixed External CTA Destination<![CDATA[ ## Vulnerability Details **File Location**: `assets/template/index.html:16-22` **Vulnerability Type**: Hardcoded external redirect and traffic destination **Risk Level**: Medium ### Vulnerable Code ```javascript // CTA function window.install = function() { console.log('install called'); if (typeof mraid !== 'undefined' && mraid.open) { mraid.open('https://play.google.com/store/apps/details?id=com.fashion.contest.dressup.idol.style.shiningme'); } else { window.open('https://play.google.com/store/apps/details?id=com.fashion.contest.dressup.idol.style.shiningme', '_blank'); } }; ``` The CTA is reached from the template as follows: ```javascript function downloadGame() { window.install(); } ``` ### Technical Analysis The purportedly reusable playable-ad generator embeds one fixed Google Play URL in every output. The destination points to package: ```text com.fashion.contest.dressup.idol.style.shiningme ``` The generator does not require or expose a destination URL argument. As a result, generating a playable for another product does not update the install destination. This is not an open redirect because users cannot freely select the target through the current interface. It is a hardcoded external traffic diversion risk. The behavior is externally visible only after the CTA is activated, making it possible for an operator to generate and publish an otherwise customized ad without noticing that its install traffic still goes to the embedded application. ### Attack Path 1. An operator uses the documented generator with assets for a different application. 2. The generator copies the template without changing `window.install()`. 3. The operator previews the dress-up interaction but does not inspect or activate the final CTA. 4. The generated playable is uploaded to an advertising platform. 5. A user completes the interaction and clicks “DOWNLOAD NOW.” 6. `window.install()` invokes `mraid.open()` or `window.open( ...[truncated 685 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the fixed store URL from the reusable template. 2. Require an explicit CTA destination during generation: ```python parser.add_argument("--cta-url", required=True, help="HTTPS application-store URL") ``` 3. Parse the supplied URL and enforce: - The `https` scheme. - An explicit allowlist of approved application-store hostnames. - No embedded credentials. - A package or application identifier matching the campaign configuration. 4. Serialize the validated URL using `json.dumps()` before inserting it into JavaScript. Do not place raw command-line text into script source. 5. Fail closed if the CTA URL is absent or invalid; do not retain a default destination belonging to a particular application. 6. Print the final CTA destination prominently in the generation summary and include it in generated metadata so reviewers can verify it before publication. 7. Add an automated output test that activates or inspects `window.install()` and confirms that the generated destination equals the requested campaign URL. ]]>
Vulnerability Patterns
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (6)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill claims to create dress-up playable ads, but the referenced output includes an MRAID runtime/API surface with functions for opening external URLs and exposing device/ad-container capabilities. That mismatch matters because it can conceal navigation, tracking, or privileged ad-environment interactions beyond simple asset templating, increasing the risk of misuse or reviewer confusion.

Ae1

High
Category
analysis-evasion
Content
Modify `steps` array in `index.html` to change clothing categories:
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
Modify `steps` array in `index.html` to change clothing categories:
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

YARA rule 'privilege_escalation_tools': Privilege escalation tools and techniques [hacktools]

High
Category
YARA Match
Content
QVd/TUvncxzBCcAA0iEnTZxSGF1Xykei4QeQ6ouIIXEwJSp5fimzTKJV2Dl42srVl2m/FEZugLawQqM4JouaxEKoRZNd2HM11FS5GKSFWaMtL0uzbCA6ARtMxtshJOiWEqKbjaI7CcQJXY9ue78TPPS9uXFBIzBrTkfhtLTyhHCNJ3CeC4jiyKLLfs6ggxDnlGfMXzMAkPUBD6uxfdCIjnjjjNFczBe4z6dO/ajk1L/5ex5+UXlOEqNw34quXHEPiVEoI1XXTF9ow1wbQsApDSFgyWHDosq2J3A9IfvAfvxdMZ8Ghc98SjNANOZXEBCdQR2OOHqCBFQYS0xMppaL2k+Cw+MzcP6+ejOYJtbhvSbLrqiSmO1oxiXN7ABrajMFFpaRQajiOwn1PL7fPSPyx8T9Zq5iaKxGi68TSzOLIzgQANLQZw4piyYz7sqbAfYfP969nio4FB/02N2QtlAghlsRpRmYocgOg4dWSI5RYcEjct9RyZkE9zZpB/LevHFLohgJhdDG0MuzeFwBoeCw5DC/QZNP4oTSarqsYkSx/Wx4VHEdUKSW6EqqpWejCAjQFVueQPE8zFev70ujERD3GZwghX/2WpCiGpMfb11e0LksAQHOYMTDD+Z6jSDr3FVpO1bfbYm2e13X9S/5YhihKnCVrQnEctm8ANI/CqIwVlWSDql+/cOSH1VeO6MzwqvSxI2upVJVERRENx9EHjZmRAIDmURiT0YOSqH61wtBppt51RbqoqzRODj2eS2G5YTq2WGyfibIoQHPpHD+I2r701dYtvZis9wVhFrUoJZT9xY1ZlhuaJhXZTBsANJmOWSkSRvqXHRiqVBxe99C+IOqUfEQNU7ZDNTMEu8wBmtCkmdmi4tLPjVRFTU2r+6fmf8kNopuhJi/PzsKuL4DmtGx0UXIsonKMIsfzbfUalSefGbZtrBixIAEATWpeumhpBrU4QdGs/F887AvylNTocq8nFAdimQLQxKYPy5BQoZw
Confidence
75% confidence
Finding
YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
The entire guide is written in Chinese and does not indicate that other languages are supported or that Chinese is a required locale for a region-specific workflow. Under the policy, forcing a specific language without user opt-in can be a natural-language policy violation.

Static analysis

No suspicious patterns detected.