VYPR
Low severityNVD Advisory· Published Mar 19, 2026· Updated Mar 21, 2026

OpenClaw < 2026.2.23 - HTML Injection via Unvalidated Image MIME Type in Data-URL Interpolation

CVE-2026-32040

Description

OpenClaw versions prior to 2026.2.23 contain an html injection vulnerability in the HTML session exporter that allows attackers to execute arbitrary javascript by injecting malicious mimeType values in image content blocks. Attackers can craft session entries with specially crafted mimeType attributes that break out of the img src data-URL context to achieve cross-site scripting when exported HTML is opened.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
openclawnpm
< 2026.2.232026.2.23

Affected products

1

Patches

1
f3adf142c195

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

https://github.com/openclaw/openclawCornBrother0xFeb 23, 2026via ghsa
3 files changed · +55 3
  • CHANGELOG.md+1 0 modified
    @@ -38,6 +38,7 @@ Docs: https://docs.openclaw.ai
     ### Fixes
     
     - 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/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.
    
  • skills/openai-image-gen/scripts/gen.py+4 3 modified
    @@ -9,6 +9,7 @@
     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>
    
  • skills/openai-image-gen/scripts/test_gen.py+50 0 added
    @@ -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
    +
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.