diff --git a/extensions/amp-analytics/0.1/config.js b/extensions/amp-analytics/0.1/config.js index 36b2d81b4d0b..56657253ffb2 100644 --- a/extensions/amp-analytics/0.1/config.js +++ b/extensions/amp-analytics/0.1/config.js @@ -495,6 +495,16 @@ export function mergeObjects(from, to, opt_predefinedVendorConfig) { ); for (const property in from) { + // Never merge keys that can reach Object.prototype. A remote or inline + // config returning {"__proto__": {...}} would otherwise recurse into the + // prototype and pollute it for every object on the page. + if ( + property === '__proto__' || + property === 'constructor' || + property === 'prototype' + ) { + continue; + } userAssert( opt_predefinedVendorConfig || property != 'iframePing', 'iframePing config is only available to vendor config.' diff --git a/extensions/amp-analytics/0.1/test/test-config.js b/extensions/amp-analytics/0.1/test/test-config.js index df7d45c17cb2..c2576fd9001e 100644 --- a/extensions/amp-analytics/0.1/test/test-config.js +++ b/extensions/amp-analytics/0.1/test/test-config.js @@ -338,6 +338,13 @@ describes.realWin( 'foobar': {'foobar': ['abc', 'def']}, }); }); + + it('does not pollute Object.prototype via __proto__', function () { + // JSON.parse produces a real own "__proto__" key, matching a remote + // config response. + mergeObjects(JSON.parse('{"__proto__": {"polluted": "yes"}}'), {}); + expect({}.polluted).to.be.undefined; + }); }); describe('vendor only configs', () => {