Skip to content

Commit bf8fbeb

Browse files
improvement(code-quality): centralize regex checks, normalization (#2554)
* improvement(code-quality): centralize regex checks, normalization * simplify resolution * fix(copilot): don't allow duplicate name blocks * centralize uuid check
1 parent b23299d commit bf8fbeb

File tree

68 files changed

+425
-396
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+425
-396
lines changed

apps/sim/app/api/copilot/checkpoints/revert/route.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import {
1010
createRequestTracker,
1111
createUnauthorizedResponse,
1212
} from '@/lib/copilot/request-helpers'
13-
import { validateUUID } from '@/lib/core/security/input-validation'
1413
import { getBaseUrl } from '@/lib/core/utils/urls'
1514
import { createLogger } from '@/lib/logs/console/logger'
15+
import { isUuidV4 } from '@/executor/constants'
1616

1717
const logger = createLogger('CheckpointRevertAPI')
1818

@@ -87,9 +87,8 @@ export async function POST(request: NextRequest) {
8787
isDeployed: cleanedState.isDeployed,
8888
})
8989

90-
const workflowIdValidation = validateUUID(checkpoint.workflowId, 'workflowId')
91-
if (!workflowIdValidation.isValid) {
92-
logger.error(`[${tracker.requestId}] Invalid workflow ID: ${workflowIdValidation.error}`)
90+
if (!isUuidV4(checkpoint.workflowId)) {
91+
logger.error(`[${tracker.requestId}] Invalid workflow ID format`)
9392
return NextResponse.json({ error: 'Invalid workflow ID format' }, { status: 400 })
9493
}
9594

apps/sim/app/api/copilot/execute-tool/route.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { generateRequestId } from '@/lib/core/utils/request'
1414
import { getEffectiveDecryptedEnv } from '@/lib/environment/utils'
1515
import { createLogger } from '@/lib/logs/console/logger'
1616
import { refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils'
17+
import { REFERENCE } from '@/executor/constants'
18+
import { createEnvVarPattern } from '@/executor/utils/reference-validation'
1719
import { executeTool } from '@/tools'
1820
import { getTool } from '@/tools/utils'
1921

@@ -33,14 +35,18 @@ const ExecuteToolSchema = z.object({
3335
function resolveEnvVarReferences(value: any, envVars: Record<string, string>): any {
3436
if (typeof value === 'string') {
3537
// Check for exact match: entire string is "{{VAR_NAME}}"
36-
const exactMatch = /^\{\{([^}]+)\}\}$/.exec(value)
38+
const exactMatchPattern = new RegExp(
39+
`^\\${REFERENCE.ENV_VAR_START}([^}]+)\\${REFERENCE.ENV_VAR_END}$`
40+
)
41+
const exactMatch = exactMatchPattern.exec(value)
3742
if (exactMatch) {
3843
const envVarName = exactMatch[1].trim()
3944
return envVars[envVarName] ?? value
4045
}
4146

4247
// Check for embedded references: "prefix {{VAR}} suffix"
43-
return value.replace(/\{\{([^}]+)\}\}/g, (match, varName) => {
48+
const envVarPattern = createEnvVarPattern()
49+
return value.replace(envVarPattern, (match, varName) => {
4450
const trimmedName = varName.trim()
4551
return envVars[trimmedName] ?? match
4652
})

apps/sim/app/api/files/authorization.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type { StorageConfig } from '@/lib/uploads/core/storage-client'
1414
import { getFileMetadataByKey } from '@/lib/uploads/server/metadata'
1515
import { inferContextFromKey } from '@/lib/uploads/utils/file-utils'
1616
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
17+
import { isUuid } from '@/executor/constants'
1718

1819
const logger = createLogger('FileAuthorization')
1920

@@ -85,9 +86,7 @@ function extractWorkspaceIdFromKey(key: string): string | null {
8586
const parts = key.split('/')
8687
const workspaceId = parts[0]
8788

88-
// Validate UUID format
89-
const UUID_PATTERN = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i
90-
if (workspaceId && UUID_PATTERN.test(workspaceId)) {
89+
if (workspaceId && isUuid(workspaceId)) {
9190
return workspaceId
9291
}
9392

apps/sim/app/api/files/upload/route.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { type NextRequest, NextResponse } from 'next/server'
22
import { createLogger } from '@/lib/logs/console/logger'
3+
import { sanitizeFileName } from '@/executor/constants'
34
import '@/lib/uploads/core/setup.server'
45
import { getSession } from '@/lib/auth'
56
import type { StorageContext } from '@/lib/uploads/config'
@@ -154,7 +155,7 @@ export async function POST(request: NextRequest) {
154155
logger.info(`Uploading knowledge-base file: ${originalName}`)
155156

156157
const timestamp = Date.now()
157-
const safeFileName = originalName.replace(/\s+/g, '-')
158+
const safeFileName = sanitizeFileName(originalName)
158159
const storageKey = `kb/${timestamp}-${safeFileName}`
159160

160161
const metadata: Record<string, string> = {
@@ -267,9 +268,8 @@ export async function POST(request: NextRequest) {
267268

268269
logger.info(`Uploading ${context} file: ${originalName}`)
269270

270-
// Generate storage key with context prefix and timestamp to ensure uniqueness
271271
const timestamp = Date.now()
272-
const safeFileName = originalName.replace(/\s+/g, '-')
272+
const safeFileName = sanitizeFileName(originalName)
273273
const storageKey = `${context}/${timestamp}-${safeFileName}`
274274

275275
const metadata: Record<string, string> = {

apps/sim/app/api/function/execute/route.ts

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { executeInE2B } from '@/lib/execution/e2b'
55
import { executeInIsolatedVM } from '@/lib/execution/isolated-vm'
66
import { CodeLanguage, DEFAULT_CODE_LANGUAGE, isValidCodeLanguage } from '@/lib/execution/languages'
77
import { createLogger } from '@/lib/logs/console/logger'
8+
import { escapeRegExp, normalizeName, REFERENCE } from '@/executor/constants'
89
import {
910
createEnvVarPattern,
1011
createWorkflowVariablePattern,
@@ -405,7 +406,7 @@ function resolveWorkflowVariables(
405406

406407
// Find the variable by name (workflowVariables is indexed by ID, values are variable objects)
407408
const foundVariable = Object.entries(workflowVariables).find(
408-
([_, variable]) => (variable.name || '').replace(/\s+/g, '') === variableName
409+
([_, variable]) => normalizeName(variable.name || '') === variableName
409410
)
410411

411412
let variableValue: unknown = ''
@@ -513,31 +514,26 @@ function resolveTagVariables(
513514
): string {
514515
let resolvedCode = code
515516

516-
const tagMatches = resolvedCode.match(/<([a-zA-Z_][a-zA-Z0-9_.]*[a-zA-Z0-9_])>/g) || []
517+
const tagPattern = new RegExp(
518+
`${REFERENCE.START}([a-zA-Z_][a-zA-Z0-9_${REFERENCE.PATH_DELIMITER}]*[a-zA-Z0-9_])${REFERENCE.END}`,
519+
'g'
520+
)
521+
const tagMatches = resolvedCode.match(tagPattern) || []
517522

518523
for (const match of tagMatches) {
519-
const tagName = match.slice(1, -1).trim()
524+
const tagName = match.slice(REFERENCE.START.length, -REFERENCE.END.length).trim()
520525

521526
// Handle nested paths like "getrecord.response.data" or "function1.response.result"
522527
// First try params, then blockData directly, then try with block name mapping
523528
let tagValue = getNestedValue(params, tagName) || getNestedValue(blockData, tagName) || ''
524529

525530
// If not found and the path starts with a block name, try mapping the block name to ID
526-
if (!tagValue && tagName.includes('.')) {
527-
const pathParts = tagName.split('.')
531+
if (!tagValue && tagName.includes(REFERENCE.PATH_DELIMITER)) {
532+
const pathParts = tagName.split(REFERENCE.PATH_DELIMITER)
528533
const normalizedBlockName = pathParts[0] // This should already be normalized like "function1"
529534

530-
// Find the block ID by looking for a block name that normalizes to this value
531-
let blockId = null
532-
533-
for (const [blockName, id] of Object.entries(blockNameMapping)) {
534-
// Apply the same normalization logic as the UI: remove spaces and lowercase
535-
const normalizedName = blockName.replace(/\s+/g, '').toLowerCase()
536-
if (normalizedName === normalizedBlockName) {
537-
blockId = id
538-
break
539-
}
540-
}
535+
// Direct lookup using normalized block name
536+
const blockId = blockNameMapping[normalizedBlockName] ?? null
541537

542538
if (blockId) {
543539
const remainingPath = pathParts.slice(1).join('.')
@@ -617,13 +613,6 @@ function getNestedValue(obj: any, path: string): any {
617613
}, obj)
618614
}
619615

620-
/**
621-
* Escape special regex characters in a string
622-
*/
623-
function escapeRegExp(string: string): string {
624-
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
625-
}
626-
627616
/**
628617
* Remove one trailing newline from stdout
629618
* This handles the common case where print() or console.log() adds a trailing \n

apps/sim/app/api/mcp/servers/test-connection/route.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
66
import type { McpServerConfig, McpTransport } from '@/lib/mcp/types'
77
import { validateMcpServerUrl } from '@/lib/mcp/url-validator'
88
import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils'
9+
import { REFERENCE } from '@/executor/constants'
10+
import { createEnvVarPattern } from '@/executor/utils/reference-validation'
911

1012
const logger = createLogger('McpServerTestAPI')
1113

@@ -23,12 +25,13 @@ function isUrlBasedTransport(transport: McpTransport): boolean {
2325
* Resolve environment variables in strings
2426
*/
2527
function resolveEnvVars(value: string, envVars: Record<string, string>): string {
26-
const envMatches = value.match(/\{\{([^}]+)\}\}/g)
28+
const envVarPattern = createEnvVarPattern()
29+
const envMatches = value.match(envVarPattern)
2730
if (!envMatches) return value
2831

2932
let resolvedValue = value
3033
for (const match of envMatches) {
31-
const envKey = match.slice(2, -2).trim()
34+
const envKey = match.slice(REFERENCE.ENV_VAR_START.length, -REFERENCE.ENV_VAR_END.length).trim()
3235
const envValue = envVars[envKey]
3336

3437
if (envValue === undefined) {

apps/sim/app/api/tools/google_calendar/calendars/route.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { type NextRequest, NextResponse } from 'next/server'
22
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
3-
import { validateUUID } from '@/lib/core/security/input-validation'
43
import { generateRequestId } from '@/lib/core/utils/request'
54
import { createLogger } from '@/lib/logs/console/logger'
65
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
6+
import { isUuidV4 } from '@/executor/constants'
77
export const dynamic = 'force-dynamic'
88

99
const logger = createLogger('GoogleCalendarAPI')
@@ -35,18 +35,14 @@ export async function GET(request: NextRequest) {
3535
return NextResponse.json({ error: 'Credential ID is required' }, { status: 400 })
3636
}
3737

38-
const credentialValidation = validateUUID(credentialId, 'credentialId')
39-
if (!credentialValidation.isValid) {
38+
if (!isUuidV4(credentialId)) {
4039
logger.warn(`[${requestId}] Invalid credentialId format`, { credentialId })
41-
return NextResponse.json({ error: credentialValidation.error }, { status: 400 })
40+
return NextResponse.json({ error: 'Invalid credential ID format' }, { status: 400 })
4241
}
4342

44-
if (workflowId) {
45-
const workflowValidation = validateUUID(workflowId, 'workflowId')
46-
if (!workflowValidation.isValid) {
47-
logger.warn(`[${requestId}] Invalid workflowId format`, { workflowId })
48-
return NextResponse.json({ error: workflowValidation.error }, { status: 400 })
49-
}
43+
if (workflowId && !isUuidV4(workflowId)) {
44+
logger.warn(`[${requestId}] Invalid workflowId format`, { workflowId })
45+
return NextResponse.json({ error: 'Invalid workflow ID format' }, { status: 400 })
5046
}
5147
const authz = await authorizeCredentialUse(request, { credentialId, workflowId })
5248
if (!authz.ok || !authz.credentialOwnerUserId) {

apps/sim/app/api/workflows/[id]/execute/route.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
import { createStreamingResponse } from '@/lib/workflows/streaming/streaming'
2323
import { createHttpResponseFromBlock, workflowHasResponseBlock } from '@/lib/workflows/utils'
2424
import type { WorkflowExecutionPayload } from '@/background/workflow-execution'
25+
import { normalizeName } from '@/executor/constants'
2526
import { type ExecutionMetadata, ExecutionSnapshot } from '@/executor/execution/snapshot'
2627
import type { StreamingExecution } from '@/executor/types'
2728
import { Serializer } from '@/serializer'
@@ -86,10 +87,9 @@ function resolveOutputIds(
8687
const blockName = outputId.substring(0, dotIndex)
8788
const path = outputId.substring(dotIndex + 1)
8889

89-
const normalizedBlockName = blockName.toLowerCase().replace(/\s+/g, '')
90+
const normalizedBlockName = normalizeName(blockName)
9091
const block = Object.values(blocks).find((b: any) => {
91-
const normalized = (b.name || '').toLowerCase().replace(/\s+/g, '')
92-
return normalized === normalizedBlockName
92+
return normalizeName(b.name || '') === normalizedBlockName
9393
})
9494

9595
if (!block) {

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/deploy-modal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { getEnv } from '@/lib/core/config/env'
1818
import { createLogger } from '@/lib/logs/console/logger'
1919
import { getInputFormatExample as getInputFormatExampleUtil } from '@/lib/workflows/operations/deployment-utils'
2020
import type { WorkflowDeploymentVersionResponse } from '@/lib/workflows/persistence/utils'
21+
import { startsWithUuid } from '@/executor/constants'
2122
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
2223
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
2324
import type { WorkflowState } from '@/stores/workflows/workflow/types'
@@ -289,10 +290,9 @@ export function DeployModal({
289290
if (!open || selectedStreamingOutputs.length === 0) return
290291

291292
const blocks = Object.values(useWorkflowStore.getState().blocks)
292-
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i
293293

294294
const validOutputs = selectedStreamingOutputs.filter((outputId) => {
295-
if (UUID_REGEX.test(outputId)) {
295+
if (startsWithUuid(outputId)) {
296296
const underscoreIndex = outputId.indexOf('_')
297297
if (underscoreIndex === -1) return false
298298

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-subflow-editor.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import {
77
} from '@/lib/workflows/sanitization/references'
88
import { checkTagTrigger } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
99
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
10+
import { normalizeName, REFERENCE } from '@/executor/constants'
1011
import { createEnvVarPattern, createReferencePattern } from '@/executor/utils/reference-validation'
1112
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
12-
import { normalizeName } from '@/stores/workflows/utils'
1313
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
1414
import type { BlockState } from '@/stores/workflows/workflow/types'
1515

@@ -89,7 +89,7 @@ export function useSubflowEditor(currentBlock: BlockState | null, currentBlockId
8989
*/
9090
const shouldHighlightReference = useCallback(
9191
(part: string): boolean => {
92-
if (!part.startsWith('<') || !part.endsWith('>')) {
92+
if (!part.startsWith(REFERENCE.START) || !part.endsWith(REFERENCE.END)) {
9393
return false
9494
}
9595

@@ -108,8 +108,8 @@ export function useSubflowEditor(currentBlock: BlockState | null, currentBlockId
108108
return true
109109
}
110110

111-
const inner = reference.slice(1, -1)
112-
const [prefix] = inner.split('.')
111+
const inner = reference.slice(REFERENCE.START.length, -REFERENCE.END.length)
112+
const [prefix] = inner.split(REFERENCE.PATH_DELIMITER)
113113
const normalizedPrefix = normalizeName(prefix)
114114

115115
if (SYSTEM_REFERENCE_PREFIXES.has(normalizedPrefix)) {

0 commit comments

Comments
 (0)