From 9bb646b3367c59a8e469752077332b26a52dd61f Mon Sep 17 00:00:00 2001 From: Artemii Fridriksen <111710573+TemaDeveloper@users.noreply.github.com> Date: Mon, 22 Jun 2026 13:34:41 -0400 Subject: [PATCH] fix: validate plugin names in tailwind config generator to prevent code injection (#275) The _format_plugins() method interpolated plugin names directly into require() statements without sanitization. A plugin name containing a single quote could break out of require() and inject arbitrary JavaScript that executes when Node.js loads the generated config. Add a strict regex allowlist matching valid npm package name patterns (optional @scope, alphanumeric/hyphen/underscore, optional subpath). Reject any plugin name that doesn't match before generating output. Closes #246 --- .../ui-styling/scripts/tailwind_config_gen.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/.claude/skills/ui-styling/scripts/tailwind_config_gen.py b/.claude/skills/ui-styling/scripts/tailwind_config_gen.py index 5109311..56cd277 100644 --- a/.claude/skills/ui-styling/scripts/tailwind_config_gen.py +++ b/.claude/skills/ui-styling/scripts/tailwind_config_gen.py @@ -8,10 +8,16 @@ Supports colors, fonts, spacing, breakpoints, and plugin recommendations. import argparse import json +import re import sys from pathlib import Path from typing import Any, Dict, List, Optional +# Valid npm package name pattern: optional @scope/, then package name with +# optional subpath. Only allows alphanumeric, hyphens, dots, underscores, +# and forward slashes — no quotes, parens, or semicolons. +_VALID_PLUGIN_NAME = re.compile(r'^(@[a-zA-Z0-9_-]+/)?[a-zA-Z0-9_-]+(/[a-zA-Z0-9_.-]+)*$') + class TailwindConfigGenerator: """Generate Tailwind CSS configuration files.""" @@ -230,13 +236,24 @@ module.exports = {{ """ def _format_plugins(self) -> str: - """Format plugins array for config.""" + """Format plugins array for config. + + Validates each plugin name against a strict allowlist pattern + to prevent code injection via crafted require() statements + (see: CWE-94). + """ if not self.config["plugins"]: return "" - plugin_requires = [ - f"require('{plugin}')" for plugin in self.config["plugins"] - ] + plugin_requires = [] + for plugin in self.config["plugins"]: + if not _VALID_PLUGIN_NAME.match(plugin): + raise ValueError( + f"Invalid plugin name: {plugin!r}. " + "Plugin names must be valid npm package names " + "(e.g. '@tailwindcss/typography')." + ) + plugin_requires.append(f"require('{plugin}')") return ", ".join(plugin_requires) def _indent_json(self, json_str: str, level: int) -> str: