T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/image_generator.py:297
- Finding
- Unescaped Repository URL Enables SVG/XML Injection During Cover Generation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/image_generator.py:194-199`, `scripts/image_generator.py:297-300`, with rendering sinks at `scripts/image_generator.py:328-330`, `scripts/image_generator.py:343-348`, and `scripts/image_generator.py:360-365` **Vulnerability Type**: SVG/XML injection through unescaped user-controlled data **Risk Level**: High ### Vulnerable Code ```python repo_name = repo_data.get('repo', 'Unknown') description = repo_data.get('description', '') language = repo_data.get('language', 'Unknown') or 'Unknown' stars = repo_data.get('stars', 0) github_url = repo_data.get('url', '') or f"github.com/{repo_data.get('owner', '')}/{repo_name}" # Escape XML special chars display_name = escape_xml(truncate_text(repo_name, 22)) desc_text = escape_xml(truncate_text(description, 80)) lang_text = escape_xml(language) ``` The repository URL is subsequently inserted into the SVG without XML escaping: ```python <!-- GitHub URL --> <text x="540" y="1220" font-family="Courier New, monospace" font-size="20" fill="{accent_color}" text-anchor="middle"> {github_url} </text> ``` The generated SVG is then processed by one of several renderers: ```python cairosvg.svg2png( url=svg_path, write_to=output_path, output_width=COVER_WIDTH, output_height=COVER_HEIGHT ) ``` ```python result = subprocess.run( ['convert', '-background', 'none', svg_path, '-resize', f'{COVER_WIDTH}x{COVER_HEIGHT}!', '-density', '150', output_path], capture_output=True, text=True, timeout=30, env=env ) ``` ```python result = subprocess.run( ['inkscape', svg_path, '--export-type=png', f'--export-filename={output_path}', f'--export-width={COVER_WIDTH}', f'--export-height={COVER_HEIGHT}', '--export-dpi=150'], capture_output=True, text=True, timeout=30 ) ``` ### Technical Analysis The `github_url` value originates from the user-supplied command-line URL and is retained as `repo_data['url']`. Unlike ...[truncated 2728 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Escape every dynamic value inserted into SVG XML, including the URL: ```python github_url_text = escape_xml( truncate_text( f"github.com/{repo_data.get('owner', '')}/{repo_name}", 100 ) ) ``` 2. Do not display the original user-supplied URL. Reconstruct the display URL exclusively from validated owner and repository components returned by the GitHub API. 3. Tighten URL validation so that only these forms are accepted: ```text https://github.com/<owner>/<repository> github.com/<owner>/<repository> <owner>/<repository> ``` Reject query strings, fragments, control characters, XML metacharacters, credentials, and unexpected trailing path components. 4. Use a standard URL parser rather than permissive regular expressions. Validate the hostname exactly as `github.com`. 5. Configure SVG renderers to prohibit: - External network resources. - Local-file references. - Script execution. - Unsafe XML entities. - Unbounded filters, dimensions, and resource consumption. 6. Run image conversion in a sandbox with: - No network access. - A restricted temporary directory. - CPU, memory, file-size, and execution-time limits. - No access to sensitive home-directory files. 7. Add regression tests containing XML metacharacters and attempted element injection in every repository field. ]]>
