CVE-2020-7765 (TypeScript, published 2020-11-16T12:15Z)
Description: value: This affects the package @firebase/util before 0.3.4. This vulnerability relates to the deepExtend function within the DeepCopy.ts file. Depending on if user input is provided, an attacker can overwrite and pollute the object prototype of a program.
Severity: MEDIUM (CVSS v3 base score: 5.3)
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/firebase/firebase-js-sdk
Files changed:
.changeset/fuzzy-impalas-brake.md
packages/util/src/deepCopy.ts
packages/util/test/deepCopy.test.ts
Commit message: Prevent __proto__ pollution in util.deepExtend (#4001)

* do not extend __proto__

* replace asserts with expects

* Create fuzzy-impalas-brake.md

* address comment

Vulnerable code:
    if (!source.hasOwnProperty(prop)) {
    (target as { [key: string]: unknown })[prop] = deepExtend(
      (target as { [key: string]: unknown })[prop],
      (source as { [key: string]: unknown })[prop]
import { assert } from 'chai';
    assert.strictEqual(deepCopy(true), true);
    assert.strictEqual(deepCopy(123), 123);
    assert.strictEqual(deepCopy('abc'), 'abc');
    assert.deepEqual(deepCopy(d), d);
    assert.deepEqual(deepCopy({}), {});
    assert.deepEqual(deepCopy({ a: 123 }), { a: 123 });
    assert.deepEqual(deepCopy({ a: { b: 123 } }), { a: { b: 123 } });
    assert.deepEqual(deepCopy([]), []);
    assert.deepEqual(deepCopy([123, 456]), [123, 456]);
    assert.deepEqual(deepCopy([123, [456]]), [123, [456]]);
    assert.strictEqual(deepExtend(1, true), true);
    assert.strictEqual(deepExtend(undefined, 123), 123);
    assert.strictEqual(deepExtend('was', 'abc'), 'abc');
    assert.deepEqual(deepExtend(new Date(), d), d);
    assert.deepEqual(deepExtend({ old: 123 }, {}), { old: 123 });
    assert.deepEqual(deepExtend({ old: 123 }, { s: 'hello' }), {
    assert.deepEqual(
      deepExtend({ old: 123, a: { c: 'in-old' } }, { a: { b: 123 } }),
      { old: 123, a: { b: 123, c: 'in-old' } }
    );
    assert.deepEqual(deepExtend([1], []), []);
    assert.deepEqual(deepExtend([1], [123, 456]), [123, 456]);
    assert.deepEqual(deepExtend([1], [123, [456]]), [123, [456]]);
    assert.deepEqual(deepExtend(o1, o2), { a: [2] });
    assert.deepEqual(o1, { a: [2] });
    assert.deepEqual(b, [, , , '3']);
    assert.deepEqual({ a: source }, target);
    assert.strictEqual(source, target.a);

Fixed code:
---
"@firebase/util": patch
---

Do not merge `__proto__` in `deepExtend` to prevent `__proto__` pollution.
 *
 * Note: we don't merge __proto__ to prevent prototype pollution
    // use isValidKey to guard against prototype pollution. See https://snyk.io/vuln/SNYK-JS-LODASH-450202
    if (!source.hasOwnProperty(prop) || !isValidKey(prop)) {
    (target as Record<string, unknown>)[prop] = deepExtend(
      (target as Record<string, unknown>)[prop],
      (source as Record<string, unknown>)[prop]

function isValidKey(key: string): boolean {
  return key !== '__proto__';
}
import { expect } from 'chai';
    expect(deepCopy(true)).to.equal(true);
    expect(deepCopy(123)).to.equal(123);
    expect(deepCopy('abc')).to.equal('abc');
    expect(deepCopy(d)).to.deep.equal(d);
    expect(deepCopy({})).to.deep.equal({});
    expect(deepCopy({ a: 123 })).to.deep.equal({ a: 123 });
    expect(deepCopy({ a: { b: 123 } })).to.deep.equal({ a: { b: 123 } });
    expect(deepCopy([])).to.deep.equal([]);
    expect(deepCopy([123, 456])).to.deep.equal([123, 456]);
    expect(deepCopy([123, [456]])).to.deep.equal([123, [456]]);
    expect(deepExtend(1, true)).to.equal(true);
    expect(deepExtend(undefined, 123)).to.equal(123);
    expect(deepExtend('was', 'abc')).to.equal('abc');
    expect(deepExtend(new Date(), d)).to.deep.equal(d);
    expect(deepExtend({ old: 123 }, {})).to.deep.equal({ old: 123 });
    expect(deepExtend({ old: 123 }, { s: 'hello' })).to.deep.equal({
    expect(
      deepExtend({ old: 123, a: { c: 'in-old' } }, { a: { b: 123 } })
    ).to.deep.equal({ old: 123, a: { b: 123, c: 'in-old' } });
    expect(deepExtend([1], [])).to.deep.equal([]);
    expect(deepExtend([1], [123, 456])).to.deep.equal([123, 456]);
    expect(deepExtend([1], [123, [456]])).to.deep.equal([123, [456]]);
    expect(deepExtend(o1, o2)).to.deep.equal({ a: [2] });
    expect(o1).to.deep.equal({ a: [2] });
    expect(b).to.deep.equal([, , , '3']);
    expect({ a: source }).to.deep.equal(target);
    expect(source).to.equal(target.a);
  });

  it('does not extend property __proto__', () => {
    const src = JSON.parse('{ "__proto__": { "polluted": "polluted" } }');
    const a: Record<string, unknown> = {};
    deepExtend(a, src);

    expect(a.__proto__).to.equal(Object.prototype);
    expect(a.polluted).to.be.undefined;