Skip to content

Commit 04c06f8

Browse files
committed
Disable prototype pollution on returned diff object
1 parent e37b759 commit 04c06f8

File tree

6 files changed

+66
-80
lines changed

6 files changed

+66
-80
lines changed

src/added.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isEmpty, isObject, hasOwnProperty } from './utils.js';
1+
import { isEmpty, isObject, hasOwnProperty, makeObjectWithoutPrototype } from './utils.js';
22

33
const addedDiff = (lhs, rhs) => {
44

@@ -19,7 +19,7 @@ const addedDiff = (lhs, rhs) => {
1919

2020
acc[key] = r[key];
2121
return acc;
22-
}, {});
22+
}, makeObjectWithoutPrototype());
2323
};
2424

2525
export default addedDiff;

src/deleted.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isEmpty, isObject, hasOwnProperty } from './utils.js';
1+
import { isEmpty, isObject, hasOwnProperty, makeObjectWithoutPrototype } from './utils.js';
22

33
const deletedDiff = (lhs, rhs) => {
44
if (lhs === rhs || !isObject(lhs) || !isObject(rhs)) return {};
@@ -18,7 +18,7 @@ const deletedDiff = (lhs, rhs) => {
1818

1919
acc[key] = undefined;
2020
return acc;
21-
}, {});
21+
}, makeObjectWithoutPrototype());
2222
};
2323

2424
export default deletedDiff;

src/diff.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isDate, isEmptyObject, isObject, hasOwnProperty } from './utils.js';
1+
import { isDate, isEmptyObject, isObject, hasOwnProperty, makeObjectWithoutPrototype } from './utils.js';
22

33
const diff = (lhs, rhs) => {
44
if (lhs === rhs) return {}; // equal return no diff
@@ -15,7 +15,7 @@ const diff = (lhs, rhs) => {
1515
}
1616

1717
return acc;
18-
}, {});
18+
}, makeObjectWithoutPrototype());
1919

2020
if (isDate(l) || isDate(r)) {
2121
if (l.valueOf() == r.valueOf()) return {};

src/updated.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isDate, isEmptyObject, isObject, hasOwnProperty } from './utils.js';
1+
import { isDate, isEmptyObject, isObject, hasOwnProperty, makeObjectWithoutPrototype } from './utils.js';
22

33
const updatedDiff = (lhs, rhs) => {
44
if (lhs === rhs) return {};
@@ -26,7 +26,7 @@ const updatedDiff = (lhs, rhs) => {
2626
}
2727

2828
return acc;
29-
}, {});
29+
}, makeObjectWithoutPrototype());
3030
};
3131

3232
export default updatedDiff;

src/utils.js

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export const isEmpty = o => Object.keys(o).length === 0;
33
export const isObject = o => o != null && typeof o === 'object';
44
export const hasOwnProperty = (o, ...args) => Object.prototype.hasOwnProperty.call(o, ...args)
55
export const isEmptyObject = (o) => isObject(o) && isEmpty(o);
6+
export const makeObjectWithoutPrototype = () => Object.create(null);

test/pollution.test.js

+57-72
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,70 @@
11
import addedDiff from "../src/added";
2+
import updatedDiff from "../src/updated";
3+
import diff from "../src/diff";
4+
import deletedDiff from "../src/deleted";
25

