Skip to content

Commit ce8c3b3

Browse files
msamuelmikesamuel
msamuel
authored andcommitted
Add polymer-resin API to enable migration and custom report handling.
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
1 parent 921e579 commit ce8c3b3

File tree

3 files changed

+160
-19
lines changed

3 files changed

+160
-19
lines changed

polymer-resin.js

+137-11
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
// Will that cause problems if parts of the web components API are defined
1818
// natively instead of polyfilled?
1919

20+
goog.provide('security.polymer_resin.UNSAFE_passThruDisallowedValues');
2021
goog.provide('security.polymer_resin.allowIdentifierWithPrefix');
22+
goog.provide('security.polymer_resin.setReportHandler');
2123

2224
goog.require('goog.dom.NodeType');
2325
goog.require('goog.html.SafeHtml');
@@ -48,6 +50,32 @@ goog.require('security.polymer_resin.hintUsesDeprecatedRegisterElement');
4850
var ValueHandler;
4951

5052

53+
/**
54+
* When called with (true), disallowed values will not be replaced so may reach
55+
* unsafe browser sinks resulting in a security violation.
56+
* <p>
57+
* This mode is provided only to allow testing of an application
58+
* to find and compile the kinds of false positives triggered by
59+
* an application that is being migrated to use polymer resin.
60+
* <p>
61+
* This MUST NOT be used in production with end users and
62+
* MUST NOT be set based on any attacker-controllable state like
63+
* URL parameters.
64+
* <p>
65+
* If you never call this function, you are safer.
66+
*
67+
* <p>
68+
* When not in goog.DEBUG mode, this is a no-op.
69+
*
70+
* @param {boolean} enable Pass true to enable UNSAFE mode.
71+
*/
72+
security.polymer_resin.UNSAFE_passThruDisallowedValues = function (enable) {
73+
if (goog.DEBUG) {
74+
security.polymer_resin.allowUnsafeValues_ = enable === true;
75+
}
76+
};
77+
78+
5179
/**
5280
* Specifies that attributes with type IDENTIFIER that have the given
5381
* prefix should be allowed.
@@ -65,10 +93,56 @@ security.polymer_resin.allowIdentifierWithPrefix = function (prefix) {
6593
security.polymer_resin.allowedIdentifierPattern_.source
6694
+ '|^' + goog.string.regExpEscape(prefix));
6795
};
96+
97+
/**
98+
* Sets a callback to receive reports about rejected values and module status.
99+
*
100+
* <p>
101+
* By default, if {@code goog.DEBUG} is false at init time, reportHandler is
102+
* never called, and if {@code goog.DEBUG} is true at init time, reportHandler
103+
* logs to the JS developer console.
104+
* <p>
105+
* Assuming it is enabled, either via {@code goog.DEBUG} or an explicit call to
106+
* this setter, then it is called on every rejected value, and on major events
107+
* like module initialization.
108+
* <p>
109+
* This may be used to identify false positives during debugging; to compile
110+
* lists of false positives when migrating; or to gather telemetry by
111+
*
112+
* @param {?function (boolean, string, ...*)} reportHandler
113+
* A function that takes (isDisallowedValue, printfFormatString, printfArgs).
114+
* The arguments are ready to forward straight to the console with minimal
115+
* overhead.
116+
* <p>
117+
* If isDisallowedValue is true then the args have the printArgs have the form
118+
* [contextNodeName, nodeName, attributeOrPropertyName, disallowedValue].
119+
* <p>
120+
* The context node is the element being manipulated, or if nodeName is
121+
* {@code "#text"},
122+
* then contextNode is the parent of the text node being manipulated, so
123+
* the contextNode should always be an element or document fragment.
124+
* In that case, attributeOrPropertyName can be ignored.
125+
* <p>
126+
* If reportHandler is null then reporting is disabled.
127+
*/
128+
security.polymer_resin.setReportHandler = function (reportHandler) {
129+
security.polymer_resin.reportHandler_ = reportHandler || null;
130+
};
131+
132+
133+
134+
goog.exportSymbol(
135+
'security.polymer_resin.UNSAFE_passThruDisallowedValues',
136+
security.polymer_resin.UNSAFE_passThruDisallowedValues);
137+
68138
goog.exportSymbol(
69139
'security.polymer_resin.allowIdentifierWithPrefix',
70140
security.polymer_resin.allowIdentifierWithPrefix);
71141

142+
goog.exportSymbol(
143+
'security.polymer_resin.setReportHandler',
144+
security.polymer_resin.setReportHandler);
145+
72146

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

82156

