- From: Adam Barth <w3c@adambarth.com>
- Date: Sun, 10 Nov 2013 23:56:57 -0800
- To: Ryosuke Niwa <rniwa@apple.com>
- Cc: "public-webapps@w3.org WG" <public-webapps@w3.org>
Hi Ryosuke, Can you help me understand what security properties your proposal achieves and how it achieves them? I spent some time thinking about this problem a couple of years ago when this issue was discussed in depth, but I couldn't come up with a design that was simultaneously useful and secure. For example, your proposal seems to have the vulnerability described below: == Trusted container document == <link rel="import" href="https://untrusted.org/untrusted-components.html" importcomponents="name-card"> <body> <name-card ></name-card> == untrusted-components.html == <template defines="name-card" interface="NameCardElement"> Name: {{name}}<br>Email:{{email}} </template> <script> NameCardElement.prototype.created = function (shadowRoot) { var victim = shadowRoot.ownerDocument; var script = victim.createElement("script"); script.textContent = "alert(/hacked/);"; victim.body.appendChild(script); }; </script> Maybe I'm not understanding your proposal correct? If this issue is indeed a vulnerability with your proposal, I have no doubt that you can modify your proposal to patch this hole, but iterating in that way isn't likely to lead to a secure design. Thanks, Adam On Fri, Nov 8, 2013 at 11:24 AM, Ryosuke Niwa <rniwa@apple.com> wrote: > Hi all, > > We have been discussing cross-orign use case and declarative syntax of web > components internally at Apple, and here are our straw man proposal to amend > the existing Web Components specifications to support it. > > 1. Modify HTML Imports to run scripts in the imported document itself > This allows the importee and the importer to not share the same script > context, etc… > > 2. Add “importcomponents" content attribute on link element > It defines the list of custom element tag names to be imported from the > imported HTML document. > e.g. <link rel="import" href="~" importcomponents="tag-1 tag-2"> will export > custom elements of tag names "tag-1" and "tag-2" from ~. Any name that > didn't have a definition in the import document is ignored (i.e. if "tag-2" > was not defined in ~, it would be skipped but "tag-1" will be still > imported). > > This mechanism prevents the imported document from defining arbitrary > components in the host document. > > 3. Support "static" (write-once) binding of a HTML template > e.g. > <template id=cardTemplate>Name: {{name}}<br>Email:{{email}}</template> > <script> > document.body.appendChild(cardTemplate.instantiate({name: "Ryosuke Niwa", > email:"rniwa@webkit.org"})); > </script> > > 4. Add “interface" content attribute to template element > This content attribute specifies the name of the JavaScript constructor > function to be created in the global scope. The UA creates one and will be > used to instantiate a given custom element. The author can then setup the > prototype chain as needed: > > <template defines="name-card" interface="NameCardElement"> > Name: {{name}}<br>Email:{{email}} > </template> > <script> > NameCardElement.prototype.name = function () {...} > NameCardElement.prototype.email = function () {...} > </script> > > This is similar to doing: > var NameCardElement = document.register(’name-card'); > > 5. Add "defines" content attribute on HTML template element to define a > custom element > This new attribute defines a custom element of the given name for the > template content. > e.g. <template defines="nestedDiv"><div><div></div></div></template> will > let you use <nestedDiv></nestedDiv> > > We didn’t think having a separate custom element was useful because we > couldn’t think of a use case where you wanted to define a custom element > declaratively and not use template by default, and having to associate the > first template element with the custom element seemed unnecessary > complexity. > > 5.1. When a custom element is instantiated, automatically instantiate > template inside a shadow root after statically binding the template with > dataset > This allows statically declaring arguments to a component. > e.g. > <template defines="name-card">Name: {{name}}<br>Email:{{email}}</template> > <name-card data-name="Ryosuke Niwa" data-email="rniwa@webkit.org”> > > 5.2. When a new custom element object is constructed, "created" callback is > called with a shadow root > Unfortunately, we can't let the author define a constructor because the > element hadn't been properly initialized with the right JS wrapper at the > time of its construction. So just like we can't do "new HTMLTitleElement", > we're not going to let the author do an interesting things inside a custom > element's constructor. Instead, we're going to call "created" function on > its prototype chain: > > <template defines="name-card" interface="NameCardElement"> > Name: {{name}}<br>Email:{{email}} > </template> > <script> > NameCardElement.prototype.name = function () {...} > NameCardElement.prototype.email = function () {...} > NameCardElement.prototype.created = function (shadowRoot) { > ... // Initialize the shadowRoot here. > } > </script> > > This is similar to the way document.register works in that document.register > creates a constructor automatically. > > 6. The cross-origin component does not have access to the shadow host > element, and the host document doesn’t have access to the element object. > When member functions of the element is called, “this” object will be > undefined. This is necessary because exposing the object to a cross-origin > content will result in tricky security issues, forcing us to have proxy > objects, etc… > > Inside the document that imported a component, the element doesn’t use the > prototype defined by the component as that exposes JS objects cross-origin. > e.g. even if LikeButtonElement was defined in > facebook.com/~/like-button.html, the document that uses this component > wouldn’t see the prototype or the constructor. It’ll be HTMLUnknownElement. > (We could create a new custom element type such as > HTMLCrossOriginCustomElement if think that’s necessary). > > 7. Expose shadow host’s dataset on shadow root > This allows the component to communicate with the host document in a limited > fashion without exposing the element directly. > > This design allows us to have an iframe-like boundary between the shadow > host (custom element itself) and the shadow root (implementation details), > and address our cross-origin use case elegantly as follows: > > rniwa.com/webkit.html > --------------------------------- > <!DOCTYPE html> > <html> > <head> > <link rel=import href="https://webkit.org/components.html" > defines="share-button like-button"> > </head> > <body> > <like-button data-url="https://build.webkit.org/">Like > build.webkit.org</like-button> > </body> > </html> > > webkit.org/components.html > --------------------------------- > <template defines="like-button" interface="LikeButtonElement"> > <!-- implicitly does > shadowRoot.appendChild(myTemplate.instantiate(shadowHost.dataset)); --> > <form ...> > <input type=hidden value="{{url}}"> > <button type=submit>Like!</button> > </form> > <script> > LikeButtonElement.prototype.created = function (shadowRoot) { > shadowRoot.query('form').onsubmit = function () { > // ... > } > } > </script> > </template> > > - R. Niwa
Received on Monday, 11 November 2013 07:57:56 UTC