36
describe("Prototype pollution", () => {
4-
test("Demonstrate prototype pollution globally across all objects", () => {
5-
const a = {};
6-
const b = new Object();
7-
8-
expect(a.hello).toBeUndefined();
9-
expect(b.hello).toBeUndefined();
10-
expect({}.hello).toBeUndefined();
11-
12-
b.__proto__.hello = "world";
13-
14-
expect(a.hello).toBe("world");
15-
expect(b.hello).toBe("world");
16-
expect({}.hello).toBe("world");
7+
describe("diff", () => {
8+
test("should not pollute returned diffs prototype", () => {
9+
const l = { role: "user" };
10+
const r = JSON.parse('{ "role": "user", "__proto__": { "role": "admin" } }');
11+
const difference = diff(l, r);
12+
13+
expect(l.role).toBe("user");
14+
expect(r.role).toBe("user");
15+
expect(difference.role).toBeUndefined();
16+
});
17+
18+
test("should not pollute returned diffs prototype on nested diffs", () => {
19+
const l = { about: { role: "user" } };
20+
const r = JSON.parse('{ "about": { "__proto__": { "role": "admin" } } }');
21+
const difference = addedDiff(l, r);
22+
23+
expect(l.about.role).toBe("user");
24+
expect(r.about.role).toBeUndefined();
25+
expect(difference.about.role).toBeUndefined();
26+
});
1727
});
1828

19-
test("addedDiff does not pollute global prototype when running diff with added `__proto__` key", () => {
20-
const a = { role: "user" };
21-
const b = JSON.parse('{ "__proto__": { "role": "admin" } }');
22-
23-
expect(a.role).toBe("user");
24-
expect(a.__proto__.role).toBeUndefined();
25-
expect(b.role).toBeUndefined();
26-
expect(b.__proto__.role).toBe("admin");
27-
expect({}.role).toBeUndefined();
28-
expect({}.__proto__role).toBeUndefined();
29-
30-
const difference = addedDiff(a, b);
31-
32-
expect(a.role).toBe("user");
33-
expect(a.__proto__.role).toBeUndefined();
34-
expect(b.__proto__.role).toBe("admin");
35-
expect(b.role).toBeUndefined();
36-
expect({}.role).toBeUndefined();
37-
expect({}.__proto__role).toBeUndefined();
38-
39-
expect(difference).toEqual({ __proto__: { role: "admin" } });
29+
describe("addedDiff", () => {
30+
test("addedDiff should not pollute returned diffs prototype", () => {
31+
const l = { role: "user" };
32+
const r = JSON.parse('{ "__proto__": { "role": "admin" } }');
33+
const difference = addedDiff(l, r);
34+
35+
expect(l.role).toBe("user");
36+
expect(r.role).toBeUndefined();
37+
expect(difference.role).toBeUndefined();
38+
});
39+
40+
test("should not pollute returned diffs prototype on nested diffs", () => {
41+
const l = { about: { role: "user" } };
42+
const r = JSON.parse('{ "about": { "__proto__": { "role": "admin" } } }');
43+
const difference = addedDiff(l, r);
44+
45+
expect(l.about.role).toBe("user");
46+
expect(r.about.role).toBeUndefined();
47+
expect(difference.about.role).toBeUndefined();
48+
});
4049
});
4150

42-
test("addedDiff does not pollute global prototype when running diff with added `__proto__` key generated from JSON.parse and mutating original left hand object", () => {
43-
let a = { role: "user" };
44-
// Note: Don't trust `JSON.parse`!!!
45-
const b = JSON.parse('{ "__proto__": { "role": "admin" } }');
46-
47-
expect(a.role).toBe("user");
48-
expect(a.__proto__.role).toBeUndefined();
49-
expect(b.role).toBeUndefined();
50-
expect(b.__proto__.role).toBe("admin");
51-
expect({}.role).toBeUndefined();
52-
expect({}.__proto__role).toBeUndefined();
53-
54-
// Note: although this does not pollute the global proto, it does pollute the original object. (Don't mutate kids!)
55-
a = addedDiff(a, b);
51+
test("updatedDiff should not pollute returned diffs prototype", () => {
52+
const l = { role: "user" };
53+
const r = JSON.parse('{ "role": "user", "__proto__": { "role": "admin" } }');
54+
const difference = updatedDiff(l, r);
5655

57-
expect(a.role).toBe("admin");
58-
expect(a.__proto__.role).toBe("admin");
59-
expect(b.__proto__.role).toBe("admin");
60-
expect(b.role).toBeUndefined();
61-
expect({}.role).toBeUndefined();
62-
expect({}.__proto__role).toBeUndefined();
56+
expect(l.role).toBe("user");
57+
expect(r.role).toBe("user");
58+
expect(difference.role).toBeUndefined();
6359
});
6460

65-
test("addedDiff does not pollute global prototype or original object when running diff with added `__proto__` key", () => {
66-
let a = { role: "user" };
67-
const b = { __proto__: { role: "admin" } };
68-
69-
expect(a.role).toBe("user");
70-
expect(a.__proto__.role).toBeUndefined();
71-
expect(b.role).toBe("admin");
72-
expect(b.__proto__.role).toBe("admin");
73-
expect({}.role).toBeUndefined();
74-
expect({}.__proto__role).toBeUndefined();
75-
76-
a = addedDiff(a, b);
61+
test("deletedDiff should not pollute returned diffs prototype", () => {
62+
const l = { role: "user" };
63+
const r = JSON.parse('{ "__proto__": { "role": "admin" } }');
64+
const difference = deletedDiff(l, r);
7765

78-
expect(a.role).toBeUndefined();
79-
expect(a.__proto__.role).toBeUndefined();
80-
expect(b.role).toBe("admin");
81-
expect(b.__proto__.role).toBe("admin");
82-
expect({}.role).toBeUndefined();
83-
expect({}.__proto__role).toBeUndefined();
66+
expect(l.role).toBe("user");
67+
expect(r.role).toBeUndefined();
68+
expect(difference.role).toBeUndefined();
8469
});
8570
});

0 commit comments

Comments
 (0)