Skip to content

Commit 5941faa

Browse files
committed
moved utils
1 parent eefbf53 commit 5941faa

File tree

9 files changed

+125
-125
lines changed

9 files changed

+125
-125
lines changed

apps/sim/background/webhook-execution.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { getWorkflowById } from '@/lib/workflows/utils'
1717
import { ExecutionSnapshot } from '@/executor/execution/snapshot'
1818
import type { ExecutionMetadata } from '@/executor/execution/types'
1919
import type { ExecutionResult } from '@/executor/types'
20-
import { safeAssign } from '@/tools/utils'
20+
import { safeAssign } from '@/tools/safe-assign'
2121
import { getTrigger, isTriggerValid } from '@/triggers'
2222

2323
const logger = createLogger('TriggerWebhookExecution')

apps/sim/executor/utils/start-block.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import type { InputFormatField } from '@/lib/workflows/types'
99
import type { NormalizedBlockOutput, UserFile } from '@/executor/types'
1010
import type { SerializedBlock } from '@/serializer/types'
11-
import { safeAssign } from '@/tools/utils'
11+
import { safeAssign } from '@/tools/safe-assign'
1212

1313
type ExecutionKind = 'chat' | 'manual' | 'api'
1414

apps/sim/tools/firecrawl/scrape.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { ScrapeParams, ScrapeResponse } from '@/tools/firecrawl/types'
2+
import { safeAssign } from '@/tools/safe-assign'
23
import type { ToolConfig } from '@/tools/types'
3-
import { safeAssign } from '@/tools/utils'
44

55
export const scrapeTool: ToolConfig<ScrapeParams, ScrapeResponse> = {
66
id: 'firecrawl_scrape',

apps/sim/tools/params.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import {
55
type SubBlockCondition,
66
} from '@/lib/workflows/subblocks/visibility'
77
import type { SubBlockConfig as BlockSubBlockConfig } from '@/blocks/types'
8+
import { safeAssign } from '@/tools/safe-assign'
89
import { isEmptyTagValue } from '@/tools/shared/tags'
910
import type { ParameterVisibility, ToolConfig } from '@/tools/types'
10-
import { getTool, safeAssign } from '@/tools/utils'
11+
import { getTool } from '@/tools/utils'
1112

1213
const logger = createLogger('ToolsParams')
1314
type ToolParamDefinition = ToolConfig['params'][string]

apps/sim/tools/safe-assign.test.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { isSafeKey, safeAssign } from '@/tools/safe-assign'
3+
4+
describe('isSafeKey', () => {
5+
it.concurrent('should return false for __proto__', () => {
6+
expect(isSafeKey('__proto__')).toBe(false)
7+
})
8+
9+
it.concurrent('should return false for constructor', () => {
10+
expect(isSafeKey('constructor')).toBe(false)
11+
})
12+
13+
it.concurrent('should return false for prototype', () => {
14+
expect(isSafeKey('prototype')).toBe(false)
15+
})
16+
17+
it.concurrent('should return true for normal keys', () => {
18+
expect(isSafeKey('name')).toBe(true)
19+
expect(isSafeKey('email')).toBe(true)
20+
expect(isSafeKey('customField')).toBe(true)
21+
expect(isSafeKey('data')).toBe(true)
22+
expect(isSafeKey('__internal')).toBe(true)
23+
})
24+
})
25+
26+
describe('safeAssign', () => {
27+
it.concurrent('should assign safe properties', () => {
28+
const target = { a: 1 }
29+
const source = { b: 2, c: 3 }
30+
const result = safeAssign(target, source)
31+
32+
expect(result).toEqual({ a: 1, b: 2, c: 3 })
33+
expect(result).toBe(target)
34+
})
35+
36+
it.concurrent('should filter out __proto__ key', () => {
37+
const target = { a: 1 }
38+
const source = { b: 2, __proto__: { polluted: true } } as Record<string, unknown>
39+
const result = safeAssign(target, source)
40+
41+
expect(result).toEqual({ a: 1, b: 2 })
42+
expect((result as any).__proto__).toBe(Object.prototype)
43+
expect((Object.prototype as any).polluted).toBeUndefined()
44+
})
45+
46+
it.concurrent('should filter out constructor key', () => {
47+
const target = { a: 1 }
48+
const source = { b: 2, constructor: { prototype: { polluted: true } } }
49+
const result = safeAssign(target, source)
50+
51+
expect(result).toEqual({ a: 1, b: 2 })
52+
expect((Object.prototype as any).polluted).toBeUndefined()
53+
})
54+
55+
it.concurrent('should filter out prototype key', () => {
56+
const target = { a: 1 }
57+
const source = { b: 2, prototype: { polluted: true } }
58+
const result = safeAssign(target, source)
59+
60+
expect(result).toEqual({ a: 1, b: 2 })
61+
expect((Object.prototype as any).polluted).toBeUndefined()
62+
})
63+
64+
it.concurrent('should handle null source', () => {
65+
const target = { a: 1 }
66+
const result = safeAssign(target, null as any)
67+
68+
expect(result).toEqual({ a: 1 })
69+
})
70+
71+
it.concurrent('should handle undefined source', () => {
72+
const target = { a: 1 }
73+
const result = safeAssign(target, undefined as any)
74+
75+
expect(result).toEqual({ a: 1 })
76+
})
77+
78+
it.concurrent('should handle non-object source', () => {
79+
const target = { a: 1 }
80+
const result = safeAssign(target, 'string' as any)
81+
82+
expect(result).toEqual({ a: 1 })
83+
})
84+
85+
it.concurrent('should prevent prototype pollution attack', () => {
86+
const maliciousPayload = JSON.parse('{"__proto__": {"isAdmin": true}, "normal": "value"}')
87+
const target = {}
88+
safeAssign(target, maliciousPayload)
89+
90+
const newObj = {}
91+
expect((newObj as any).isAdmin).toBeUndefined()
92+
expect((target as any).normal).toBe('value')
93+
})
94+
})

apps/sim/tools/safe-assign.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype'])
2+
3+
/**
4+
* Checks if a key is safe to use in object assignment (not a prototype pollution vector)
5+
*/
6+
export function isSafeKey(key: string): boolean {
7+
return !DANGEROUS_KEYS.has(key)
8+
}
9+
10+
/**
11+
* Safely assigns properties from source to target, filtering out prototype pollution keys.
12+
* Use this instead of Object.assign() when the source may contain user-controlled data.
13+
*/
14+
export function safeAssign<T extends object>(target: T, source: Record<string, unknown>): T {
15+
if (!source || typeof source !== 'object') {
16+
return target
17+
}
18+
19+
for (const key of Object.keys(source)) {
20+
if (isSafeKey(key)) {
21+
;(target as Record<string, unknown>)[key] = source[key]
22+
}
23+
}
24+
return target
25+
}

apps/sim/tools/sendgrid/add_contact.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
import { safeAssign } from '@/tools/safe-assign'
12
import type {
23
AddContactParams,
34
ContactResult,
45
SendGridContactObject,
56
SendGridContactRequest,
67
} from '@/tools/sendgrid/types'
78
import type { ToolConfig } from '@/tools/types'
8-
import { safeAssign } from '@/tools/utils'
99

1010
export const sendGridAddContactTool: ToolConfig<AddContactParams, ContactResult> = {
1111
id: 'sendgrid_add_contact',

apps/sim/tools/utils.test.ts

Lines changed: 0 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import {
88
executeRequest,
99
formatRequestParams,
1010
getClientEnvVars,
11-
isSafeKey,
12-
safeAssign,
1311
validateRequiredParametersAfterMerge,
1412
} from '@/tools/utils'
1513

@@ -41,98 +39,6 @@ afterEach(() => {
4139
vi.clearAllMocks()
4240
})
4341

44-
describe('isSafeKey', () => {
45-
it.concurrent('should return false for __proto__', () => {
46-
expect(isSafeKey('__proto__')).toBe(false)
47-
})
48-
49-
it.concurrent('should return false for constructor', () => {
50-
expect(isSafeKey('constructor')).toBe(false)
51-
})
52-
53-
it.concurrent('should return false for prototype', () => {
54-
expect(isSafeKey('prototype')).toBe(false)
55-
})
56-
57-
it.concurrent('should return true for normal keys', () => {
58-
expect(isSafeKey('name')).toBe(true)
59-
expect(isSafeKey('email')).toBe(true)
60-
expect(isSafeKey('customField')).toBe(true)
61-
expect(isSafeKey('data')).toBe(true)
62-
expect(isSafeKey('__internal')).toBe(true)
63-
})
64-
})
65-
66-
describe('safeAssign', () => {
67-
it.concurrent('should assign safe properties', () => {
68-
const target = { a: 1 }
69-
const source = { b: 2, c: 3 }
70-
const result = safeAssign(target, source)
71-
72-
expect(result).toEqual({ a: 1, b: 2, c: 3 })
73-
expect(result).toBe(target)
74-
})
75-
76-
it.concurrent('should filter out __proto__ key', () => {
77-
const target = { a: 1 }
78-
const source = { b: 2, __proto__: { polluted: true } } as Record<string, unknown>
79-
const result = safeAssign(target, source)
80-
81-
expect(result).toEqual({ a: 1, b: 2 })
82-
expect((result as any).__proto__).toBe(Object.prototype)
83-
expect((Object.prototype as any).polluted).toBeUndefined()
84-
})
85-
86-
it.concurrent('should filter out constructor key', () => {
87-
const target = { a: 1 }
88-
const source = { b: 2, constructor: { prototype: { polluted: true } } }
89-
const result = safeAssign(target, source)
90-
91-
expect(result).toEqual({ a: 1, b: 2 })
92-
expect((Object.prototype as any).polluted).toBeUndefined()
93-
})
94-
95-
it.concurrent('should filter out prototype key', () => {
96-
const target = { a: 1 }
97-
const source = { b: 2, prototype: { polluted: true } }
98-
const result = safeAssign(target, source)
99-
100-
expect(result).toEqual({ a: 1, b: 2 })
101-
expect((Object.prototype as any).polluted).toBeUndefined()
102-
})
103-
104-
it.concurrent('should handle null source', () => {
105-
const target = { a: 1 }
106-
const result = safeAssign(target, null as any)
107-
108-
expect(result).toEqual({ a: 1 })
109-
})
110-
111-
it.concurrent('should handle undefined source', () => {
112-
const target = { a: 1 }
113-
const result = safeAssign(target, undefined as any)
114-
115-
expect(result).toEqual({ a: 1 })
116-
})
117-
118-
it.concurrent('should handle non-object source', () => {
119-
const target = { a: 1 }
120-
const result = safeAssign(target, 'string' as any)
121-
122-
expect(result).toEqual({ a: 1 })
123-
})
124-
125-
it.concurrent('should prevent prototype pollution attack', () => {
126-
const maliciousPayload = JSON.parse('{"__proto__": {"isAdmin": true}, "normal": "value"}')
127-
const target = {}
128-
safeAssign(target, maliciousPayload)
129-
130-
const newObj = {}
131-
expect((newObj as any).isAdmin).toBeUndefined()
132-
expect((target as any).normal).toBe('value')
133-
})
134-
})
135-
13642
describe('transformTable', () => {
13743
it.concurrent('should return empty object for null input', () => {
13844
const result = transformTable(null)

apps/sim/tools/utils.ts

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,6 @@ import type { ToolConfig, ToolResponse } from '@/tools/types'
99

1010
const logger = createLogger('ToolsUtils')
1111

12-
const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype'])
13-
14-
/**
15-
* Checks if a key is safe to use in object assignment (not a prototype pollution vector)
16-
*/
17-
export function isSafeKey(key: string): boolean {
18-
return !DANGEROUS_KEYS.has(key)
19-
}
20-
21-
/**
22-
* Safely assigns properties from source to target, filtering out prototype pollution keys.
23-
* Use this instead of Object.assign() when the source may contain user-controlled data.
24-
*/
25-
export function safeAssign<T extends object>(target: T, source: Record<string, unknown>): T {
26-
if (!source || typeof source !== 'object') {
27-
return target
28-
}
29-
30-
for (const key of Object.keys(source)) {
31-
if (isSafeKey(key)) {
32-
;(target as Record<string, unknown>)[key] = source[key]
33-
}
34-
}
35-
return target
36-
}
37-
3812
/**
3913
* Strips version suffix (_v2, _v3, etc.) from a tool ID or name
4014
* @example stripVersionSuffix('notion_search_v2') => 'notion_search'

0 commit comments

Comments
 (0)