Skip to content

Commit 4cfecd7

Browse files
committed
Disallow scripts in srcdoc
1 parent faa4382 commit 4cfecd7

File tree

1 file changed

+113
-5
lines changed

1 file changed

+113
-5
lines changed

src/Elm/Kernel/VirtualDom.js

Lines changed: 113 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ var _VirtualDom_nodeNS = F2(function(namespace, tag)
7979
return {
8080
$: __2_NODE,
8181
__tag: tag,
82-
__facts: _VirtualDom_organizeFacts(factList),
82+
__facts: _VirtualDom_organizeFacts(factList, tag),
8383
__kids: kids,
8484
__namespace: namespace,
8585
__descendantsCount: descendantsCount
@@ -110,7 +110,7 @@ var _VirtualDom_keyedNodeNS = F2(function(namespace, tag)
110110
return {
111111
$: __2_KEYED_NODE,
112112
__tag: tag,
113-
__facts: _VirtualDom_organizeFacts(factList),
113+
__facts: _VirtualDom_organizeFacts(factList, tag),
114114
__kids: kids,
115115
__namespace: namespace,
116116
__descendantsCount: descendantsCount
@@ -130,7 +130,7 @@ function _VirtualDom_custom(factList, model, render, diff)
130130
{
131131
return {
132132
$: __2_CUSTOM,
133-
__facts: _VirtualDom_organizeFacts(factList),
133+
__facts: _VirtualDom_organizeFacts(factList, ''),
134134
__model: model,
135135
__render: render,
136136
__diff: diff
@@ -285,10 +285,12 @@ var _VirtualDom_attributeNS = F3(function(namespace, key, value)
285285
// js_html ones are so weird that I prefer to see them near each other.
286286

287287

288+
var _VirtualDom_RE_iframe = /^iframe$/i;
288289
var _VirtualDom_RE_script = /^script$/i;
289290
var _VirtualDom_RE_on_formAction = /^(on|formAction$)/i;
290291
var _VirtualDom_RE_js = /^\s*j\s*a\s*v\s*a\s*s\s*c\s*r\s*i\s*p\s*t\s*:/i;
291292
var _VirtualDom_RE_js_html = /^\s*(j\s*a\s*v\s*a\s*s\s*c\s*r\s*i\s*p\s*t\s*:|d\s*a\s*t\s*a\s*:\s*t\s*e\s*x\s*t\s*\/\s*h\s*t\s*m\s*l\s*(,|;))/i;
293+
var _VirtualDom_RE_sandboxAllowScripts = /(^|[\t\n\f\r ])allow-scripts(?=$|[\t\n\f\r ])/gi;
292294

293295

294296
function _VirtualDom_noScript(tag)
@@ -384,9 +386,12 @@ var _VirtualDom_mapEventRecord = F2(function(func, record)
384386
// ORGANIZE FACTS
385387

386388

387-
function _VirtualDom_organizeFacts(factList)
389+
function _VirtualDom_organizeFacts(factList, elementTag)
388390
{
389-
for (var facts = {}; factList.b; factList = factList.b) // WHILE_CONS
391+
var isIframe = _VirtualDom_RE_iframe.test(elementTag);
392+
393+
// For iframe, make sure attributes come first – see _VirtualDom_iframeSandbox.
394+
for (var facts = isIframe ? { a__1_ATTR: {} } : {}; factList.b; factList = factList.b) // WHILE_CONS
390395
{
391396
var entry = factList.a;
392397

@@ -409,6 +414,11 @@ function _VirtualDom_organizeFacts(factList)
409414
: subFacts[key] = value;
410415
}
411416

417+
if (isIframe)
418+
{
419+
_VirtualDom_iframeSandbox(facts);
420+
}
421+
412422
return facts;
413423
}
414424

@@ -418,6 +428,104 @@ function _VirtualDom_addClass(object, key, newClass)
418428
object[key] = classes ? classes + ' ' + newClass : newClass;
419429
}
420430

431+
function _VirtualDom_iframeSandbox(facts)
432+
{
433+
var hasSrcdocProperty = false;
434+
var hasSandboxProperty = false;
435+
var srcAttrs = [];
436+
var srcdocAttrs = [];
437+
var sandboxAttrs = [];
438+
439+
for (var key in facts)
440+
{
441+
switch (key)
442+
{
443+
// This handles attributes, but what about `a__1_ATTR_NS`, by the way?
444+
// It is only possible to set the sandbox via `.setAttributeNS(null, 'sandbox', 'allow-x')`
445+
// but the Elm API always passes a string as the first argument (the namespace).
446+
case 'a__1_ATTR':
447+
for (var attr in facts[key])
448+
{
449+
switch (attr.toLowerCase())
450+
{
451+
case 'src':
452+
srcAttrs.push(attr);
453+
break;
454+
455+
case 'srcdoc':
456+
srcdocAttrs.push(attr);
457+
break;
458+
459+
case 'sandbox':
460+
sandboxAttrs.push(attr);
461+
break;
462+
}
463+
}
464+
break;
465+
466+
case 'srcdoc':
467+
hasSrcdocProperty = true;
468+
break;
469+
470+
case 'sandbox':
471+
hasSandboxProperty = true;
472+
break;
473+
}
474+
}
475+
476+
if (hasSrcdocProperty || srcdocAttrs.length > 0)
477+
{
478+
// If srcdoc is set, but not sandbox, use a default sandbox that protects against scripts.
479+
if (!hasSandboxProperty && sandboxAttrs.length === 0)
480+
{
481+
// Setting the sandbox attribute disallows everything not mentioned.
482+
// We’re especially interested in NOT having allow-scripts.
483+
// The below permissions are the only ones that don’t require JavaScript
484+
// and might be expected to be allowed by Elm developers, since Elm used
485+
// to not set the sandbox attribute at all (allowing everything).
486+
facts.a__1_ATTR.sandbox = 'allow-downloads allow-forms allow-top-navigation';
487+
}
488+
// Otherwise, use the user provided sandbox, but make sure it does not use allow-scripts.
489+
else
490+
{
491+
if (hasSandboxProperty)
492+
{
493+
facts.sandbox = String(facts.sandbox).replace(_VirtualDom_RE_sandboxAllowScripts, '');
494+
}
495+
496+
for (var i = 0; i < sandboxAttrs.length; i++)
497+
{
498+
var attr = sandboxAttrs[i];
499+
facts.a__1_ATTR[attr] = facts.a__1_ATTR[attr].replace(_VirtualDom_RE_sandboxAllowScripts, '');
500+
}
501+
}
502+
}
503+
504+
// Move src and srcdoc attributes last (in object iteration order), so that sandbox is set first.
505+
// That’s important when the iframe is already inserted into the document.
506+
// Otherwise, at least srcdoc can execute (without restrictions) before the sandbox is set.
507+
// Attributes are already guaranteed to be before properties – see `_VirtualDom_organizeFacts`.
508+
// Note that srcdoc overrides src according to the spec. If you however set first `src` and then
509+
// immediately `srcdoc` with JavaScript, you can still see the request for `src` show up in the
510+
// browser network panel, being cancelled right away.
511+
// Here, we choose to apply consistently apply `srcdoc` first.
512+
// Note that this is needed both when we add a default srcdoc, and when the user added srcdoc themselves.
513+
for (var i = 0; i < srcdocAttrs.length; i++)
514+
{
515+
var attr = srcdocAttrs[i];
516+
var value = facts.a__1_ATTR[attr];
517+
delete facts.a__1_ATTR[attr];
518+
facts.a__1_ATTR[attr] = value;
519+
}
520+
for (var i = 0; i < srcAttrs.length; i++)
521+
{
522+
var attr = srcAttrs[i];
523+
var value = facts.a__1_ATTR[attr];
524+
delete facts.a__1_ATTR[attr];
525+
facts.a__1_ATTR[attr] = value;
526+
}
527+
}
528+
421529

422530

423531
// RENDER

0 commit comments

Comments
 (0)