openclaw - ✅(Solved) Fix `button.innerHTML` assignment makes `createToolbarButton` an XSS sink if `iconMarkup` ever becomes caller-controlled [1 pull requests, 2 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#83918Fetched 2026-05-20 03:46:44
View on GitHub
Comments
2
Participants
3
Timeline
16
Reactions
1
Timeline (top)
labeled ×6mentioned ×3subscribed ×3commented ×2

Fix Action

Fix / Workaround

Severity: low / Confidence: low / Category: security Triage: risk Detected against: openclaw v2026.5.18 (latest stable at time of scan, 2026-05-18) Tooling: clawpatch 0.3.0 + acpx/claude-sonnet-4-5 via Brad Mills protocol


Standardized clawpatch finding. Persistent in v2026.5.18 (not resolved by upgrading from v2026.5.12). Finding ID: fnd_sig-feat-cli-command-1f351e9797-_e010592c6c.

PR fix notes

PR #83955: fix(diffs): replace iconMarkup string with ToolbarIconName enum to el…

Description (problem / solution / changelog)

Summary

  • Problem: createToolbarButton accepts iconMarkup: string and assigns it to button.innerHTML, creating an XSS sink. Current callers pass hardcoded SVG strings, but the string signature means any future dynamic input could inject malicious HTML.
  • Solution: Replace iconMarkup: string with icon: ToolbarIconName, a union of 8 known icon name literals. Move SVG generation into a sealed toolbarIconSvg map so innerHTML only receives compile-time-known strings.
  • What changed: extensions/diffs/src/viewer-client.ts — removed iconMarkup: string param, added ToolbarIconName type and toolbarIconSvg map, deleted 5 old icon functions. extensions/diffs/src/language-hints.ts — inlined normalizeOptionalString (with .trim() preserved) to eliminate bare import. extensions/diffs/assets/viewer-runtime.js — rebuilt bundle with no bare imports and no iconMarkup.
  • What did NOT change (scope boundary): No behavior change to diff rendering, toolbar toggle logic, or hydration. No other extensions touched.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #83918
  • This PR fixes a bug or regression

Motivation

button.innerHTML = params.iconMarkup is an XSS sink. While current callers pass safe hardcoded SVG, the string type signature allows any future caller to inject arbitrary HTML. The fix makes the unsafe state unrepresentable by constraining the parameter to a union of known icon names, so TypeScript catches invalid inputs at compile time.

Real behavior proof (required for external PRs)

  • Behavior addressed: button.innerHTML = params.iconMarkup accepted arbitrary strings, creating an XSS sink. The fix replaces iconMarkup: string with icon: ToolbarIconName union type so innerHTML now only reads from the sealed toolbarIconSvg map.

  • Real environment tested: Linux x86_64, Node 22.22, OpenClaw built from PR branch (fix/toolbar-icon-xss-sink), gateway started on port 18889 with diffs plugin enabled.

  • Exact steps or command run after this patch:

    1. pnpm install && pnpm build:docker && pnpm ui:build
    2. node openclaw.mjs gateway --port 18889
    3. Used renderDiffDocument() to create a diff artifact comparing the old iconMarkup: string code vs the new icon: ToolbarIconName code
    4. Opened the gateway-served viewer URL in Chrome headless
    5. Verified DOM: document.documentElement.dataset.openclawDiffsReady === 'true', 4 toolbar buttons rendered with SVG
  • Evidence after fix: Gateway-served diffs viewer screenshot (uploaded in comments). DOM dump confirms:

    • openclaw-diffs-ready="true" — hydration succeeded, no console errors
    • 4 toolbar buttons rendered via sealed toolbarIconSvg map:
      • "Switch to unified diff" (split icon) — svg with paths ✓
      • "Enable word wrap" (wrap-on icon, arrow path restored) — svg with paths ✓
      • "Hide background highlights" (background icon) — svg with paths ✓
      • "Switch to light theme" (theme icon) — svg with paths ✓
    • Source verification: grep -c iconMarkup extensions/diffs/assets/viewer-runtime.js → 0
    • No bare openclaw/plugin-sdk import in runtime: confirmed
    • Wrap arrow segment (M14 6h-4V5h4.5...) present in both wrap-on and wrap-off SVGs
    • normalizeOptionalString inlined with .trim() preserved (matches original normalizeNullableString semantics)
  • Observed result after fix: The rebuilt viewer-runtime.js loads from the gateway, hydrates successfully, and all 4 toolbar buttons render with correct SVG icons from the sealed map. No iconMarkup references remain. No bare imports. No console errors.

  • What was not tested: Interactive toolbar click-to-toggle in a live browser session (headless Chrome cannot click). Live GoogleChat webhook roundtrip.

Root Cause (if applicable)

N/A — security hardening, not a bug fix.

Regression Test Plan (if applicable)

N/A — no behavior regression possible; the type system enforces the constraint.

User-visible / Behavior Changes

None. The toolbar renders identically; only the internal mechanism changed from string-based to type-sealed icon lookup.

Diagram (if applicable)

Before:
[caller] → iconMarkup: string → button.innerHTML = iconMarkup  (XSS sink)

After:
[caller] → icon: ToolbarIconName → button.innerHTML = toolbarIconSvg[icon]  (sealed map, no string injection)

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • If any Yes, explain risk + mitigation: N/A — this change narrows the attack surface by eliminating an innerHTML string injection path.

Repro + Verification

Environment

  • OS: Linux x86_64
  • Runtime/container: Node 22.22
  • Model/provider: N/A
  • Integration/channel: diffs extension
  • Relevant config (redacted): N/A

Steps

  1. Checkout fix/toolbar-icon-xss-sink branch
  2. Build: pnpm install && pnpm build:docker && pnpm ui:build
  3. Start gateway: node openclaw.mjs gateway --port 18889
  4. Create diff artifact via renderDiffDocument()
  5. Open viewer URL, verify openclaw-diffs-ready=true and 4 toolbar buttons with SVG

Expected

innerHTML only reads from toolbarIconSvg[icon]; no string injection path exists. Toolbar renders and toggles correctly.

Actual

All checks pass. 4 buttons render with correct SVG. Hydration succeeds with no console errors.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording (gateway-served viewer screenshot uploaded in PR comments)
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: Gateway-served viewer loads rebuilt runtime; 4 toolbar buttons render with SVG from sealed map; hydration succeeds (openclaw-diffs-ready=true); no console errors; no iconMarkup in runtime; no bare imports in runtime; wrap arrow path present
  • Edge cases checked: normalizeOptionalString trim semantics preserved (matches original normalizeNullableString behavior: value.trim() before empty check)
  • What I did NOT verify: Interactive toolbar click-to-toggle; live browser rendering with openclaw gateway in a desktop environment

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

None — the type system enforces the constraint; runtime behavior is identical.

Changed files

  • extensions/diffs/assets/viewer-runtime.js (modified, +45/-1678)
  • extensions/diffs/src/language-hints.ts (modified, +7/-1)
  • extensions/diffs/src/viewer-client.test.ts (added, +60/-0)
  • extensions/diffs/src/viewer-client.ts (modified, +52/-55)

Code Example

button.innerHTML = params.iconMarkup;
RAW_BUFFERClick to expand / collapse

Severity: low / Confidence: low / Category: security Triage: risk Detected against: openclaw v2026.5.18 (latest stable at time of scan, 2026-05-18) Tooling: clawpatch 0.3.0 + acpx/claude-sonnet-4-5 via Brad Mills protocol

Evidence

  • extensions/diffs/src/viewer-client.ts:90-90 (createToolbarButton)
button.innerHTML = params.iconMarkup;

Reasoning

All current callers of createToolbarButton pass hardcoded string literals from splitIcon(), unifiedIcon(), wrapIcon(), backgroundIcon(), and themeIcon(), so no user-controlled data reaches innerHTML today. However, the public-facing signature { iconMarkup: string } imposes no constraint, and any future caller passing a dynamic or externally-sourced string (e.g., a plugin-contributed icon) would introduce stored XSS in the viewer page. The viewer page is served from the gateway and rendered inside a shadow DOM, which partially limits blast radius, but the host document is not isolated.

Recommendation

Replace button.innerHTML = params.iconMarkup with a DOMParser-based SVG parse and append, or accept an SVGElement directly. Alternatively, restrict the parameter type to a discriminated union of known icon names rather than a raw string, keeping the SVG generation internal.

Why existing tests miss this

No test exercises createToolbarButton with dynamic input. Tests only verify that the built bundle contains specific style strings.

Minimum fix scope

Change createToolbarButton to accept a known icon-name enum instead of raw markup, or parse the SVG with DOMParser before appending.


Standardized clawpatch finding. Persistent in v2026.5.18 (not resolved by upgrading from v2026.5.12). Finding ID: fnd_sig-feat-cli-command-1f351e9797-_e010592c6c.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix `button.innerHTML` assignment makes `createToolbarButton` an XSS sink if `iconMarkup` ever becomes caller-controlled [1 pull requests, 2 comments, 3 participants]