Skip to content

Commit

Permalink
Add polymer-resin API to enable migration and custom report handling.
Browse files Browse the repository at this point in the history
YT would like to be able to use goog.log instead of the JS dev console.
The report handler API enables that, without pulling goog.log in for
the standalone compiled version.

In the getting-started docs, I need a story to explain how to get
telemetry about false positives.
When migrating an app, one might do

  var polymerResinDebugTelemetry = {};

  // Allow application to progress as normal so we can exercise
  // as much of the API as possible without working around problems
  // caused by false positives.
  // HACK: DO NOT  SUBMIT.
  security.polymer_resin.UNSAFE_passThruDisallowedValues(true);
  // Collect violation counts in a table instead of logging.
  security.polymer_resin.setReportHandler(
      function (isDisallowedValue, fmtString,
                optContextNodeName, optNodeName, optAttrName, optValue,
		var_args) {
        if (isDisallowedValue) {
	  var key = optContextNodeName + ' : ' + optNodeName + ' : '
	      + optAttrName;
	  polymerResingDebugTelemetry[key] =
	      (polymerResingDebugTelemetry[key] || 0) + 1;
        }
      });
  // Can be called from console.
  function dumpPolymerResinDebugTelemetry() {
    console.log(JSON.stringify(polymerResinDebugTelemetry, null, 2));
  }

The report handler API can also be used to feed violations during
production back to the server for later investigation.

Tested:
I have not added tests for the new APIs.
I ran existing test suite to test default behavior.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=157109206
  • Loading branch information
msamuel authored and mikesamuel committed May 26, 2017
1 parent 921e579 commit ce8c3b3
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 19 deletions.
148 changes: 137 additions & 11 deletions polymer-resin.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
// Will that cause problems if parts of the web components API are defined
// natively instead of polyfilled?

goog.provide('security.polymer_resin.UNSAFE_passThruDisallowedValues');
goog.provide('security.polymer_resin.allowIdentifierWithPrefix');
goog.provide('security.polymer_resin.setReportHandler');

goog.require('goog.dom.NodeType');
goog.require('goog.html.SafeHtml');
Expand Down Expand Up @@ -48,6 +50,32 @@ goog.require('security.polymer_resin.hintUsesDeprecatedRegisterElement');
var ValueHandler;


/**
* When called with (true), disallowed values will not be replaced so may reach
* unsafe browser sinks resulting in a security violation.
* <p>
* This mode is provided only to allow testing of an application
* to find and compile the kinds of false positives triggered by
* an application that is being migrated to use polymer resin.
* <p>
* This MUST NOT be used in production with end users and
* MUST NOT be set based on any attacker-controllable state like
* URL parameters.
* <p>
* If you never call this function, you are safer.
*
* <p>
* When not in goog.DEBUG mode, this is a no-op.
*
* @param {boolean} enable Pass true to enable UNSAFE mode.
*/
security.polymer_resin.UNSAFE_passThruDisallowedValues = function (enable) {
if (goog.DEBUG) {
security.polymer_resin.allowUnsafeValues_ = enable === true;
}
};


/**
* Specifies that attributes with type IDENTIFIER that have the given
* prefix should be allowed.
Expand All @@ -65,10 +93,56 @@ security.polymer_resin.allowIdentifierWithPrefix = function (prefix) {
security.polymer_resin.allowedIdentifierPattern_.source
+ '|^' + goog.string.regExpEscape(prefix));
};

/**
* Sets a callback to receive reports about rejected values and module status.
*
* <p>
* By default, if {@code goog.DEBUG} is false at init time, reportHandler is
* never called, and if {@code goog.DEBUG} is true at init time, reportHandler
* logs to the JS developer console.
* <p>
* Assuming it is enabled, either via {@code goog.DEBUG} or an explicit call to
* this setter, then it is called on every rejected value, and on major events
* like module initialization.
* <p>
* This may be used to identify false positives during debugging; to compile
* lists of false positives when migrating; or to gather telemetry by
*
* @param {?function (boolean, string, ...*)} reportHandler
* A function that takes (isDisallowedValue, printfFormatString, printfArgs).
* The arguments are ready to forward straight to the console with minimal
* overhead.
* <p>
* If isDisallowedValue is true then the args have the printArgs have the form
* [contextNodeName, nodeName, attributeOrPropertyName, disallowedValue].
* <p>
* The context node is the element being manipulated, or if nodeName is
* {@code "#text"},
* then contextNode is the parent of the text node being manipulated, so
* the contextNode should always be an element or document fragment.
* In that case, attributeOrPropertyName can be ignored.
* <p>
* If reportHandler is null then reporting is disabled.
*/
security.polymer_resin.setReportHandler = function (reportHandler) {
security.polymer_resin.reportHandler_ = reportHandler || null;
};



goog.exportSymbol(
'security.polymer_resin.UNSAFE_passThruDisallowedValues',
security.polymer_resin.UNSAFE_passThruDisallowedValues);

goog.exportSymbol(
'security.polymer_resin.allowIdentifierWithPrefix',
security.polymer_resin.allowIdentifierWithPrefix);

