Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions iframe-srcdoc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# iframe srcdoc security

There used to be an `Html.Attributes.srcdoc` function. Well, there technically still is one, but all it does is take you here. As it turns out, `srcdoc` on an iframes is a security issue. It allows Elm packages to execute JavaScript code without you knowing. Maybe you thought you were rendering an icon from a package, but behind the scenes it also stole all data on your page. Oops!

If you have code like this:

```elm
Html.iframe [ Html.Attributes.srcdoc myHtml ] []
```

Then you can change it to a web component:

```elm
Html.node "my-iframe" [ Html.Attributes.attribute "html" myHtml ] []
```

```js
/**
* This custom element forwards the `html` attribute to `srcdoc` on an `iframe`.
*
* Example:
*
* <my-iframe html="<p>Hello</p>"></my-iframe>
*
* Becomes:
*
* <my-iframe html="<p>Hello</p>">
* <iframe srcdoc="<p>Hello</p>" sandbox=""></iframe>
* </my-iframe>
*/
customElements.define(
'my-iframe',
class extends HTMLElement {
iframe = document.createElement('iframe');

connectedCallback() {
// It's important to set `sandbox` on iframes for security.
// https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe#sandbox
this.iframe.sandbox = '';
this.appendChild(this.iframe);
}

// Note: You can't call the attribute `srcdoc` even on the custom element.
// For simplicity, Elm forbids that name regardless of element.
// Simply choose another name.
static observedAttributes = ['html'];

attributeChangedCallback(name, _oldValue, newValue) {
switch (name) {
case 'html':
if (newValue === null) {
this.iframe.removeAttribute('srcdoc');
} else {
this.iframe.setAttribute('srcdoc', newValue);
}
break;
}
}
}
);
```

Why is a web component safer than using an iframe directly? The difference is that an Elm package can't ship the JavaScript web component definition, only the Elm code that tries to render it. And with the web component, you can guarantee that the `sandbox` attribute is set _before_ `srcdoc`.
4 changes: 2 additions & 2 deletions src/Html/Attributes.elm
Original file line number Diff line number Diff line change
Expand Up @@ -417,8 +417,8 @@ sandbox =
override the content of the `src` attribute if it has been specified.
-}
srcdoc : String -> Attribute msg
srcdoc =
stringProperty "srcdoc"
srcdoc _ =
stringProperty "srcdoc" """This is an XSS vector. Please <a href="https://github.com/elm/html/blob/master/iframe-srcdoc.md">use a web component instead</a>."""
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wording is inspired by elm/virtual-dom:

https://github.com/elm/virtual-dom/blob/faa43820c372b1a3077582887971686f7ba1474d/src/Elm/Kernel/VirtualDom.js#L309-L321

Note that in the linked code, the message does not appear in --optimize mode. I don’t think it’s worth the effort doing the same here, since nobody will use Html.Attributes.srcdoc in production, since it no longer does anything useful.




Expand Down