{"id":19856834,"url":"https://github.com/polymer/polymer-resin","last_synced_at":"2025-05-02T02:30:24.622Z","repository":{"id":57327133,"uuid":"88063868","full_name":"Polymer/polymer-resin","owner":"Polymer","description":"XSS mitigation for Polymer webcomponents that uses safe html type contracts","archived":false,"fork":false,"pushed_at":"2019-05-16T17:20:09.000Z","size":548,"stargazers_count":18,"open_issues_count":4,"forks_count":5,"subscribers_count":21,"default_branch":"master","last_synced_at":"2025-04-21T15:44:41.569Z","etag":null,"topics":["polymer","security","webcomponents","xss"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Polymer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-04-12T14:53:20.000Z","updated_at":"2024-05-16T22:21:38.000Z","dependencies_parsed_at":"2022-09-13T19:00:52.330Z","dependency_job_id":null,"html_url":"https://github.com/Polymer/polymer-resin","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Polymer%2Fpolymer-resin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Polymer%2Fpolymer-resin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Polymer%2Fpolymer-resin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Polymer%2Fpolymer-resin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Polymer","download_url":"https://codeload.github.com/Polymer/polymer-resin/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251972401,"owners_count":21673599,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["polymer","security","webcomponents","xss"],"created_at":"2024-11-12T14:16:43.465Z","updated_at":"2025-05-02T02:30:24.222Z","avatar_url":"https://github.com/Polymer.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Polymer Resin [![Build Status][build-status]][build-dashboard]\n\nXSS mitigation for Polymer webcomponents.\n\n--------------------------------------------------------------------------------\n\nThis document explains **what** polymer-resin is. See [\"Getting\nStarted\"][getting-started] if you're interested in **how** to use it.\n\n--------------------------------------------------------------------------------\n\nRelevant Concepts \u0026 Specs\n\n*   [Webcomponents][webcomponents]\n*   [Auto-sanitization][autosan]\n*   [Safe HTML Types][safe-html-types]\n\nRelevant Code\n\n*   [PolyGerrit UI on GoogleSource][polygerrit-ui]\n*   Polymer value hooks ([V1][poly-v1], [V2][poly-v2])\n*   JavaScript [Safe HTML APIs][safe-html-types-js]\n\n## Summary\n\nPolymer-resin hooks into Polymer and checks values from data binding expressions\njust before they reach browser internals.  It applies configurable policies with\ntype-safe exceptions so that developers can write data binding expressions\nwithout worrying about untrusted inputs abusing web APIs.\n\n![Untrusted javascript colon URL flowing through a custom element, into Polymer, through a DOM API, to the browser and eventually to the JavaScript engine](images/polymer-diagram-with-resin.png)\n\nFor example, if a `\u003cbar-element url={{url}}\u003e` is backed by an `\u003ca href={{url}}\u003e`\nand an attacker can cause `url` to be `javascript:alert(1)`, then, without\nPolymer-resin, Polymer will assign that string to `\u003ca href\u003e` at which point\nthe browser takes over, and routes `alert(1)` to the JavaScript engine,\nwhich unpacks and executes the attacker's payload: `alert(1)`.\n\n## Background\n\nIn most template languages, one defines templates, but in Polymer one defines\ncustom elements. This means that there is not a closed class of HTML elements\nand attributes about which we can reason as there is for [Closure Templates\nauto-sanitizer][soy-sec].\n\n## Goal\n\nMake it easy for security auditors to quickly check whether a project's custom\nelement definitions fall into a known-safe subset of Polymer.\n\n## Security Assumptions\n\nExisting auto-escaping template systems (CTemplates, Closure Templates,\nGo's html/template) assume that:\n\n1.  There is a large community of non-malicious application developers who\n    author templates.\n\n    This community is large enough that we cannot assume that they uniformly\n    apply rigorous secure coding and review practices.\n\n    A system worked on by N developers is vulnerable if just one of them\n    mistakenly introduces a vulnerability. Thus, large groups of individuals who\n    individually rarely make mistakes are nevertheless likely to produce a\n    system that has vulnerabilities.\n\n2.  There is a much smaller group of expert browser implementors who define the\n    semantics of tags and attributes. They are aware of the security\n    consequences of their work, and the code they produce is heavily reviewed,\n    but they produce blunt instruments.\n\nFor Polymer, we assume that\n\n1.  There is a large community of non-malicious custom element authors.\n2.  There is a large community of non-malicious application developers.\n3.  These two communities overlap to a great degree and are (similar to above)\n    large enough that we cannot assume uniform rigor in applying secure\n    practices.\n4.  The novel security consequences of webcomponents arise because they expand\n    the ways in which *unchecked values* can reach *builtin sinks*.\n\n    A builtin sink is a property or attribute that can be set by user code and\n    is handled specially by the browser in a manner that may have security\n    implications, in particular, attacker-controlled script execution. Builtin\n    sinks tend to correspond to IDL attributes that are annotated with\n    [*Reflect*][idl-reflect],\n    [*CustomElementCallbacks*][idl-custom-element-callbacks], or\n    [*CEReactions*][idl-cereactions].\n\n    There is a hazard whenever an *unchecked value* reaches a builtin sink. An\n    unchecked value is one that could be controlled by an attacker; it might\n    originate from outside an [origin][same-origin] controlled by the\n    application author. For instance, the `href` attribute of an\n    *HTMLAnchorElement* is a security-relevant sink; if it can be reached by a\n    value entirely under an attacker's control, that attacker can execute\n    arbitrary script in the context of the user's browser session through\n    injection of a `javascript:` URL.\n\n    There are many ways that JavaScript can manipulate builtin sinks, and we\n    will use [JSConformance][jsconf] policies to guide developers towards safe\n    patterns, and focus instead on web-component specific hazards including\n\n    *   an unchecked value reaches a builtin sink on a normal HTML element, e.g.\n        `\u003ca href=\"[[...]]\"\u003e` where `[[...]]` can be controlled by an attacker;\n    *   an unchecked value reaches a builtin sink inherited by a custom element,\n        e.g. `\u003cmy-custom-element onclick=\"[[...]]\"\u003e`;\n    *   an unchecked value reaches a custom property on a custom element that is\n        then forwarded to a builtin-sink by element or framework code, e.g.\n        `\u003cmy-custom-element my-url=\"[[...]]\"\u003e` and the custom element's shadow\n        DOM contains a builtin sink `\u003ctemplate\u003e\u003ca href=\"[[myUrl]]\"\u003e\u003c/template\u003e`;\n    *   an unchecked value reaches a builtin sink on a customized-builtin\n        element either directly (`\u003ca is=\"my-custom-link\" href=\"[[...]]\"\u003e`) or\n        indirectly (`\u003ca is=\"my-custom-link\" href=\"[[my-url]]\"\n        my-url=\"[[...]]\"\u003e`)\n    *   an unchecked value specifies a property name `\u003ca [[...]]=\"some-value\"\u003e`\n        or can specify the type of custom element `\u003ca is=\"[[...]]\"\u003e` or\n        `\u003c[[...]] src=\"some-value\"\u003e`.\n\nOur security goal is to allow element authors to write code that receives\nunchecked values, and routes them to builtin sinks without the risk of XSS,\nredirection attacks, etc. We do this by taking the burden of avoiding these\nattacks off the authors and reviewers of large amounts of application code and\nmove it into a small amount of vetted infrastructure code.\n\nIt is not a goal to address direct access to builtin sinks by JavaScript (e.g.\n`HTMLElement.setAttribute(...)` or `HTMLElement.innerHTML = ...`) as those are\nwell handled by existing [JSConformance][jsconf] policies.\n\n## High-Level Design\n\nWe could do this by tweaking Polymer to attach data provenance to properties and\nattributes but this would be deeply backward incompatible.\n\nWe could do this by static analysis of custom element definition, but this\nrequires analysis of JavaScript, and our existing JS type systems are unsound,\nso any sound analysis would require a lot of duplication of effort or would\nproduce many false positives.\n\nInstead we propose to use existing property value hooks (links at top) provided\nby Polymer. A security auditor can then check that a Polymer project is properly\nconfigured to load these hooks, and check that the project uses\n[JSConformance][jsconf] to prevent forgery of safe string values.\n\nWe provide a standalone importable HTML file `polymer-resin.html` that\nimplements `Polymer.sanitizeDOMValue` to intercept assignments to builtin sinks\ngiven values that originate from expressions specified in Polymer HTML. We also\nprovide a Polymer v1 shim that checks `Polymer.version` to see if it needs to\npatch `Polymer.Base._computeFinalAnnotationValue` to call the same sanitizer.\n\nA security auditor should check that *polymer-resin* is running early in the\npage render process. It should load and initialize before the applications main\nelement is instantiated so that it can intercept reflected XSS. Putting the HTML\nimport or script load immediately after the load of framework code suffices.\n\n### Text interpolation\n\nWhen text is interpolated\n\n```html\n\u003cscript\u003e\nbefore;\n[[interpolation]]\nafter;\n\u003c/script\u003e\n```\n\nPolymer denormalizes the DOM so that \"`before;`\", \"`[[interpolation]]`\" and\n\"`after;`\" are three different text nodes, and control reaches the sanitizer\nwith a *TextNode* as the node, and a *null* property name.\n\nWe use this to intercept text interpolation and allow it only when the\ncontent is human-readable HTML.\n\n## Life of a Polymer+Resin page\n\nNormally, when an HTML page is parsed, the browser knows that, for an `\u003cA\u003e`\nelement, it creates an *HTMLAElement* instance. The [custom elements draft\nspecification](https://www.w3.org/TR/custom-elements/) explicitly allows parsing\nof `\u003cmy-custom\u003e` before the JavaScript that will eventually define and register\n*HTMLMyCustomElement*.\n\n\u003ca name=\"custom-element-example\"\u003e\u003c/a\u003e\n\n```html\n\u003chead\u003e\n\u003cscript src=\"webcomponents-lite.js\"\u003e\u003c/script\u003e\n\u003clink rel=\"import\" href=\"custom-element.html\"\u003e\n\u003cbody\u003e\n\u003ccustom-element id=\"app\"\u003e\u003c/custom-element\u003e\n```\n\nThe life-cycle of a polymer app often looks like\n\n1.  Synchronously load *webcomponents-lite.js*\n2.  Start parsing HTML imported page *custom-element.html*\n3.  Indirectly HTML import load *polymer.html* which provides a framework for\n    custom elements and has the hooks we need to intercept bound data values.\n4.  Finish processing *custom-element.html* which registers the custom element\n    definition.\n5.  Instantiate `\u003ccustom-element\u003e`\n\n```js\nwindow.addEventListener('WebComponentsReady', …)\n```\n\nseems like it might run at the right time, and the `'HTMLImportsLoaded'` event\nis another good candidate.\n\nBoth run after the HTML element definitions have been loaded, and before the app\nhas been provisioned with state loaded from the server, but state that is\ninitialized based on location or query parameters will already have reached\ncustom elements, meaning [reflected XSS][reflected-xss] is still possible.\n\nTo prevent reflected XSS, we need to initialize after Polymer is loaded, and\nbefore the first custom element definition is registered (except for those\ndefined by Polymer internally).\n\n--------------------------------------------------------------------------------\n\n`polygerrit-ui/app/index.html` is a good example of a polymer app. The column on\nthe left shows the app before resin is added, and the column on the right shows\nhow we want it to work with Resin. Note that *polymer.html* is not explicitly\nloaded by the *index.html* page; it's loaded via a transitive HTML import.\n\n\u003c!-- mdformat off(mangles tables with empty cells in leftmost column) --\u003e\n\n| Without Resin                  | With Resin                            |\n| ------------------------------ | ------------------------------------- |\n| Enter `\u003chtml\u003e\u003chead\u003e`           | ditto                                 |\n| Load *webcomponents-lite.js*   | ditto                                 |\n|                                | HTML import *polymer.html*            |\n|                                | Load and configure *polymer-resin.js* |\n| Preload `\u003cgr-app\u003e` definition  | ditto                                 |\n| HTML import *polymer.html*     |                                       |\n| Load other element definitions | ditto                                 |\n| Instantiate `\u003cgr-app\u003e` element | ditto                                 |\n\n\u003c!-- mdformat on --\u003e\n\n--------------------------------------------------------------------------------\n\nWe provide a *polymer-resin.html*, an importable HTML file that does two things.\n\n1.  HTML import *polymer.html* so that we have a place to install the hooks\n2.  synchronously load a script to install the hooks\n\n## Bound data handler\n\nThe handler receives\n\n1.  node - A DOM element\n2.  property - the property or attribute name\n3.  info.type (polymer v1) or type (polymer v2) - one of \"attribute\" or\n    \"property\"\n4.  value - the untrusted value\n\nand returns a safe value.\n\n## Sanitize DOM Value Algorithm\n\nWhen sanitizing a property or attribute value we\n\n1.  Allow all falsey values. This allows resetting, initializing to\n    blank/nullish. This has the side effect of also allowing `0`, `NaN`, `false`\n    which we deem low-risk.\n2.  Classify the containing element as customized or not-customized.\n3.  Find a clean (no non-default attributes or JS muckery) proxy for the\n    element.\n    *   For custom elements, this is a vanilla *HTMLElement* instance.\n    *   For a builtin or customized-builtin element, it is a vanilla\n        `document.createElement(builtinElementName)`.\n    *   For legacy elements, treat as builtins.\n    *   For customizable elements (those which meet the naming convention but\n        for which no custom element constructor has yet been registered), treat\n        as a custom element. Our analysis is dynamic, so we need not assume the\n        worst.\n4.  If the proxy does not have the named property in it, then allow any value\n    without unwrapping or checking typed string values.\n5.  Otherwise, if the value is whitelisted according to the element/attribute\n    curated contract tables (JS namespace `security.html.contracts`), then\n    unwrap any typed string values and allow.\n6.  Otherwise, log as appropriate, and return a known-safe value.\n\nWe could break from the loop if the prototype has an own property with the given\nname. We could memoize the fact that we found a result with the original key if\nwe’re willing to assume that no properties are deleted from prototypes during\nprogram execution.\n\n## Table of Security-Relevant Properties and Attributes\n\nThe `security.html.contracts` module captures builtin HTML element and attribute\nrelationships, and we apply the following filters.\n\nAttribute Type       | Privileged Typed String Type | Raw Value Filter\n-------------------- | ---------------------------- | ----------------\nNONE                 | none                         | allow\nSAFE_HTML            | goog.html.SafeHtml           | goog.string.htmlEscape\nSAFE_URL             | goog.html.SafeUrl            | goog.html.SafeUrl.sanitize\nTRUSTED_RESOURCE_URL | goog.html.TrustedResourceUrl | none\nSAFE_STYLE           | goog.html.SafeStyle          | reject\nSAFE_SCRIPT          | goog.html.SafeScript         | reject\nENUM                 | none                         | whitelist per element/attribute\nCONSTANT             | goog.string.Const            | reject\nIDENTIFIER           | none                         | reject\n\nThe definitions of privileged types assume polymer-resin is installed with the\n[closure bridge][].  Other [safe type bridges][] may define privileged types differently.\n\nNo processing is applied to custom properties.\n\nValues that are of the privileged type are unwrapped and allowed to\nreach a builtin attribute alias.\n\nValues that are of other type string types are unwrapped before being filtered.\n\nRejected values are replaced with an innocuous value.\n\n## Testing\n\nThere are two main failure modes:\n\n1.  False negatives -- a failure to apply the appropriate handler to a payload.\n2.  False positives -- trustworthy code in a custom element definition\n    constructs an attribute value that triggers a filter but does not wrap it in\n    an appropriate safe string type. For example\n    `myElement.href = “javascript:myVeryOwnFunction()”`\n\nWe can check for false negatives by writing custom elements\n\n```html\n    \u003cdom-module id=”xss-me”\u003e\n      \u003ctemplate\u003e\n         \u003ca href=\"{{myhref}}\"\u003e\n```\n\nthat use the relevant properties, and instantiating them with variables bound to\nknown payloads like `{ “myhref”: “javascript:shouldNotBeCalled()” }`.\n\nWe will also write regression tests for polygerrit that programmatically creates\nan author, changelist, and review comment with common payloads, and uses\nselenium to view the pages and check for breaches.\n\nWe will try to get a handle on the kinds of false positives and their frequency\nby running polygerrit/app/*_test.sh, and looking for regressions.\n\nRunning both an instrumented version, and an uninstrumented version side by side\nin two browser windows should make changes in behavior apparent.\n\nThere are a few minor failure modes:\n\n1.  Failure to load early enough or at all. Manual inspection of the JS debugger\n    when running polygerrit should suffice. poly-resin.js could also set a\n    property after load that the app could assert.\n2.  Failure to recognize and reject unsafe values in a handler. Since we’re\n    reusing Soy sanitizers which have a long history of use in production by\n    large projects, I consider this low risk.\n\n## Deployment\n\nGerrit builds via bazel but loads most of its scripts via bower. Polymer resin\nis available as a [bower\ncomponent](http://bower.herokuapp.com/packages/polymer-resin).\n\nThere are three deployment options.\n\n1.  `polymer-resin.html` which is best for closure-friendly polymer apps.\n2.  `standalone/polymer-resin.html` which includes a single JS bundle that\n    includes pre-compiled JS.\n3.  `standalone/polymer-resin-debug.html` which is like the previous file but\n    the JS is not obfuscated and it logs to the console whenever a property\n    value is rejected.\n\n--------------------------------------------------------------------------------\n\nTo deploy in the [custom element example](#custom-element-example) the head\nchanges from\n\n```html\n\u003cscript src=\"webcomponents-lite.js\"\u003e\u003c/script\u003e\n\u003clink rel=\"import\" href=\"custom-element.html\"\u003e\n```\n\nto\n\n```html\n\u003cscript src=\"webcomponents-lite.js\"\u003e\u003c/script\u003e\n\u003clink rel=\"import\" href=\"polymer-resin/polymer-resin.html\"\u003e\n\u003cscript\u003e\n// This step is essential to the security of this project.\nsecurity.polymer_resin.install({ /* config */ });\n\u003c/script\u003e\n\u003clink rel=\"import\" href=\"custom-element.html\"\u003e\n```\n\nor with one of the standalone variants above. The `\u003cscript\u003e` is required because\nit forces the imports above it to be handled before *custom-element.html*. The\n\u003ctt\u003einstall\u003c/tt\u003e call is explained in [configuring][].\n\n## Running tests from the command line\n\nPer https://github.com/Polymer/web-component-tester\nmake sure that you have bower installed and have run `bower update`.\nThen use the test script.\n\n```bash\n$ ./run_tests.sh\n```\n\n# Running tests in the browser\n\nFrom the project root\n\n```bash\n$ ./run_tests.sh -p -l chrome\n```\n\ncauses it to keep the server open.\nSee the log output for the localhost URL to browse to.\n\n[getting-started]: https://github.com/Polymer/polymer-resin/blob/master/getting-started.md#getting-started\n[configuring]: https://github.com/Polymer/polymer-resin/blob/master/getting-started.md#configuring\n[closure bridge]: https://github.com/Polymer/polymer-resin/blob/master/closure-bridge.js\n[safe type bridges]: https://github.com/Polymer/polymer-resin/blob/master/getting-started.md#-safetypesbridge-mybridgefn-\n\n[reflected-xss]: https://www.owasp.org/index.php/Testing_for_Reflected_Cross_site_scripting_(OTG-INPVAL-001)#Summary\n[webcomponents]: https://developer.mozilla.org/en-US/docs/Web/Web_Components#Specifications\n[polygerrit-ui]: https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/\n[poly-v1]: https://github.com/Polymer/polymer/blob/7e732f6b2dc2fd49f78a3993828283d997de63bd/src/standard/effectBuilder.html#L343\n[poly-v2]: https://github.com/Polymer/polymer/blob/2.0-preview/lib/mixins/property-effects.html#L555-L556\n[jsconf]: https://github.com/google/closure-compiler/wiki/JS-Conformance-Framework\n[soyutils]: https://github.com/google/closure-templates/blob/master/javascript/soyutils_usegoog.js#L1407\n[safe-html-types]: https://github.com/google/safe-html-types/blob/master/doc/safehtml-types.md\n[safe-html-types-js]: https://google.github.io/closure-library/api/goog.html.SafeHtml.html\n[autosan]: https://security.googleblog.com/2009/03/reducing-xss-by-way-of-automatic.html\n[soy-sec]: https://developers.google.com/closure/templates/docs/security\n[build-status]: https://travis-ci.org/Polymer/polymer-resin.svg?branch=master\n[build-dashboard]: https://travis-ci.org/Polymer/polymer-resin\n[idl-reflect]: https://html.spec.whatwg.org/#cereactions\n[idl-custom-element-callbacks]: https://chromium.googlesource.com/chromium/src/+/master/third_party/WebKit/Source/bindings/IDLExtendedAttributes.md\n[idl-cereactions]: https://html.spec.whatwg.org/#cereactions\n[same-origin]: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpolymer%2Fpolymer-resin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpolymer%2Fpolymer-resin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpolymer%2Fpolymer-resin/lists"}