T09 · Insecure Skill Coding Practices
- Location
scripts/scaffold_addon.py:38- Finding
Unescaped Add-on Metadata Allows Python Source Injection
- Content
View full analysis
Vulnerability Details
File Location:
scripts/scaffold_addon.py:38-71andscripts/scaffold_addon.py:139-163
Vulnerability Type: Generated-code injection through unsafe string interpolation
Risk Level: MediumVulnerable Code
python def build_init_py( addon_name: str, module_name: str, author: str, description: str, category: str, addon_version: tuple[int, int, int], blender_min_version: tuple[int, int, int], ) -> str: return dedent( f'''\ bl_info = {{ "name": "{addon_name}", "author": "{author}", "version": {tuple_literal(addon_version)}, "blender": {tuple_literal(blender_min_version)}, "location": "View3D > Sidebar", "description": "{description}", "warning": "", "doc_url": "", "category": "{category}", }} import bpy from .operators import classes as operator_classes from .ui import classes as ui_classes classes = (*operator_classes, *ui_classes) def register(): for cls in classes: bpy.utils.register_class(cls) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls) if __name__ == "__main__": register() ''' )The same unsafe interpolation pattern is used when generating the user-interface module:
python def build_ui_py(module_name: str, addon_name: str) -> str: class_prefix = "".join(part.capitalize() for part in module_name.split("_")) return dedent( f'''\ import bpy class {class_prefix}_PT_panel(bpy.types.Panel): bl_label = "{addon_name}" bl_idname = "VIEW3D_PT_{module_name}_panel" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Tool" def draw(self, context): ...[truncated 3843 chars]- Remediation
View remediation
Remediation Suggestions
- Generate safe Python literals. Use
repr()for every string embedded in generated Python rather than manually surrounding values with quotation marks:
python def python_string_literal(value: str) -> str: return repr(value)Then construct metadata entries without hard-coded surrounding quotes:
python bl_info = {{ "name": {addon_name!r}, "author": {author!r}, "version": {tuple_literal(addon_version)}, "blender": {tuple_literal(blender_min_version)}, "location": "View3D > Sidebar", "description": {description!r}, "warning": "", "doc_url": "", "category": {category!r}, }}Apply the same protection to the generated panel label:
python bl_label = {addon_name!r}-
Validate metadata separately from serialization. Define reasonable maximum lengths and reject NUL bytes and disallowed control characters. Serialization with
repr()remains necessary even after validation. -
Compile all generated modules before reporting success. After writing the files, invoke Python's
compile()function on their contents or runpy_compilethrough its Python API. Avoid relying solely on external post-generation validation. -
Fail safely. If validation or compilation fails, remove incomplete generated files or generate into a temporary directory and move the package into place only after every file passes validation.
-
Add regression tests. Test all metadata parameters with:
- Double and single quotes.
- Backslashes.
- Newline and carriage-return characters.
- Unicode characters.
- Strings resembling Python expressions or dictionary terminators.
- Extremely long input.
-
Verify behavior, not only syntax. Tests should assert that hostile-looking input appears only as inert string data in the generated abstract syntax tree and never creates additional top-level statements.
- Generate safe Python literals. Use
