Skip to content

Commit 0316fd7

Browse files
author
Jiří Fencl
committed
Add escHtml utility and sanitize widget output in Counter, WidgetDescription, HeadlineSlot, and ErrorView components
1 parent 2ca0b15 commit 0316fd7

6 files changed

Lines changed: 31 additions & 9 deletions

File tree

.changeset/shy-chicken-flow.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"@merkur/create-widget": patch
3+
---
4+
5+
Escape HTML output in vanilla template to prevent XSS
6+
7+
- **What** Added `escHtml()` calls around all widget state and property values interpolated into template literals in `Counter.js`, `WidgetDescription.js`, `HeadlineSlot.js`, and `ErrorView.js`. Also fixed `className` to `class` in `ErrorView.js`.
8+
- **Why** Raw interpolation of widget state into HTML strings is vulnerable to XSS. Any user-controlled value (e.g. `widget.state.counter`, `widget.name`, `widget.error.message`) could inject arbitrary HTML.
9+
- **How** Nothing.
10+

packages/create-widget/views/vanilla/template/src/components/Counter.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import { escHtml } from '../utils';
2+
13
export default function Counter(widget) {
24
return `
35
<div>
46
<h3>Counter widget:</h3>
5-
<p>Count: <span data-merkur="counter">${widget.state.counter}</span></p>
7+
<p>Count: <span data-merkur="counter">${escHtml(widget.state.counter)}</span></p>
68
<button data-merkur="on-increase">
79
increase counter
810
</button>
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { escHtml } from '../utils';
2+
13
export default function ViewDescription(widget) {
2-
return `<p>The widget's name is <strong>${widget.name}@${widget.version}</strong>.</p>`;
4+
return `<p>The widget's name is <strong>${escHtml(widget.name)}@${escHtml(widget.version)}</strong>.</p>`;
35
}

packages/create-widget/views/vanilla/template/src/slots/HeadlineSlot.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { escHtml } from '../utils';
12
import Welcome from '../components/Welcome';
23
import WidgetDescription from '../components/WidgetDescription';
34
import ErrorView from '../views/ErrorView';
@@ -19,9 +20,7 @@ function HeadlineSlot(widget) {
1920
<div class='merkur__headline'>
2021
<div class='merkur__view'>
2122
${Welcome(widget)}
22-
<h3>Current count: <span data-merkur="counter">${
23-
widget.state.counter
24-
}</span></h3>
23+
<h3>Current count: <span data-merkur="counter">${escHtml(widget.state.counter)}</span></h3>
2524
${WidgetDescription(widget)}
2625
</div>
2726
</div>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function escHtml(s) {
2+
return String(s)
3+
.replace(/&/g, '&amp;')
4+
.replace(/</g, '&lt;')
5+
.replace(/>/g, '&gt;')
6+
.replace(/"/g, '&quot;');
7+
}
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import { escHtml } from '../utils';
2+
13
export default function ErrorView(widget) {
24
return `
3-
<div className='merkur__error'>
4-
<h1>Status: ${widget.error.status}</h1>
5-
<h2>Message: ${widget.error.message}</h2>
6-
<pre>${widget.error.stack}</pre>
5+
<div class='merkur__error'>
6+
<h1>Status: ${escHtml(widget.error.status)}</h1>
7+
<h2>Message: ${escHtml(widget.error.message)}</h2>
8+
<pre>${escHtml(widget.error.stack)}</pre>
79
</div>
810
`;
911
}

0 commit comments

Comments
 (0)