157+
/**
158+
* @type {boolean}
159+
security.polymer_resin.ViolationHandlingMode}
160+
* @private
161+
*/
162+
security.polymer_resin.allowUnsafeValues_ = false;
163+
164+
165+
/**
166+
* Undefined means never set (see default behavior under docs for
167+
* setter above), null means disabled.
168+
*
169+
* @type {function (boolean, string, ...*)|null|undefined}
170+
* @private
171+
*/
172+
security.polymer_resin.reportHandler_ = undefined;
173+
174+
83175
(function () {
84176
"use strict";
85177

86178
function initResin() {
179+
if (goog.DEBUG && security.polymer_resin.reportHandler_ === undefined
180+
&& typeof console !== 'undefined') {
181+
security.polymer_resin.reportHandler_ =
182+
function (isViolation, formatString, var_args) {
183+
var consoleArgs = [formatString];
184+
for (var i = 2, n = arguments.length; i < n; ++i) {
185+
consoleArgs[i - 1] = arguments[i];
186+
}
187+
if (isViolation) {
188+
console.warn.apply(console, consoleArgs);
189+
} else {
190+
console.log.apply(console, consoleArgs);
191+
}
192+
};
193+
}
194+
87195
// TODO: check not in IE quirks mode.
88-
console.log('initResin');
196+
if (security.polymer_resin.reportHandler_) {
197+
// Emitting this allows an integrator to tell where resin is
198+
// installing relative to other code that is running in the app.
199+
security.polymer_resin.reportHandler_(false, 'initResin');
200+
}
89201

90202
/**
91203
* @param {string} name
@@ -214,10 +326,14 @@ security.polymer_resin.allowedIdentifierPattern_ = /^$/;
214326
}
215327
}
216328
}
217-
if (goog.DEBUG && 'undefined' !== typeof console) {
218-
console.warn('Failed to sanitize text %o in %o',
219-
value, node.parentElement);
329+
330+
if (security.polymer_resin.reportHandler_) {
331+
security.polymer_resin.reportHandler_(
332+
true, 'Failed to sanitize %s %s%s node to value %O',
333+
node.parentElement && node.parentElement.nodeName,
334+
'#text', '', value);
220335
}
336+
221337
return INNOCUOUS_STRING;
222338
}
223339

@@ -294,10 +410,12 @@ security.polymer_resin.allowedIdentifierPattern_ = /^$/;
294410
return safeValue;
295411
}
296412
}
297-
if (goog.DEBUG && 'undefined' !== typeof console) {
298-
console.warn('Failed to sanitize <%s %s="%o">',
299-
elementName, attrName, value);
413+
if (security.polymer_resin.reportHandler_) {
414+
security.polymer_resin.reportHandler_(
415+
true, 'Failed to sanitize in %s: <%s %s="%O">',
416+
elementName, elementName, attrName, value);
300417
}
418+
301419
return safeValue;
302420
}
303421

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

339-
return sanitize(node, name, type, finalValue);
457+
var safeValue = sanitize(node, name, type, finalValue);
458+
459+
return (
460+
security.polymer_resin.allowUnsafeValues_
461+
? finalValue : safeValue);
340462
};
341463
Polymer.Base._computeFinalAnnotationValue =
342464
computeFinalAnnotationSafeValue;
@@ -351,10 +473,14 @@ security.polymer_resin.allowedIdentifierPattern_ = /^$/;
351473
var origSanitize = Polymer.sanitizeDOMValue;
352474
var sanitizeDOMValue =
353475
function sanitizeDOMValue(value, name, type, node) {
354-
var sanitizedValue = origSanitize
476+
var origSanitizedValue = origSanitize
355477
? origSanitize.call(Polymer, value, name, type, node)
356478
: value;
357-
return sanitize(node, name, type, sanitizedValue);
479+
var safeValue = sanitize(node, name, type, origSanitizedValue);
480+
481+
return (
482+
security.polymer_resin.allowUnsafeValues_
483+
? origSanitizedValue : safeValue);
358484
};
359485
Polymer.sanitizeDOMValue = sanitizeDOMValue;
360486
if (Polymer.sanitizeDOMValue !== sanitizeDOMValue) {
@@ -407,7 +533,7 @@ security.polymer_resin.allowedIdentifierPattern_ = /^$/;
407533
* @param {string} v attribute value
408534
* @return {string}
409535
*/
410-
function plainTextToHtml(a, e, v) {
536+
function plainTextToHtml(e, a, v) {
411537
return goog.string.htmlEscape(v);
412538
}),
413539
safeReplacement: null,

standalone/polymer-resin-debug.js

+22-7
Original file line numberDiff line numberDiff line change
@@ -2926,10 +2926,18 @@ security.polymer_resin.classifyElement = function(name, ctor) {
29262926
}
29272927
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;
29282928
};
2929+
security.polymer_resin.UNSAFE_passThruDisallowedValues = function(enable) {
2930+
goog.DEBUG && (security.polymer_resin.allowUnsafeValues_ = !0 === enable);
2931+
};
29292932
security.polymer_resin.allowIdentifierWithPrefix = function(prefix) {
29302933
security.polymer_resin.allowedIdentifierPattern_ = new RegExp(security.polymer_resin.allowedIdentifierPattern_.source + "|^" + goog.string.regExpEscape(prefix));
29312934
};
2935+
security.polymer_resin.setReportHandler = function(reportHandler) {
2936+
security.polymer_resin.reportHandler_ = reportHandler || null;
2937+
};
29322938
security.polymer_resin.allowedIdentifierPattern_ = /^$/;
2939+
security.polymer_resin.allowUnsafeValues_ = !1;
2940+
security.polymer_resin.reportHandler_ = void 0;
29332941
(function() {
29342942
function initResin() {
29352943
function getAttributeValue(name) {
@@ -2960,7 +2968,7 @@ security.polymer_resin.allowedIdentifierPattern_ = /^$/;
29602968
}
29612969
}
29622970
}
2963-
goog.DEBUG && "undefined" !== typeof console && console.warn("Failed to sanitize text %o in %o", value, node.parentElement);
2971+
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);
29642972
return "zClosurez";
29652973
}
29662974
var elementName = node.localName;
@@ -3001,10 +3009,16 @@ security.polymer_resin.allowedIdentifierPattern_ = /^$/;
30013009
return safeValue;
30023010
}
30033011
}
3004-
goog.DEBUG && "undefined" !== typeof console && console.warn('Failed to sanitize <%s %s="%o">', elementName, attrName, value);
3012+
security.polymer_resin.reportHandler_ && security.polymer_resin.reportHandler_(!0, 'Failed to sanitize in %s: <%s %s="%O">', elementName, elementName, attrName, value);
30053013
return safeValue;
30063014
}
3007-
console.log("initResin");
3015+
goog.DEBUG && void 0 === security.polymer_resin.reportHandler_ && "undefined" !== typeof console && (security.polymer_resin.reportHandler_ = function(isViolation, formatString, var_args) {
3016+
for (var consoleArgs = [formatString], i = 2, n = arguments.length; i < n; ++i) {
3017+
consoleArgs[i - 1] = arguments[i];
3018+
}
3019+
isViolation ? console.warn.apply(console, consoleArgs) : console.log.apply(console, consoleArgs);
3020+
});
3021+
security.polymer_resin.reportHandler_ && security.polymer_resin.reportHandler_(!1, "initResin");
30083022
var uncustomizedProxies = {}, VANILLA_HTML_ELEMENT_ = document.createElement("polyresinuncustomized");
30093023
if (/^1\./.test(Polymer.version)) {
30103024
security.polymer_resin.hintUsesDeprecatedRegisterElement();
@@ -3015,16 +3029,17 @@ security.polymer_resin.allowedIdentifierPattern_ = /^$/;
30153029
} else {
30163030
name = property, type = info && info.kind || "property";
30173031
}
3018-
return sanitize(node, name, type, finalValue);
3032+
var safeValue = sanitize(node, name, type, finalValue);
3033+
return security.polymer_resin.allowUnsafeValues_ ? finalValue : safeValue;
30193034
};
30203035
Polymer.Base._computeFinalAnnotationValue = computeFinalAnnotationSafeValue;
30213036
if (Polymer.Base._computeFinalAnnotationValue !== computeFinalAnnotationSafeValue) {
30223037
throw Error("Cannot replace _computeFinalAnnotationValue. Is Polymer frozen?");
30233038
}
30243039
} else {
30253040
var origSanitize = Polymer.sanitizeDOMValue, sanitizeDOMValue = function(value, name, type, node) {
3026-
var sanitizedValue = origSanitize ? origSanitize.call(Polymer, value, name, type, node) : value;
3027-
return sanitize(node, name, type, sanitizedValue);
3041+
var origSanitizedValue = origSanitize ? origSanitize.call(Polymer, value, name, type, node) : value, safeValue = sanitize(node, name, type, origSanitizedValue);
3042+
return security.polymer_resin.allowUnsafeValues_ ? origSanitizedValue : safeValue;
30283043
};
30293044
Polymer.sanitizeDOMValue = sanitizeDOMValue;
30303045
if (Polymer.sanitizeDOMValue !== sanitizeDOMValue) {
@@ -3034,7 +3049,7 @@ security.polymer_resin.allowedIdentifierPattern_ = /^$/;
30343049
}
30353050
var VALUE_HANDLERS_ = [];
30363051
VALUE_HANDLERS_[security.html.contracts.AttrType.NONE] = {filter:null, safeReplacement:null, typeToUnwrap:null, unwrap:null};
3037-
VALUE_HANDLERS_[security.html.contracts.AttrType.SAFE_HTML] = {filter:function(a, e, v) {
3052+
VALUE_HANDLERS_[security.html.contracts.AttrType.SAFE_HTML] = {filter:function(e, a, v) {
30383053
return goog.string.htmlEscape(v);
30393054
}, safeReplacement:null, typeToUnwrap:goog.html.SafeHtml, unwrap:goog.html.SafeHtml.unwrap};
30403055
VALUE_HANDLERS_[security.html.contracts.AttrType.SAFE_URL] = {filter:function(e, a, v) {

standalone/polymer-resin.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)