mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
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:
@@ -38,6 +38,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Agents/Compaction: count auto-compactions only after a non-retry `auto_compaction_end`, keeping session `compactionCount` aligned to completed compactions.
|
- Agents/Compaction: count auto-compactions only after a non-retry `auto_compaction_end`, keeping session `compactionCount` aligned to completed compactions.
|
||||||
|
- Security/Skills: escape user-controlled prompt, filename, and output-path values in `openai-image-gen` HTML gallery generation to prevent stored XSS in generated `index.html` output. (#12538) Thanks @CornBrother0x.
|
||||||
- Security/OTEL: redact sensitive values (API keys, tokens, credential fields) from diagnostics-otel log bodies, log attributes, and error/reason span fields before OTLP export. (#12542) Thanks @brandonwise.
|
- Security/OTEL: redact sensitive values (API keys, tokens, credential fields) from diagnostics-otel log bodies, log attributes, and error/reason span fields before OTLP export. (#12542) Thanks @brandonwise.
|
||||||
- Security/CLI: redact sensitive values in `openclaw config get` output before printing config paths, preventing credential leakage to terminal output/history. (#13683) Thanks @SleuthCo.
|
- Security/CLI: redact sensitive values in `openclaw config get` output before printing config paths, preventing credential leakage to terminal output/history. (#13683) Thanks @SleuthCo.
|
||||||
- Install/Discord Voice: make `@discordjs/opus` an optional dependency so `openclaw` install/update no longer hard-fails when native Opus builds fail, while keeping `opusscript` as the runtime fallback decoder for Discord voice flows. (#23737, #23733, #23703) Thanks @jeadland, @Sheetaa, and @Breakyman.
|
- Install/Discord Voice: make `@discordjs/opus` an optional dependency so `openclaw` install/update no longer hard-fails when native Opus builds fail, while keeping `opusscript` as the runtime fallback decoder for Discord voice flows. (#23737, #23733, #23703) Thanks @jeadland, @Sheetaa, and @Breakyman.
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
from html import escape as html_escape
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
@@ -131,8 +132,8 @@ def write_gallery(out_dir: Path, items: list[dict]) -> None:
|
|||||||
[
|
[
|
||||||
f"""
|
f"""
|
||||||
<figure>
|
<figure>
|
||||||
<a href="{it["file"]}"><img src="{it["file"]}" loading="lazy" /></a>
|
<a href="{html_escape(it["file"], quote=True)}"><img src="{html_escape(it["file"], quote=True)}" loading="lazy" /></a>
|
||||||
<figcaption>{it["prompt"]}</figcaption>
|
<figcaption>{html_escape(it["prompt"])}</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
""".strip()
|
""".strip()
|
||||||
for it in items
|
for it in items
|
||||||
@@ -152,7 +153,7 @@ def write_gallery(out_dir: Path, items: list[dict]) -> None:
|
|||||||
code {{ color: #9cd1ff; }}
|
code {{ color: #9cd1ff; }}
|
||||||
</style>
|
</style>
|
||||||
<h1>openai-image-gen</h1>
|
<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">
|
<div class="grid">
|
||||||
{thumbs}
|
{thumbs}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
50
skills/openai-image-gen/scripts/test_gen.py
Normal file
50
skills/openai-image-gen/scripts/test_gen.py
Normal 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 "<script>" 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 """ 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 & dogs <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
|
||||||
|
|
||||||
Reference in New Issue
Block a user