diff --git a/extensions/amp-analytics/0.1/config.js b/extensions/amp-analytics/0.1/config.js index 36b2d81b4d0b..7fc0360c397d 100644 --- a/extensions/amp-analytics/0.1/config.js +++ b/extensions/amp-analytics/0.1/config.js @@ -499,8 +499,14 @@ export function mergeObjects(from, to, opt_predefinedVendorConfig) { opt_predefinedVendorConfig || property != 'iframePing', 'iframePing config is only available to vendor config.' ); - // Only deal with own properties. - if (hasOwn(from, property)) { + // Only deal with own properties, and never let a key reach the prototype + // chain (remote/inline config is attacker reachable). + if ( + hasOwn(from, property) && + property != '__proto__' && + property != 'constructor' && + property != 'prototype' + ) { if (isArray(from[property])) { if (!isArray(to[property])) { to[property] = []; diff --git a/extensions/amp-analytics/0.1/test/test-config.js b/extensions/amp-analytics/0.1/test/test-config.js index df7d45c17cb2..f1fe22b260c7 100644 --- a/extensions/amp-analytics/0.1/test/test-config.js +++ b/extensions/amp-analytics/0.1/test/test-config.js @@ -338,6 +338,20 @@ describes.realWin( 'foobar': {'foobar': ['abc', 'def']}, }); }); + + it('does not pollute the prototype via __proto__', () => { + const from = JSON.parse('{"__proto__": {"polluted": "yes"}}'); + mergeObjects(from, {}); + expect({}.polluted).to.be.undefined; + }); + + it('does not pollute the prototype via constructor', () => { + const from = JSON.parse( + '{"constructor": {"prototype": {"polluted": "yes"}}}' + ); + mergeObjects(from, {}); + expect({}.polluted).to.be.undefined; + }); }); describe('vendor only configs', () => {