forked from mikewest/sanitizer-playground
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
266 lines (250 loc) · 10.8 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>HTML Sanitizer API Playground</title>
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;800&family=Roboto+Slab:wght@100;500&display=swap" rel="stylesheet" crossorigin="anonymous">
<link href="style.css" rel="stylesheet">
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@mikewest">
<meta name="og:title" content="Play with the HTML Sanitizer API.">
<meta name="description" content="Learn a little about how `new Sanitizer().sanitize(untrusted_string)` might usefully mitigate injection attacks.">
<meta name="og:description" content="Learn a little about how `new Sanitizer().sanitize(untrusted_string)` might usefully mitigate injection attacks.">
<meta name="og:image" content="https://sanitizer-api.dev/card.png">
</head>
<body>
<h1>HTML Sanitizer API Playground</h1>
<article>
<div id="warnings"></div>
<p>
The <a href="https://wicg.github.io/sanitizer-api/">HTML Sanitizer API</a>
is an API that <a href="https://en.wikipedia.org/wiki/HTML_sanitization">sanitizes</a>
untrusted HTML for insertion into the DOM, minimizing the risk of
unintended script execution.
</p>
<h2>Input String</h2>
<textarea id="input"><p>
This is a very boring string that
<em onclick="alert('onclick fired!')">certainly</em>
does <span style="font-weight:800;">not</span> contain
injection attacks of any sort.
<img src="data:image/png," onerror="alert('onerror fired!')">
</p>
<td>Let's try a table cell.</td>
<style>
</style></textarea>
<p><a href="#" id="permalink">Link to this input.</a></p>
<h2>Sanitizer Configuration</h2>
<p>
The Sanitizer default configuration is safe and will likely suffice for
many applications. Note that all sanitizers guarantee a safe baseline,
so even a misconfigured Sanitizer will not allow script execution.
</p>
<p>
<strong>For: </strong>
<select id="contextselect">
<option value="div">div</option>
<option value="table">table</option>
<option value="custom">other (edit below)</option>
</select>
<span> </span>
<strong>Configuration: </strong>
<select id="configselect">
<option value="default">default</option>
<option value="textonly">text only (no elements)</option>
<option value="style">only simple styling</option>
<option value="misconfigured">misconfigured</option>
<option value="custom">custom (edit below)</option>
</select>
</p>
<p id="contextother" class="invisible">
<code>For (parse context): <input id="contexttext"></input></code>
</p>
<div id="configother" class="invisible">
<code>const config = </code>
<textarea id="configtext"></textarea>
</div>
<h2>Live Output</h2>
<p>
<code>
<span>const sanitizer = new Sanitizer(<span id="configuse">config</span>);</span><br>
<span>node.replaceChildren(...sanitizer.sanitizeFor(<em><span id="contextuse">context</span></em>, </span><em>input</em>));
</code>
</p>
<div id="output">
<div></div>
<div></div>
</div>
<button id="inject">Inject <em>without</em> sanitization, just for grins.</button>
<h2>How does it work?</h2>
<p>
The API's shape and behavior is still under
<a href="https://github.com/WICG/sanitizer-api/issues">active discussion</a>, so we'd
appreciate your early feedback. This page is meant as a quick demonstration of the
very basics of the API, to give you a feel for it's usage. Here, that boils down to the
following:
</p>
<pre><code>const s = <strong>new Sanitizer()</strong>;
// If the input is available as DOM nodes (e.g. in a frame).
el.replaceChildren(<strong>s.sanitize(<em>untrustedNodes</em>)</strong>);
// If the input is available as a string.
el.<strong>SetHTML(<em>untrustedString</em>, <em>s</em>)</strong>;
// If the input is available as a string, but cannot be set right away:
// The <em>context</em> parameter describes the node type this result is intended for.
const node = <strong>s.sanitizeFor(<em>context</em>, <em>untrustedString</em>);</strong></code></pre>
<p>The goal for the default configuration is to ensure that the browser won't
execute any code as a result of inserting the resulting nodes. You may need
to change the sanitizer's configuration if your application has special needs.
Configuration options are
<a href="https://wicg.github.io/sanitizer-api/#config">documented in the specification</a>.
For example, you may wish to block some style vectors in addition to script:</p>
<pre><code><strong>const config = {
dropAttributes: { "style": ["*"] },
dropElements: [ "style" ]
};</strong>
const <strong>s</strong> = new Sanitizer(<strong><em>config</em></strong>);
el.SetHTML(<em>untrustedInput</em>, <strong><em>s</em></strong>);</code></pre>
</article>
<footer>
Typos? Mistakes? Poke at <a href="https://github.com/mikewest/sanitizer-playground">mikewest/sanitizer-playground</a> on GitHub.
</footer>
<template id="warnsanitizefor">
<p class="warning">Cannot call Sanitizer.sanitizeFor.</p>
</template>
<template id="warnparseconfig">
<p class="warning">Cannot parse configuration dictionary.</p>
</template>
<template id="warnpresence">
<p class="warning">
This browser doesn't appear to support the `Sanitizer` interface.
Nothing below will do much.
<br>Try Chrome M94 or greater with `chrome://flags/#sanitizer-api` enabled?
<br>Or Firefox Nightly with `dom.security.sanitizer.enabled` set to true?
</p>
</template>
<template id="warnoldversion">
<p class="warning">
This browser supports an older version of the `Sanitizer`interface.
Nothing below will do much.
<br>Try Chrome M94 or greater with `chrome://flags/#sanitizer-api` enabled?
<br>Or Firefox Nightly with `dom.security.sanitizer.enabled` set to true?
</p>
</template>
<script>
const configs = {
"default": {},
textonly: { allowElements: [] },
style: {
allowElements: [ "div", "span", "p", "em", "b" ],
allowAttributes: { "class": ["*"], "style": ["*"] },
},
misconfigured: {
allowElements: [ "em", "img" ],
allowAttributes: { "onclick": ["*"], "onerror": ["*"] },
},
};
// Exciting constant element references!
const input = document.querySelector('#input');
const outputHtml = document.querySelector('#output div:nth-of-type(1)');
const outputText = document.querySelector('#output div:nth-of-type(2)');
const configSelect = document.querySelector('#configselect');
const configText = document.querySelector('#configtext');
const configOther = document.querySelector('#configother');
const configUse = document.querySelector('#configuse');
const contextSelect = document.querySelector('#contextselect');
const contextText = document.querySelector('#contexttext');
const contextOther = document.querySelector('#contextother');
const contextUse = document.querySelector('#contextuse');
const permalink = document.querySelector('#permalink');
const warnings = document.getElementById("warnings");
function replace(node, templateid) {
const fragment = document.getElementById(templateid).content.cloneNode(true);
node.replaceChildren(...fragment.children);
}
function init() {
// Display warnings (if Sanitizer is not supported or current)
if (!("Sanitizer" in window)) {
replace(warnings, "warnpresence");
} else if ("sanitizeToString" in window.Sanitizer.prototype) {
replace(warnings, "warnoldversion");
}
// Populate <textarea> via an `input` GET parameter:
const params = new URLSearchParams(window.location.search);
if (params.has("input")) {
input.value = decodeURIComponent(params.get('input'));
}
}
function listeners() {
// Register the pure injection Handler
document.querySelector('#inject').addEventListener('click', _ => {
outputHtml.innerHTML = input.value;
outputText.textContent = outputHtml.innerHTML;
});
// Register updaters for various input elements:
input.addEventListener('keyup', update);
contextSelect.addEventListener('change', update);
contextText.addEventListener('input', update);
configSelect.addEventListener('change', update);
configText.addEventListener('input', update);
}
function update() {
// Update the permalink:
permalink.href = "?input=" + encodeURIComponent(input.value);
// Update the current parse context, from the dropdowi or "custom".
let context = contextSelect.selectedOptions[0].value;
if (context == "custom") {
context = contextText.value;
contextOther.className = "";
} else {
contextOther.className = "invisible";
contextText.value = context;
}
contextUse.textContent = `"${context}"`;
// Update the current config, from the dropdown, "custom", or "default";
let config = configSelect.selectedOptions[0].value;
if (config == "default") {
configOther.className = "invisible";
configUse.textContent = "";
configText.readOnly = true;
configText.value = "{}"
} else if (config == "custom") {
configOther.className = "";
configText.readOnly = false;
configUse.textContent = "config";
} else {
configOther.className = "";
configUse.textContent = "config";
configText.value = JSON.stringify(configs[config], null, 1);
configText.readOnly = true;
}
// The code below doesn't work without Sanitizer, so let's not go there.
if (!("Sanitizer" in window))
return;
// Create a Sanitizer with that configuration:
let sanitizer = null;
try {
const conf = JSON.parse(configText.value);
sanitizer = new Sanitizer(conf);
} catch (err) {
replace(outputHtml, "warnparseconfig"); // Display a parse error.
return;
}
// Use the sanitizer to inject the <textarea>'s value safely into the DOM
// via `Node.replaceChildren()` and to render the textual output into the
// <output> element's `Node.innerText`:
try {
let result = sanitizer.sanitizeFor(context, input.value);
outputText.textContent = result.innerHTML; // As text.
outputHtml.replaceChildren(...result.childNodes); // Replace nodes.
} catch (err) {
replace(outputHtml, "warnsanitizefor"); // Display Sanitizer API error.
return;
}
}
init();
listeners();
update();
</script>
</body>
</html>