Skip to content

Should sanitizer be allowed to remove attributes that create a declarative shadow root? #12539

Open

Description

@noamr

To create a declarative shadow root, attributes on the template must be set, e.g.:

<my-custom-element>
  <template shadowrootmode=open shadowrootdelegatesfocus>...</template>
</my-custom-element>

However, those attributes are handled at parse time, and the sanitizer doesn't recognize them as "attributes". This means that in the current spec, removeAttributes: ["shadowrootmode"] doesn't prevent declarative shadow roots, and likewise removeElements: ["template"].

Is this WAI, or an oversight?

(Note, this is different from WICG/sanitizer-api#45 which was about allowing the sanitizer to "see into" the shadow root)

cc @mozfreddyb @otherdaniel

Activity

  1. annevk commented on Mar 31, 2026

    @annevk
    Member

    I would consider this an oversight similar to is="". Addressing this will require a special case unfortunately, but so be it.

  2. noamr commented on Mar 31, 2026

    @noamr
    ContributorAuthor

    I think we should seriously consider switching to a streaming model where we sanitize during parsing rather than keep having these special post-processing cases.

    We need streaming sanitization anyway for streamHTML and the different parser insertion points are much safer places to perform this kind of operation, e.g. we have an actual template element at that point.

  3. annevk commented on Mar 31, 2026

    @annevk
    Member

    I agree we should, but as far as I can tell that would not solve this problem. This happens as part of processing the "template" start tag which is not an element.

  4. noamr commented on Mar 31, 2026

    @noamr
    ContributorAuthor

    I agree we should, but as far as I can tell that would not solve this problem. This happens as part of processing the "template" start tag which is not an element.

    Right, it still needs a special case, but at least there is some concept of this being a "template" at this stage so it can feel a bit more natural than reverse-engineering a ShadowRoot inside a DocumentFragment into a template with attributes (or delaying transforming the template into a ShadowRoot).

  5. otherdaniel commented on Apr 1, 2026

    @otherdaniel
    Contributor

    Summary of discussion in the meeting:

    • Declarative shadow roots are processed in the parser. So by the time the sanitizer gets to see this, the <template> element has already been turned into a shadow root. There is no config that would block this. The root gets recursed into and sanitized; but blocking the shadow root (e.g. by removing <template>) doesn't work.
    • This will require some sort of special treatment. Question is, is this exposed to the user (e.g. with an allowShadowRoot option or somesuch), or can we make it work as if it were a regular element (e.g., making removeElements: ["template"] work).
    • Options:
      1. Add a shadow DOM specific flag to the config.
      2. Modify the fragment parser, to call out to the parser before the <template> element is processed / inserted.
      3. Call the fragment parser with a flag to not process shadow roots; then sanitize; then post-process and attach all remaining shadow roots.
      4. Parse + sanitize as before, but fix up the result. E.g., when the sanitizer finds an element that is a shadow host, call into the sanitizer and check if <template> would have been allowed and remove the shadow root if not. Similar processing for shadow root related attributes.
    • Opinions voiced, in no particular order:
      • Option 1 changes the developer experience. Probably not a good idea.
      • Option 2 might pave a way for or help with streaming parsing.
      • Option 3 spec support (parser flag) seems to mostly already be there.
      • Option 4 seems error prone. Also, no spec support for some operations (like removing a shadow root).
    • General opinion was drifting towards option 2. 1 + 4 were disliked.
  6. transferred this issue fromWICG/sanitizer-apion Jun 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions