Skip to content

Commit cba326d

Browse files
committed
feat: add is dangerous key to encode/decode
1 parent b7e8bc3 commit cba326d

File tree

7 files changed

+41
-20
lines changed

7 files changed

+41
-20
lines changed

src/ObjectStateMutations.ts

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import TaskQueue from './TaskQueue';
66
import { RelationOp } from './ParseOp';
77
import type { Op } from './ParseOp';
88
import type ParseObject from './ParseObject';
9+
import { isDangerousKey } from "./isDangerousKey";
910

1011
export type AttributeMap = Record<string, any>;
1112
export type OpsMap = Record<string, Op>;
@@ -19,24 +20,6 @@ export interface State {
1920
existed: boolean;
2021
}
2122

22-
/**
23-
* Check if a property name or path is potentially dangerous for prototype pollution
24-
* @param key
25-
*/
26-
function isDangerousKey(key: string): boolean {
27-
const dangerousKeys = ["__proto__", "constructor", "prototype"];
28-
// Check if the key itself is dangerous
29-
if (dangerousKeys.includes(key)) {
30-
return true;
31-
}
32-
// Check if any part of a dotted path is dangerous
33-
if (key.includes(".")) {
34-
const parts = key.split(".");
35-
return parts.some((part) => dangerousKeys.includes(part));
36-
}
37-
return false;
38-
}
39-
4023
export function defaultState(): State {
4124
return {
4225
serverData: Object.create(null),

src/__tests__/ObjectStateMutations-test.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
jest.dontMock('../decode');
22
jest.dontMock('../encode');
33
jest.dontMock('../CoreManager');
4+
jest.dontMock('../isDangerousKey');
45
jest.dontMock('../ObjectStateMutations');
56
jest.dontMock('../ParseFile');
67
jest.dontMock('../ParseGeoPoint');
@@ -11,7 +12,7 @@ jest.dontMock('../TaskQueue');
1112
const mockObject = function (className) {
1213
this.className = className;
1314
};
14-
mockObject.registerSubclass = function () {};
15+
mockObject.registerSubclass = function () { };
1516
jest.setMock('../ParseObject', mockObject);
1617
const CoreManager = require('../CoreManager').default;
1718
CoreManager.setParseObject(mockObject);

src/__tests__/decode-test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
jest.dontMock('../decode');
22
jest.dontMock('../CoreManager');
3+
jest.dontMock('../isDangerousKey');
34
jest.dontMock('../ParseFile');
45
jest.dontMock('../ParseGeoPoint');
56
jest.dontMock('../ParseObject');

src/__tests__/encode-test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
jest.dontMock('../encode');
2+
jest.dontMock('../isDangerousKey');
23
jest.dontMock('../ParseACL');
34
jest.dontMock('../ParseFile');
45
jest.dontMock('../ParseGeoPoint');

src/decode.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ParseFile from './ParseFile';
33
import ParseGeoPoint from './ParseGeoPoint';
44
import ParsePolygon from './ParsePolygon';
55
import ParseRelation from './ParseRelation';
6+
import { isDangerousKey } from "./isDangerousKey";
67

78
export default function decode(value: any): any {
89
if (value === null || typeof value !== 'object' || value instanceof Date) {
@@ -50,6 +51,10 @@ export default function decode(value: any): any {
5051
const copy = {};
5152
for (const k in value) {
5253
if (Object.prototype.hasOwnProperty.call(value, k)) {
54+
// Skip dangerous keys that could pollute prototypes
55+
if (isDangerousKey(k)) {
56+
continue;
57+
}
5358
copy[k] = decode(value[k]);
5459
}
5560
}

src/encode.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import ParseFile from './ParseFile';
44
import ParseGeoPoint from './ParseGeoPoint';
55
import ParsePolygon from './ParsePolygon';
66
import ParseRelation from './ParseRelation';
7+
import { isDangerousKey } from "./isDangerousKey";
78

89
function encode(
910
value: any,
@@ -73,7 +74,17 @@ function encode(
7374
for (const k in value) {
7475
// Only iterate over own properties
7576
if (Object.prototype.hasOwnProperty.call(value, k)) {
76-
output[k] = encode(value[k], disallowObjects, forcePointers, seen, offline);
77+
// Skip dangerous keys that could pollute prototypes
78+
if (isDangerousKey(k)) {
79+
continue;
80+
}
81+
output[k] = encode(
82+
value[k],
83+
disallowObjects,
84+
forcePointers,
85+
seen,
86+
offline
87+
);
7788
}
7889
}
7990
return output;

src/isDangerousKey.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Check if a property name or path is potentially dangerous for prototype pollution
3+
* Dangerous keys include: __proto__, constructor, prototype
4+
* @param key - The property name or dotted path to check
5+
* @returns true if the key is dangerous, false otherwise
6+
*/
7+
export function isDangerousKey(key: string): boolean {
8+
const dangerousKeys = ["__proto__", "constructor", "prototype"];
9+
// Check if the key itself is dangerous
10+
if (dangerousKeys.includes(key)) {
11+
return true;
12+
}
13+
// Check if any part of a dotted path is dangerous
14+
if (key.includes(".")) {
15+
const parts = key.split(".");
16+
return parts.some((part) => dangerousKeys.includes(part));
17+
}
18+
return false;
19+
}

0 commit comments

Comments
 (0)