T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/gen.py:123
- Finding
- Stored HTML Injection in the Generated Image Gallery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/gen.py`, lines 123–151 **Vulnerability Type**: Stored HTML injection caused by unescaped user-controlled values **Risk Level**: Medium ### Vulnerable Code ```python def write_gallery(out_dir: Path, items: list[dict]) -> None: thumbs = "\n".join( [ f""" <figure> <a href="{it["file"]}"><img src="{it["file"]}" loading="lazy" /></a> <figcaption>{it["prompt"]}</figcaption> </figure> """.strip() for it in items ] ) html = f"""<!doctype html> <meta charset="utf-8" /> <title>openai-image-gen</title> <style> :root {{ color-scheme: dark; }} body {{ margin: 24px; font: 14px/1.4 ui-sans-serif, system-ui; background: #0b0f14; color: #e8edf2; }} h1 {{ font-size: 18px; margin: 0 0 16px; }} .grid {{ display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; }} figure {{ margin: 0; padding: 12px; border: 1px solid #1e2a36; border-radius: 14px; background: #0f1620; }} img {{ width: 100%; height: auto; border-radius: 10px; display: block; }} figcaption {{ margin-top: 10px; color: #b7c2cc; }} code {{ color: #9cd1ff; }} </style> <h1>openai-image-gen</h1> <p>Output: <code>{out_dir.as_posix()}</code></p> <div class="grid"> {thumbs} </div> """ ``` ### Technical Analysis The gallery generator constructs HTML through direct string interpolation. The prompt supplied through `--prompt` is stored in each item and inserted verbatim into the `<figcaption>` element. The user-controlled output-directory path is likewise inserted into a `<code>` element without HTML escaping. Consequently, HTML metacharacters in either value are interpreted as markup rather than displayed as text. For example, a prompt containing: ```html <img src=x onerror=alert(document.domain)> ``` would create an executable event handler in the generated `index.html`. This is a stored HTML injection vulnerability because the malicious value is persisted in ...[truncated 2082 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Escape every dynamic value before inserting it into HTML. Use `html.escape(value, quote=True)` for text and attribute contexts: ```python import html def write_gallery(out_dir: Path, items: list[dict]) -> None: thumbs = "\n".join( f""" <figure> <a href="{html.escape(it["file"], quote=True)}"> <img src="{html.escape(it["file"], quote=True)}" loading="lazy" /> </a> <figcaption>{html.escape(it["prompt"], quote=True)}</figcaption> </figure> """.strip() for it in items ) escaped_out_dir = html.escape(out_dir.as_posix(), quote=True) ``` 2. Insert `escaped_out_dir` instead of the raw path: ```python <p>Output: <code>{escaped_out_dir}</code></p> ``` 3. Prefer a maintained template engine with automatic HTML escaping if gallery generation becomes more complex. 4. Continue constraining generated filenames to a safe character set. Explicitly allowlist output formats such as `png`, `jpeg`, and `webp` before using them as filename extensions. 5. Add regression tests that generate galleries from prompts and paths containing characters such as `<`, `>`, `"`, `'`, and `&`. Verify that these appear as encoded text and cannot create elements or event handlers. 6. Consider adding a restrictive Content Security Policy to the generated gallery as defense in depth: ```html <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline';"> ``` HTML escaping remains mandatory because a Content Security Policy alone does not prevent all markup injection or deceptive-content attacks. ]]>
