T09 · Insecure Skill Coding Practices
Error
- Location
- prompt_browse.py:366
- Finding
- Stored JavaScript Injection Through Unvalidated Imported Prompt IDs<![CDATA[ ## Vulnerability Details **File Location**: `prompt_sync.py:119-130` and `prompt_browse.py:329-366` **Vulnerability Type**: Stored JavaScript injection in generated HTML **Risk Level**: High ### Vulnerable Code The import process accepts an attacker-controlled prompt ID without validating its format or type: ```python # prompt_sync.py:119-130 with open(import_path, 'r', encoding='utf-8') as f: import_data = json.load(f) existing_ids = {p['id']: i for i, p in enumerate(vault['prompts'])} stats = { 'total': len(import_data.get('prompts', [])), 'added': 0, 'skipped': 0, 'replaced': 0, 'errors': [] } for prompt in import_data.get('prompts', []): prompt_id = prompt.get('id') ``` The imported ID is subsequently embedded directly into an inline JavaScript event handler: ```python # prompt_browse.py:329-366 for p in by_category[cat]: prompt_id = p.get('id', '') name = p.get('name', 'Untitled') text = p.get('text', '') rating = p.get('rating', 0) stars = '⭐' * rating if rating else '☆☆☆☆☆' tags = p.get('tags', []) author = p.get('author', 'Unknown') usage_count = p.get('usage_count', 0) notes = p.get('notes', '') models = p.get('model_compat', '') tags_html = ''.join(f'<span class="tag">{html.escape(tag)}</span>' for tag in tags) html_content += f""" <div class="prompt-card" data-tags="{html.escape(','.join(tags))}"> <div class="prompt-header"> <div class="prompt-name">{html.escape(name)}</div> <div class="prompt-rating">{stars}</div> </div> <div class="prompt-meta"> <span>ID: <code>{html.escape(prompt_id)}</code></span> <span>By: {html.escape(author)}</span> <span>Used: {usage_count} times</span> """ if models: html_content += f' <span>Models: {html.escape(models)}</span>\n' html_content += ' </div>\n' if tags: ...[truncated 3037 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Strictly validate imported IDs** Enforce the same format used by the native ID generator: ```python import re if not isinstance(prompt_id, str) or not re.fullmatch(r'[0-9a-f]{12}', prompt_id): stats['errors'].append("Prompt has an invalid ID") continue ``` 2. **Remove inline JavaScript event handlers** Store identifiers in an escaped data attribute: ```python html_content += ( f'<button class="copy-btn" ' f'data-prompt-id="{html.escape(prompt_id, quote=True)}">' f'Copy to Clipboard</button>\n' ) ``` Register the event handler separately: ```javascript document.querySelectorAll('.copy-btn').forEach(button => { button.addEventListener('click', event => { copyPrompt(event.currentTarget.dataset.promptId, event.currentTarget); }); }); ``` 3. **Avoid searching IDs through unrestricted text content** Associate each card with an exact, validated `data-prompt-id` value and select the matching card by that value. This prevents ambiguous matches and separates data from executable code. 4. **Validate the complete import schema** Before persistence, verify that each prompt is an object and that fields such as `id`, `name`, `text`, `category`, `tags`, `rating`, and `usage_count` have expected types and reasonable size limits. 5. **Apply a restrictive Content Security Policy** Generated pages should prohibit inline scripts and unnecessary network access. Move JavaScript into a separate local file or use a nonce/hash-based policy if a standalone file is required. CSP should be treated as defense in depth rather than a replacement for safe construction. 6. **Add regression tests** Test imported IDs containing single quotes, double quotes, angle brackets, backslashes, newlines, and JavaScript syntax. Verify that generated HTML contains no executable attacker-controlled content and that legitimate copy functiona ...[truncated 29 chars]
