fix(security): escape user input in HTML gallery to prevent stored XSS (#16958)

* Security/openai-image-gen: escape HTML gallery user input

* Tests/openai-image-gen: add gallery XSS regression coverage

* Changelog: add openai-image-gen XSS hardening note

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
CornBrother0x
2026-02-23 01:39:58 -05:00
committed by GitHub
parent 5ad5ea53cd
commit f3adf142c1
3 changed files with 55 additions and 3 deletions

View File

@@ -9,6 +9,7 @@ import re
import sys
import urllib.error
import urllib.request
from html import escape as html_escape
from pathlib import Path
@@ -131,8 +132,8 @@ def write_gallery(out_dir: Path, items: list[dict]) -> None:
[
f"""
<figure>
<a href="{it["file"]}"><img src="{it["file"]}" loading="lazy" /></a>
<figcaption>{it["prompt"]}</figcaption>
<a href="{html_escape(it["file"], quote=True)}"><img src="{html_escape(it["file"], quote=True)}" loading="lazy" /></a>
<figcaption>{html_escape(it["prompt"])}</figcaption>
</figure>
""".strip()
for it in items
@@ -152,7 +153,7 @@ def write_gallery(out_dir: Path, items: list[dict]) -> None:
code {{ color: #9cd1ff; }}
</style>
<h1>openai-image-gen</h1>
<p>Output: <code>{out_dir.as_posix()}</code></p>
<p>Output: <code>{html_escape(out_dir.as_posix())}</code></p>
<div class="grid">
{thumbs}
</div>

View File

@@ -0,0 +1,50 @@
"""Tests for write_gallery HTML escaping (fixes #12538 - stored XSS)."""
import tempfile
from pathlib import Path
from gen import write_gallery
def test_write_gallery_escapes_prompt_xss():
with tempfile.TemporaryDirectory() as tmpdir:
out = Path(tmpdir)
items = [{"prompt": '<script>alert("xss")</script>', "file": "001-test.png"}]
write_gallery(out, items)
html = (out / "index.html").read_text()
assert "<script>" not in html
assert "&lt;script&gt;" in html
def test_write_gallery_escapes_filename():
with tempfile.TemporaryDirectory() as tmpdir:
out = Path(tmpdir)
items = [{"prompt": "safe prompt", "file": '" onload="alert(1)'}]
write_gallery(out, items)
html = (out / "index.html").read_text()
assert 'onload="alert(1)"' not in html
assert "&quot;" in html
def test_write_gallery_escapes_ampersand():
with tempfile.TemporaryDirectory() as tmpdir:
out = Path(tmpdir)
items = [{"prompt": "cats & dogs <3", "file": "001-test.png"}]
write_gallery(out, items)
html = (out / "index.html").read_text()
assert "cats &amp; dogs &lt;3" in html
def test_write_gallery_normal_output():
with tempfile.TemporaryDirectory() as tmpdir:
out = Path(tmpdir)
items = [
{"prompt": "a lobster astronaut, golden hour", "file": "001-lobster.png"},
{"prompt": "a cozy reading nook", "file": "002-nook.png"},
]
write_gallery(out, items)
html = (out / "index.html").read_text()
assert "a lobster astronaut, golden hour" in html
assert 'src="001-lobster.png"' in html
assert "002-nook.png" in html