@@ -19,19 +19,45 @@ export interface State {
1919 existed : boolean ;
2020}
2121
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+
2240export function defaultState ( ) : State {
2341 return {
24- serverData : { } ,
25- pendingOps : [ { } ] ,
26- objectCache : { } ,
42+ serverData : Object . create ( null ) ,
43+ pendingOps : [ Object . create ( null ) ] ,
44+ objectCache : Object . create ( null ) ,
2745 tasks : new TaskQueue ( ) ,
2846 existed : false ,
2947 } ;
3048}
3149
3250export function setServerData ( serverData : AttributeMap , attributes : AttributeMap ) {
3351 for ( const attr in attributes ) {
34- if ( typeof attributes [ attr ] !== 'undefined' ) {
52+ // Skip properties from prototype chain
53+ if ( ! Object . prototype . hasOwnProperty . call ( attributes , attr ) ) {
54+ continue ;
55+ }
56+ // Skip dangerous keys that could pollute prototypes
57+ if ( isDangerousKey ( attr ) ) {
58+ continue ;
59+ }
60+ if ( typeof attributes [ attr ] !== "undefined" ) {
3561 serverData [ attr ] = attributes [ attr ] ;
3662 } else {
3763 delete serverData [ attr ] ;
@@ -40,6 +66,10 @@ export function setServerData(serverData: AttributeMap, attributes: AttributeMap
4066}
4167
4268export function setPendingOp ( pendingOps : OpsMap [ ] , attr : string , op ?: Op ) {
69+ // Skip dangerous keys that could pollute prototypes
70+ if ( isDangerousKey ( attr ) ) {
71+ return ;
72+ }
4373 const last = pendingOps . length - 1 ;
4474 if ( op ) {
4575 pendingOps [ last ] [ attr ] = op ;
@@ -49,13 +79,13 @@ export function setPendingOp(pendingOps: OpsMap[], attr: string, op?: Op) {
4979}
5080
5181export function pushPendingState ( pendingOps : OpsMap [ ] ) {
52- pendingOps . push ( { } ) ;
82+ pendingOps . push ( Object . create ( null ) ) ;
5383}
5484
5585export function popPendingState ( pendingOps : OpsMap [ ] ) : OpsMap {
5686 const first = pendingOps . shift ( ) ;
5787 if ( ! pendingOps . length ) {
58- pendingOps [ 0 ] = { } ;
88+ pendingOps [ 0 ] = Object . create ( null ) ;
5989 }
6090 return first ;
6191}
@@ -64,6 +94,14 @@ export function mergeFirstPendingState(pendingOps: OpsMap[]) {
6494 const first = popPendingState ( pendingOps ) ;
6595 const next = pendingOps [ 0 ] ;
6696 for ( const attr in first ) {
97+ // Skip properties from prototype chain
98+ if ( ! Object . prototype . hasOwnProperty . call ( first , attr ) ) {
99+ continue ;
100+ }
101+ // Skip dangerous keys that could pollute prototypes
102+ if ( isDangerousKey ( attr ) ) {
103+ continue ;
104+ }
67105 if ( next [ attr ] && first [ attr ] ) {
68106 const merged = next [ attr ] . mergeWith ( first [ attr ] ) ;
69107 if ( merged ) {
@@ -81,6 +119,10 @@ export function estimateAttribute(
81119 object : ParseObject ,
82120 attr : string
83121) : any {
122+ // Skip dangerous keys that could pollute prototypes
123+ if ( isDangerousKey ( attr ) ) {
124+ return undefined ;
125+ }
84126 let value = serverData [ attr ] ;
85127 for ( let i = 0 ; i < pendingOps . length ; i ++ ) {
86128 if ( pendingOps [ i ] [ attr ] ) {
@@ -101,13 +143,21 @@ export function estimateAttributes(
101143 pendingOps : OpsMap [ ] ,
102144 object : ParseObject
103145) : AttributeMap {
104- const data = { } ;
146+ const data = Object . create ( null ) ;
105147 let attr ;
106148 for ( attr in serverData ) {
107149 data [ attr ] = serverData [ attr ] ;
108150 }
109151 for ( let i = 0 ; i < pendingOps . length ; i ++ ) {
110152 for ( attr in pendingOps [ i ] ) {
153+ // Skip properties from prototype chain
154+ if ( ! Object . prototype . hasOwnProperty . call ( pendingOps [ i ] , attr ) ) {
155+ continue ;
156+ }
157+ // Skip dangerous keys that could pollute prototypes
158+ if ( isDangerousKey ( attr ) ) {
159+ continue ;
160+ }
111161 if ( pendingOps [ i ] [ attr ] instanceof RelationOp ) {
112162 if ( object . id ) {
113163 data [ attr ] = ( pendingOps [ i ] [ attr ] as RelationOp ) . applyTo ( data [ attr ] , object , attr ) ;
@@ -125,7 +175,7 @@ export function estimateAttributes(
125175 if ( ! isNaN ( nextKey ) ) {
126176 object [ key ] = [ ] ;
127177 } else {
128- object [ key ] = { } ;
178+ object [ key ] = Object . create ( null ) ;
129179 }
130180 } else {
131181 if ( Array . isArray ( object [ key ] ) ) {
@@ -165,7 +215,7 @@ function nestedSet(obj, key, value) {
165215 if ( ! isNaN ( nextPath ) ) {
166216 obj [ path ] = [ ] ;
167217 } else {
168- obj [ path ] = { } ;
218+ obj [ path ] = Object . create ( null ) ;
169219 }
170220 }
171221 obj = obj [ path ] ;
@@ -184,6 +234,14 @@ export function commitServerChanges(
184234) {
185235 const ParseObject = CoreManager . getParseObject ( ) ;
186236 for ( const attr in changes ) {
237+ // Skip properties from prototype chain
238+ if ( ! Object . prototype . hasOwnProperty . call ( changes , attr ) ) {
239+ continue ;
240+ }
241+ // Skip dangerous keys that could pollute prototypes
242+ if ( isDangerousKey ( attr ) ) {
243+ continue ;
244+ }
187245 const val = changes [ attr ] ;
188246 nestedSet ( serverData , attr , val ) ;
189247 if (
0 commit comments