goog.exportSymbol(
'security.polymer_resin.setReportHandler',
security.polymer_resin.setReportHandler);


/**
* @type {!RegExp}
Expand All @@ -80,12 +154,50 @@ security.polymer_resin.allowedIdentifierPattern_ = /^$/;
// authority.


/**
* @type {boolean}
security.polymer_resin.ViolationHandlingMode}
* @private
*/
security.polymer_resin.allowUnsafeValues_ = false;


/**
* Undefined means never set (see default behavior under docs for
* setter above), null means disabled.
*
* @type {function (boolean, string, ...*)|null|undefined}
* @private
*/
security.polymer_resin.reportHandler_ = undefined;


(function () {
"use strict";

function initResin() {
if (goog.DEBUG && security.polymer_resin.reportHandler_ === undefined
&& typeof console !== 'undefined') {
security.polymer_resin.reportHandler_ =
function (isViolation, formatString, var_args) {
var consoleArgs = [formatString];
for (var i = 2, n = arguments.length; i < n; ++i) {
consoleArgs[i - 1] = arguments[i];
}
if (isViolation) {
console.warn.apply(console, consoleArgs);
} else {
console.log.apply(console, consoleArgs);
}
};
}

// TODO: check not in IE quirks mode.
console.log('initResin');
if (security.polymer_resin.reportHandler_) {
// Emitting this allows an integrator to tell where resin is
// installing relative to other code that is running in the app.
security.polymer_resin.reportHandler_(false, 'initResin');
}

/**
* @param {string} name
Expand Down Expand Up @@ -214,10 +326,14 @@ security.polymer_resin.allowedIdentifierPattern_ = /^$/;
}
}
}
if (goog.DEBUG && 'undefined' !== typeof console) {
console.warn('Failed to sanitize text %o in %o',
value, node.parentElement);

if (security.polymer_resin.reportHandler_) {
security.polymer_resin.reportHandler_(
true, 'Failed to sanitize %s %s%s node to value %O',
node.parentElement && node.parentElement.nodeName,
'#text', '', value);
}

return INNOCUOUS_STRING;
}

Expand Down Expand Up @@ -294,10 +410,12 @@ security.polymer_resin.allowedIdentifierPattern_ = /^$/;
return safeValue;
}
}
if (goog.DEBUG && 'undefined' !== typeof console) {
console.warn('Failed to sanitize <%s %s="%o">',
elementName, attrName, value);
if (security.polymer_resin.reportHandler_) {
security.polymer_resin.reportHandler_(
true, 'Failed to sanitize in %s: <%s %s="%O">',
elementName, elementName, attrName, value);
}

return safeValue;
}

Expand Down Expand Up @@ -336,7 +454,11 @@ security.polymer_resin.allowedIdentifierPattern_ = /^$/;
type = info && info.kind || 'property';
}

return sanitize(node, name, type, finalValue);
var safeValue = sanitize(node, name, type, finalValue);

return (
security.polymer_resin.allowUnsafeValues_
? finalValue : safeValue);
};
Polymer.Base._computeFinalAnnotationValue =
computeFinalAnnotationSafeValue;
Expand All @@ -351,10 +473,14 @@ security.polymer_resin.allowedIdentifierPattern_ = /^$/;
var origSanitize = Polymer.sanitizeDOMValue;
var sanitizeDOMValue =
function sanitizeDOMValue(value, name, type, node) {
var sanitizedValue = origSanitize
var origSanitizedValue = origSanitize
? origSanitize.call(Polymer, value, name, type, node)
: value;
return sanitize(node, name, type, sanitizedValue);
var safeValue = sanitize(node, name, type, origSanitizedValue);

return (
security.polymer_resin.allowUnsafeValues_
? origSanitizedValue : safeValue);
};
Polymer.sanitizeDOMValue = sanitizeDOMValue;
if (Polymer.sanitizeDOMValue !== sanitizeDOMValue) {
Expand Down Expand Up @@ -407,7 +533,7 @@ security.polymer_resin.allowedIdentifierPattern_ = /^$/;
* @param {string} v attribute value
* @return {string}
*/
function plainTextToHtml(a, e, v) {
function plainTextToHtml(e, a, v) {
return goog.string.htmlEscape(v);
}),
safeReplacement: null,
Expand Down
29 changes: 22 additions & 7 deletions standalone/polymer-resin-debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -2926,10 +2926,18 @@ security.polymer_resin.classifyElement = function(name, ctor) {
}
return customElementsRegistry && customElementsRegistry.get(name) || security.polymer_resin.docRegisteredElements_[name] === security.polymer_resin.docRegisteredElements_ ? security.polymer_resin.CustomElementClassification.CUSTOM : ctor === HTMLUnknownElement ? security.polymer_resin.CustomElementClassification.LEGACY : ctor === HTMLElement && security.polymer_resin.VALID_CUSTOM_ELEMENT_NAME_REGEX_.test(name) ? security.polymer_resin.CustomElementClassification.CUSTOMIZABLE : security.polymer_resin.CustomElementClassification.BUILTIN;
};
security.polymer_resin.UNSAFE_passThruDisallowedValues = function(enable) {
goog.DEBUG && (security.polymer_resin.allowUnsafeValues_ = !0 === enable);
};
security.polymer_resin.allowIdentifierWithPrefix = function(prefix) {
security.polymer_resin.allowedIdentifierPattern_ = new RegExp(security.polymer_resin.allowedIdentifierPattern_.source + "|^" + goog.string.regExpEscape(prefix));
};
security.polymer_resin.setReportHandler = function(reportHandler) {
security.polymer_resin.reportHandler_ = reportHandler || null;
};
security.polymer_resin.allowedIdentifierPattern_ = /^$/;
security.polymer_resin.allowUnsafeValues_ = !1;
security.polymer_resin.reportHandler_ = void 0;
(function() {
function initResin() {
function getAttributeValue(name) {
Expand Down Expand Up @@ -2960,7 +2968,7 @@ security.polymer_resin.allowedIdentifierPattern_ = /^$/;
}
}
}
goog.DEBUG && "undefined" !== typeof console && console.warn("Failed to sanitize text %o in %o", value, node.parentElement);
security.polymer_resin.reportHandler_ && security.polymer_resin.reportHandler_(!0, "Failed to sanitize %s %s%s node to value %O", node.parentElement && node.parentElement.nodeName, "#text", "", value);
return "zClosurez";
}
var elementName = node.localName;
Expand Down Expand Up @@ -3001,10 +3009,16 @@ security.polymer_resin.allowedIdentifierPattern_ = /^$/;
return safeValue;
}
}
goog.DEBUG && "undefined" !== typeof console && console.warn('Failed to sanitize <%s %s="%o">', elementName, attrName, value);
security.polymer_resin.reportHandler_ && security.polymer_resin.reportHandler_(!0, 'Failed to sanitize in %s: <%s %s="%O">', elementName, elementName, attrName, value);
return safeValue;
}
console.log("initResin");
goog.DEBUG && void 0 === security.polymer_resin.reportHandler_ && "undefined" !== typeof console && (security.polymer_resin.reportHandler_ = function(isViolation, formatString, var_args) {
for (var consoleArgs = [formatString], i = 2, n = arguments.length; i < n; ++i) {
consoleArgs[i - 1] = arguments[i];
}
isViolation ? console.warn.apply(console, consoleArgs) : console.log.apply(console, consoleArgs);
});
security.polymer_resin.reportHandler_ && security.polymer_resin.reportHandler_(!1, "initResin");
var uncustomizedProxies = {}, VANILLA_HTML_ELEMENT_ = document.createElement("polyresinuncustomized");
if (/^1\./.test(Polymer.version)) {
security.polymer_resin.hintUsesDeprecatedRegisterElement();
Expand All @@ -3015,16 +3029,17 @@ security.polymer_resin.allowedIdentifierPattern_ = /^$/;
} else {
name = property, type = info && info.kind || "property";
}
return sanitize(node, name, type, finalValue);
var safeValue = sanitize(node, name, type, finalValue);
return security.polymer_resin.allowUnsafeValues_ ? finalValue : safeValue;
};
Polymer.Base._computeFinalAnnotationValue = computeFinalAnnotationSafeValue;
if (Polymer.Base._computeFinalAnnotationValue !== computeFinalAnnotationSafeValue) {
throw Error("Cannot replace _computeFinalAnnotationValue. Is Polymer frozen?");
}
} else {
var origSanitize = Polymer.sanitizeDOMValue, sanitizeDOMValue = function(value, name, type, node) {
var sanitizedValue = origSanitize ? origSanitize.call(Polymer, value, name, type, node) : value;
return sanitize(node, name, type, sanitizedValue);
var origSanitizedValue = origSanitize ? origSanitize.call(Polymer, value, name, type, node) : value, safeValue = sanitize(node, name, type, origSanitizedValue);
return security.polymer_resin.allowUnsafeValues_ ? origSanitizedValue : safeValue;
};
Polymer.sanitizeDOMValue = sanitizeDOMValue;
if (Polymer.sanitizeDOMValue !== sanitizeDOMValue) {
Expand All @@ -3034,7 +3049,7 @@ security.polymer_resin.allowedIdentifierPattern_ = /^$/;
}
var VALUE_HANDLERS_ = [];
VALUE_HANDLERS_[security.html.contracts.AttrType.NONE] = {filter:null, safeReplacement:null, typeToUnwrap:null, unwrap:null};
VALUE_HANDLERS_[security.html.contracts.AttrType.SAFE_HTML] = {filter:function(a, e, v) {
VALUE_HANDLERS_[security.html.contracts.AttrType.SAFE_HTML] = {filter:function(e, a, v) {
return goog.string.htmlEscape(v);
}, safeReplacement:null, typeToUnwrap:goog.html.SafeHtml, unwrap:goog.html.SafeHtml.unwrap};
VALUE_HANDLERS_[security.html.contracts.AttrType.SAFE_URL] = {filter:function(e, a, v) {
Expand Down
2 changes: 1 addition & 1 deletion standalone/polymer-resin.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit ce8c3b3

Please sign in to comment.