@schukai/monster 4.150.7 → 4.150.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -41,7 +41,9 @@ describe("Viewer", function () {
41
41
  expect(textNode.textContent).to.equal(
42
42
  "<strong>raw</strong>\n<script>alert(1)</script>",
43
43
  );
44
- expect(textNode.innerHTML).to.contain("&lt;strong&gt;raw&lt;/strong&gt;");
44
+ expect(textNode.innerHTML).to.contain(
45
+ "&lt;strong&gt;raw&lt;/strong&gt;",
46
+ );
45
47
  done();
46
48
  } catch (error) {
47
49
  done(error);
@@ -66,6 +68,10 @@ describe("Viewer", function () {
66
68
  `
67
69
  <link rel="stylesheet" href="https://evil.example/tracker.css">
68
70
  <script src="https://evil.example/tracker.js"></script>
71
+ <style id="tracking-style">
72
+ @import "https://evil.example/import.css";
73
+ .tracked { background-image: url("https://evil.example/background.png"); }
74
+ </style>
69
75
  <img id="external" src="https://evil.example/pixel.png">
70
76
  <img id="local" src="/image.png">
71
77
  `,
@@ -77,11 +83,16 @@ describe("Viewer", function () {
77
83
  const root = viewer.shadowRoot;
78
84
  expect(root.querySelector("link")).to.equal(null);
79
85
  expect(root.querySelector("script")).to.equal(null);
86
+ expect(root.querySelector("#tracking-style")).to.equal(null);
80
87
 
81
88
  const externalImage = root.querySelector("#external");
82
89
  expect(externalImage).to.not.equal(null);
83
- expect(externalImage.classList.contains("privacyImage")).to.equal(true);
84
- expect(externalImage.getAttribute("src")).to.match(/^data:image\/gif;base64,/);
90
+ expect(externalImage.classList.contains("privacyImage")).to.equal(
91
+ true,
92
+ );
93
+ expect(externalImage.getAttribute("src")).to.match(
94
+ /^data:image\/gif;base64,/,
95
+ );
85
96
 
86
97
  const localImage = root.querySelector("#local");
87
98
  expect(localImage).to.not.equal(null);
@@ -96,4 +107,240 @@ describe("Viewer", function () {
96
107
  }
97
108
  }, 0);
98
109
  });
110
+
111
+ it("removes unsafe links, forms, and interactive html", function (done) {
112
+ const mocks = document.getElementById("mocks");
113
+ mocks.innerHTML = `<monster-viewer id="viewer"></monster-viewer>`;
114
+
115
+ setTimeout(() => {
116
+ try {
117
+ const viewer = document.getElementById("viewer");
118
+ viewer.setContent(
119
+ `
120
+ <a id="script-link" href="jav&#x09;ascript:alert(1)" onclick="alert(2)">masked</a>
121
+ <a id="data-link" href=" DATA:text/html,evil">data</a>
122
+ <a id="custom-link" href="intent://evil">intent</a>
123
+ <a id="web-link" href="https://safe.example/path" ping="https://tracker.example" attributionsrc="https://tracker.example/register">web</a>
124
+ <a id="mail-link" href="mailto:user@example.com">mail</a>
125
+ <a id="phone-link" href="tel:+491234">phone</a>
126
+ <a id="fragment-link" href="#section">fragment</a>
127
+ <form id="form"><input name="secret"><button>Send</button></form>
128
+ <details id="details"><summary>Open</summary>Hidden</details>
129
+ <monster-button id="custom-element">Action</monster-button>
130
+ <svg><use id="external-use" href="https://evil.example/image.svg#pixel"></use></svg>
131
+ `,
132
+ "text/html",
133
+ );
134
+
135
+ setTimeout(() => {
136
+ try {
137
+ const root = viewer.shadowRoot;
138
+ expect(
139
+ root.querySelector("#script-link").hasAttribute("href"),
140
+ ).to.equal(false);
141
+ expect(
142
+ root.querySelector("#script-link").hasAttribute("onclick"),
143
+ ).to.equal(false);
144
+ expect(
145
+ root.querySelector("#data-link").hasAttribute("href"),
146
+ ).to.equal(false);
147
+ expect(
148
+ root.querySelector("#custom-link").hasAttribute("href"),
149
+ ).to.equal(false);
150
+ expect(
151
+ root.querySelector("#web-link").getAttribute("href"),
152
+ ).to.equal("https://safe.example/path");
153
+ expect(
154
+ root.querySelector("#web-link").hasAttribute("ping"),
155
+ ).to.equal(false);
156
+ expect(
157
+ root.querySelector("#web-link").hasAttribute("attributionsrc"),
158
+ ).to.equal(false);
159
+ expect(
160
+ root.querySelector("#mail-link").getAttribute("href"),
161
+ ).to.equal("mailto:user@example.com");
162
+ expect(
163
+ root.querySelector("#phone-link").getAttribute("href"),
164
+ ).to.equal("tel:+491234");
165
+ expect(
166
+ root.querySelector("#fragment-link").getAttribute("href"),
167
+ ).to.equal("#section");
168
+ expect(root.querySelector("form")).to.equal(null);
169
+ expect(root.querySelector("input")).to.equal(null);
170
+ expect(
171
+ root.querySelector("button:not([data-monster-link-action])"),
172
+ ).to.equal(null);
173
+ expect(root.querySelector("details")).to.equal(null);
174
+ expect(root.querySelector("monster-button")).to.equal(null);
175
+ expect(
176
+ root.querySelector("#external-use").hasAttribute("href"),
177
+ ).to.equal(false);
178
+ done();
179
+ } catch (error) {
180
+ done(error);
181
+ }
182
+ }, 0);
183
+ } catch (error) {
184
+ done(error);
185
+ }
186
+ }, 0);
187
+ });
188
+
189
+ it("confirms the canonical destination before opening a link", function (done) {
190
+ const mocks = document.getElementById("mocks");
191
+ mocks.innerHTML = `<monster-viewer id="viewer"></monster-viewer>`;
192
+
193
+ setTimeout(() => {
194
+ const viewer = document.getElementById("viewer");
195
+ const originalOpen = window.open;
196
+ const opened = [];
197
+ window.open = (...args) => {
198
+ opened.push(args);
199
+ return null;
200
+ };
201
+
202
+ try {
203
+ viewer.setContent(
204
+ '<a id="masked-link" href="https://xn--bcher-kva.example/account">Your bank</a>',
205
+ "text/html",
206
+ );
207
+
208
+ setTimeout(() => {
209
+ try {
210
+ const link = viewer.shadowRoot.querySelector("#masked-link");
211
+ link.click();
212
+
213
+ const confirmation = viewer.shadowRoot.querySelector(
214
+ '[data-monster-role="external-link-confirmation"]',
215
+ );
216
+ expect(confirmation.hidden).to.equal(false);
217
+ expect(
218
+ viewer.shadowRoot.querySelector(
219
+ '[data-monster-role="external-link-host"]',
220
+ ).textContent,
221
+ ).to.equal("xn--bcher-kva.example");
222
+ expect(
223
+ viewer.shadowRoot.querySelector(
224
+ '[data-monster-role="external-link-url"]',
225
+ ).textContent,
226
+ ).to.equal("https://xn--bcher-kva.example/account");
227
+
228
+ viewer.shadowRoot
229
+ .querySelector('[data-monster-link-action="cancel"]')
230
+ .click();
231
+ expect(confirmation.hidden).to.equal(true);
232
+ expect(opened).to.have.length(0);
233
+
234
+ link.click();
235
+ viewer.shadowRoot
236
+ .querySelector('[data-monster-link-action="new-window"]')
237
+ .click();
238
+
239
+ link.click();
240
+ viewer.shadowRoot
241
+ .querySelector('[data-monster-link-action="open"]')
242
+ .click();
243
+ expect(opened).to.deep.equal([
244
+ [
245
+ "https://xn--bcher-kva.example/account",
246
+ "_blank",
247
+ "noopener,noreferrer",
248
+ ],
249
+ [
250
+ "https://xn--bcher-kva.example/account",
251
+ "_self",
252
+ "noopener,noreferrer",
253
+ ],
254
+ ]);
255
+ done();
256
+ } catch (error) {
257
+ done(error);
258
+ } finally {
259
+ window.open = originalOpen;
260
+ }
261
+ }, 0);
262
+ } catch (error) {
263
+ window.open = originalOpen;
264
+ done(error);
265
+ }
266
+ }, 0);
267
+ });
268
+
269
+ it("keeps fragment links local without confirmation", function (done) {
270
+ const mocks = document.getElementById("mocks");
271
+ mocks.innerHTML = `<monster-viewer id="viewer"></monster-viewer>`;
272
+
273
+ setTimeout(() => {
274
+ try {
275
+ const viewer = document.getElementById("viewer");
276
+ viewer.setContent(
277
+ '<a id="fragment" href="#message-section">Section</a>',
278
+ "text/html",
279
+ );
280
+ setTimeout(() => {
281
+ try {
282
+ viewer.shadowRoot.querySelector("#fragment").click();
283
+ expect(
284
+ viewer.shadowRoot.querySelector(
285
+ '[data-monster-role="external-link-confirmation"]',
286
+ ).hidden,
287
+ ).to.equal(true);
288
+ done();
289
+ } catch (error) {
290
+ done(error);
291
+ }
292
+ }, 0);
293
+ } catch (error) {
294
+ done(error);
295
+ }
296
+ }, 0);
297
+ });
298
+
299
+ it("confirms links rendered inside an email message", function (done) {
300
+ const mocks = document.getElementById("mocks");
301
+ mocks.innerHTML = `<monster-viewer id="viewer"></monster-viewer>`;
302
+
303
+ setTimeout(() => {
304
+ try {
305
+ const viewer = document.getElementById("viewer");
306
+ viewer.setMessage({
307
+ parts: [
308
+ {
309
+ contentType: "text/html",
310
+ content:
311
+ '<a id="mail-link" href="https://evil.example/login">Trusted portal</a>',
312
+ },
313
+ ],
314
+ });
315
+
316
+ setTimeout(() => {
317
+ try {
318
+ const message = viewer.shadowRoot.querySelector(
319
+ "monster-message-content",
320
+ );
321
+ const html = message.shadowRoot.querySelector(
322
+ "monster-html-content",
323
+ );
324
+ const link = html.shadowRoot.querySelector("#mail-link");
325
+ link.click();
326
+
327
+ const confirmation = viewer.shadowRoot.querySelector(
328
+ '[data-monster-role="external-link-confirmation"]',
329
+ );
330
+ expect(confirmation.hidden).to.equal(false);
331
+ expect(
332
+ viewer.shadowRoot.querySelector(
333
+ '[data-monster-role="external-link-url"]',
334
+ ).textContent,
335
+ ).to.equal("https://evil.example/login");
336
+ done();
337
+ } catch (error) {
338
+ done(error);
339
+ }
340
+ }, 180);
341
+ } catch (error) {
342
+ done(error);
343
+ }
344
+ }, 0);
345
+ });
99
346
  });
@@ -1,133 +0,0 @@
1
- /** this file was created automatically by the run-web-tests script */
2
- import "./prepare.js";
3
- import "../cases/components/layout/slit-panel.mjs";
4
- import "../cases/components/layout/panel.mjs";
5
- import "../cases/components/layout/slider.mjs";
6
- import "../cases/components/content/viewer.mjs";
7
- import "../cases/components/content/image-editor.mjs";
8
- import "../cases/components/form/buy-box.mjs";
9
- import "../cases/components/form/message-state-button.mjs";
10
- import "../cases/components/form/reload.mjs";
11
- import "../cases/components/form/context-help.mjs";
12
- import "../cases/components/form/state-button.mjs";
13
- import "../cases/components/form/login.mjs";
14
- import "../cases/components/form/context-error.mjs";
15
- import "../cases/components/form/choice-cards.mjs";
16
- import "../cases/components/form/form.mjs";
17
- import "../cases/components/form/tree-select.mjs";
18
- import "../cases/components/form/popper-button.mjs";
19
- import "../cases/components/form/wizard.mjs";
20
- import "../cases/components/form/button.mjs";
21
- import "../cases/components/form/floating-ui.mjs";
22
- import "../cases/components/form/toggle-switch.mjs";
23
- import "../cases/components/form/template.mjs";
24
- import "../cases/components/notify/message.mjs";
25
- import "../cases/components/notify/notify.mjs";
26
- import "../cases/components/host/host.mjs";
27
- import "../cases/components/host/overlay.mjs";
28
- import "../cases/components/host/config-manager.mjs";
29
- import "../cases/components/host/util.mjs";
30
- import "../cases/components/host/details.mjs";
31
- import "../cases/components/datatable/config-keys.mjs";
32
- import "../cases/components/datatable/writeback-sanitizer.mjs";
33
- import "../cases/components/datatable/pagination.mjs";
34
- import "../cases/components/navigation/site-navigation.mjs";
35
- import "../cases/text/formatter.mjs";
36
- import "../cases/text/generate-range-comparison-expression.mjs";
37
- import "../cases/text/util.mjs";
38
- import "../cases/text/markdown-parser.mjs";
39
- import "../cases/text/bracketed-key-value-hash.mjs";
40
- import "../cases/math/random.mjs";
41
- import "../cases/util/trimspaces.mjs";
42
- import "../cases/util/processing.mjs";
43
- import "../cases/util/deadmansswitch.mjs";
44
- import "../cases/util/comparator.mjs";
45
- import "../cases/util/freeze.mjs";
46
- import "../cases/util/clone.mjs";
47
- import "../cases/logging/handler/console.mjs";
48
- import "../cases/logging/logger.mjs";
49
- import "../cases/logging/handler.mjs";
50
- import "../cases/logging/logentry.mjs";
51
- import "../cases/net/webconnect.mjs";
52
- import "../cases/net/webconnect/message.mjs";
53
- import "../cases/monster.mjs";
54
- import "../cases/dom/dimension.mjs";
55
- import "../cases/dom/resource/link/stylesheet.mjs";
56
- import "../cases/dom/resource/link.mjs";
57
- import "../cases/dom/resource/script.mjs";
58
- import "../cases/dom/resource/data.mjs";
59
- import "../cases/dom/util/init-options-from-attributes.mjs";
60
- import "../cases/dom/util/extract-keys.mjs";
61
- import "../cases/dom/worker/factory.mjs";
62
- import "../cases/dom/updater.mjs";
63
- import "../cases/dom/locale.mjs";
64
- import "../cases/dom/theme.mjs";
65
- import "../cases/dom/customelement.mjs";
66
- import "../cases/dom/focusmanager.mjs";
67
- import "../cases/dom/find.mjs";
68
- import "../cases/dom/ready.mjs";
69
- import "../cases/dom/util.mjs";
70
- import "../cases/dom/customelement-initfromscripthost.mjs";
71
- import "../cases/dom/slotted-nodes.mjs";
72
- import "../cases/dom/attributes.mjs";
73
- import "../cases/dom/resource.mjs";
74
- import "../cases/dom/resourcemanager.mjs";
75
- import "../cases/dom/assembler.mjs";
76
- import "../cases/dom/customcontrol.mjs";
77
- import "../cases/dom/template.mjs";
78
- import "../cases/dom/events.mjs";
79
- import "../cases/data/datasource/dom.mjs";
80
- import "../cases/data/datasource/storage/localstorage.mjs";
81
- import "../cases/data/datasource/storage/sessionstorage.mjs";
82
- import "../cases/data/datasource/server.mjs";
83
- import "../cases/data/datasource/server/websocket.mjs";
84
- import "../cases/data/datasource/server/restapi.mjs";
85
- import "../cases/data/datasource.mjs";
86
- import "../cases/data/buildmap.mjs";
87
- import "../cases/data/transformer.mjs";
88
- import "../cases/data/diff.mjs";
89
- import "../cases/data/buildtree.mjs";
90
- import "../cases/data/pathfinder.mjs";
91
- import "../cases/data/pipe.mjs";
92
- import "../cases/data/extend.mjs";
93
- import "../cases/constraint/invalid.mjs";
94
- import "../cases/constraint/abstractoperator.mjs";
95
- import "../cases/constraint/oroperator.mjs";
96
- import "../cases/constraint/isobject.mjs";
97
- import "../cases/constraint/andoperator.mjs";
98
- import "../cases/constraint/isarray.mjs";
99
- import "../cases/constraint/valid.mjs";
100
- import "../cases/types/nodelist.mjs";
101
- import "../cases/types/base.mjs";
102
- import "../cases/types/mediatype.mjs";
103
- import "../cases/types/tokenlist.mjs";
104
- import "../cases/types/proxyobserver.mjs";
105
- import "../cases/types/version.mjs";
106
- import "../cases/types/global.mjs";
107
- import "../cases/types/observerlist.mjs";
108
- import "../cases/types/internal.mjs";
109
- import "../cases/types/observablequeue.mjs";
110
- import "../cases/types/dataurl.mjs";
111
- import "../cases/types/binary.mjs";
112
- import "../cases/types/observer.mjs";
113
- import "../cases/types/regex.mjs";
114
- import "../cases/types/randomid.mjs";
115
- import "../cases/types/id.mjs";
116
- import "../cases/types/uuid.mjs";
117
- import "../cases/types/is.mjs";
118
- import "../cases/types/validate.mjs";
119
- import "../cases/types/typeof.mjs";
120
- import "../cases/types/uniquequeue.mjs";
121
- import "../cases/types/stack.mjs";
122
- import "../cases/types/basewithoptions.mjs";
123
- import "../cases/types/node.mjs";
124
- import "../cases/types/queue.mjs";
125
- import "../cases/types/noderecursiveiterator.mjs";
126
- import "../cases/i18n/time-ago.mjs";
127
- import "../cases/i18n/formatter.mjs";
128
- import "../cases/i18n/locale.mjs";
129
- import "../cases/i18n/provider.mjs";
130
- import "../cases/i18n/providers/fetch.mjs";
131
- import "../cases/i18n/providers/embed.mjs";
132
- import "../cases/i18n/util.mjs";
133
- import "../cases/i18n/translations.mjs";