Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/sim/app/api/tools/mysql/delete/route.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
Expand All @@ -17,7 +18,7 @@ const DeleteSchema = z.object({
})

export async function POST(request: NextRequest) {
const requestId = crypto.randomUUID().slice(0, 8)
const requestId = randomUUID().slice(0, 8)

try {
const body = await request.json()
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/api/tools/mysql/execute/route.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
Expand All @@ -16,7 +17,7 @@ const ExecuteSchema = z.object({
})

export async function POST(request: NextRequest) {
const requestId = crypto.randomUUID().slice(0, 8)
const requestId = randomUUID().slice(0, 8)

try {
const body = await request.json()
Expand All @@ -26,7 +27,6 @@ export async function POST(request: NextRequest) {
`[${requestId}] Executing raw SQL on ${params.host}:${params.port}/${params.database}`
)

// Validate query before execution
const validation = validateQuery(params.query)
if (!validation.isValid) {
logger.warn(`[${requestId}] Query validation failed: ${validation.error}`)
Expand Down
6 changes: 2 additions & 4 deletions apps/sim/app/api/tools/mysql/insert/route.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
Expand Down Expand Up @@ -38,13 +39,10 @@ const InsertSchema = z.object({
})

export async function POST(request: NextRequest) {
const requestId = crypto.randomUUID().slice(0, 8)
const requestId = randomUUID().slice(0, 8)

try {
const body = await request.json()

logger.info(`[${requestId}] Received data field type: ${typeof body.data}, value:`, body.data)

const params = InsertSchema.parse(body)

logger.info(
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/api/tools/mysql/query/route.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
Expand All @@ -16,7 +17,7 @@ const QuerySchema = z.object({
})

export async function POST(request: NextRequest) {
const requestId = crypto.randomUUID().slice(0, 8)
const requestId = randomUUID().slice(0, 8)

try {
const body = await request.json()
Expand All @@ -26,7 +27,6 @@ export async function POST(request: NextRequest) {
`[${requestId}] Executing MySQL query on ${params.host}:${params.port}/${params.database}`
)

// Validate query before execution
const validation = validateQuery(params.query)
if (!validation.isValid) {
logger.warn(`[${requestId}] Query validation failed: ${validation.error}`)
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/app/api/tools/mysql/update/route.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
Expand Down Expand Up @@ -36,7 +37,7 @@ const UpdateSchema = z.object({
})

export async function POST(request: NextRequest) {
const requestId = crypto.randomUUID().slice(0, 8)
const requestId = randomUUID().slice(0, 8)

try {
const body = await request.json()
Expand Down
30 changes: 22 additions & 8 deletions apps/sim/app/api/tools/mysql/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@ export async function createMySQLConnection(config: MySQLConnectionConfig) {
password: config.password,
}

// Handle SSL configuration
if (config.ssl === 'required') {
connectionConfig.ssl = { rejectUnauthorized: true }
} else if (config.ssl === 'preferred') {
connectionConfig.ssl = { rejectUnauthorized: false }
}
// For 'disabled', we don't set the ssl property at all

return mysql.createConnection(connectionConfig)
}
Expand Down Expand Up @@ -54,7 +52,6 @@ export async function executeQuery(
export function validateQuery(query: string): { isValid: boolean; error?: string } {
const trimmedQuery = query.trim().toLowerCase()

// Block dangerous SQL operations
const dangerousPatterns = [
/drop\s+database/i,
/drop\s+schema/i,
Expand Down Expand Up @@ -91,7 +88,6 @@ export function validateQuery(query: string): { isValid: boolean; error?: string
}
}

// Only allow specific statement types for execute endpoint
const allowedStatements = /^(select|insert|update|delete|with|show|describe|explain)\s+/i
if (!allowedStatements.test(trimmedQuery)) {
return {
Expand All @@ -116,6 +112,8 @@ export function buildInsertQuery(table: string, data: Record<string, unknown>) {
}

export function buildUpdateQuery(table: string, data: Record<string, unknown>, where: string) {
validateWhereClause(where)

const sanitizedTable = sanitizeIdentifier(table)
const columns = Object.keys(data)
const values = Object.values(data)
Expand All @@ -127,14 +125,33 @@ export function buildUpdateQuery(table: string, data: Record<string, unknown>, w
}

export function buildDeleteQuery(table: string, where: string) {
validateWhereClause(where)

const sanitizedTable = sanitizeIdentifier(table)
const query = `DELETE FROM ${sanitizedTable} WHERE ${where}`

return { query, values: [] }
}

function validateWhereClause(where: string): void {
const dangerousPatterns = [
/;\s*(drop|delete|insert|update|create|alter|grant|revoke)/i,
/union\s+select/i,
/into\s+outfile/i,
/load_file/i,
/--/,
/\/\*/,
/\*\//,
]

for (const pattern of dangerousPatterns) {
if (pattern.test(where)) {
throw new Error('WHERE clause contains potentially dangerous operation')
}
}
}

export function sanitizeIdentifier(identifier: string): string {
// Handle schema.table format
if (identifier.includes('.')) {
const parts = identifier.split('.')
return parts.map((part) => sanitizeSingleIdentifier(part)).join('.')
Expand All @@ -144,16 +161,13 @@ export function sanitizeIdentifier(identifier: string): string {
}

function sanitizeSingleIdentifier(identifier: string): string {
// Remove any existing backticks to prevent double-escaping
const cleaned = identifier.replace(/`/g, '')

// Validate identifier contains only safe characters
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(cleaned)) {
throw new Error(
`Invalid identifier: ${identifier}. Identifiers must start with a letter or underscore and contain only letters, numbers, and underscores.`
)
}

// Wrap in backticks for MySQL
return `\`${cleaned}\``
}
3 changes: 2 additions & 1 deletion apps/sim/app/api/tools/postgresql/delete/route.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
Expand All @@ -17,7 +18,7 @@ const DeleteSchema = z.object({
})

export async function POST(request: NextRequest) {
const requestId = crypto.randomUUID().slice(0, 8)
const requestId = randomUUID().slice(0, 8)

try {
const body = await request.json()
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/api/tools/postgresql/execute/route.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
Expand All @@ -20,7 +21,7 @@ const ExecuteSchema = z.object({
})

export async function POST(request: NextRequest) {
const requestId = crypto.randomUUID().slice(0, 8)
const requestId = randomUUID().slice(0, 8)

try {
const body = await request.json()
Expand All @@ -30,7 +31,6 @@ export async function POST(request: NextRequest) {
`[${requestId}] Executing raw SQL on ${params.host}:${params.port}/${params.database}`
)

// Validate query before execution
const validation = validateQuery(params.query)
if (!validation.isValid) {
logger.warn(`[${requestId}] Query validation failed: ${validation.error}`)
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/app/api/tools/postgresql/insert/route.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
Expand Down Expand Up @@ -38,7 +39,7 @@ const InsertSchema = z.object({
})

export async function POST(request: NextRequest) {
const requestId = crypto.randomUUID().slice(0, 8)
const requestId = randomUUID().slice(0, 8)

try {
const body = await request.json()
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/app/api/tools/postgresql/query/route.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
Expand All @@ -16,7 +17,7 @@ const QuerySchema = z.object({
})

export async function POST(request: NextRequest) {
const requestId = crypto.randomUUID().slice(0, 8)
const requestId = randomUUID().slice(0, 8)

try {
const body = await request.json()
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/app/api/tools/postgresql/update/route.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
Expand Down Expand Up @@ -36,7 +37,7 @@ const UpdateSchema = z.object({
})

export async function POST(request: NextRequest) {
const requestId = crypto.randomUUID().slice(0, 8)
const requestId = randomUUID().slice(0, 8)

try {
const body = await request.json()
Expand Down
27 changes: 22 additions & 5 deletions apps/sim/app/api/tools/postgresql/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ export function validateQuery(query: string): { isValid: boolean; error?: string
}
}

// Only allow specific statement types for execute endpoint
const allowedStatements = /^(select|insert|update|delete|with|explain|analyze|show)\s+/i
if (!allowedStatements.test(trimmedQuery)) {
return {
Expand All @@ -96,7 +95,6 @@ export function validateQuery(query: string): { isValid: boolean; error?: string
}

export function sanitizeIdentifier(identifier: string): string {
// Handle schema.table format
if (identifier.includes('.')) {
const parts = identifier.split('.')
return parts.map((part) => sanitizeSingleIdentifier(part)).join('.')
Expand All @@ -105,18 +103,33 @@ export function sanitizeIdentifier(identifier: string): string {
return sanitizeSingleIdentifier(identifier)
}

function validateWhereClause(where: string): void {
const dangerousPatterns = [
/;\s*(drop|delete|insert|update|create|alter|grant|revoke)/i,
/union\s+select/i,
/into\s+outfile/i,
/load_file/i,
/--/,
/\/\*/,
/\*\//,
]

for (const pattern of dangerousPatterns) {
if (pattern.test(where)) {
throw new Error('WHERE clause contains potentially dangerous operation')
}
}
}

function sanitizeSingleIdentifier(identifier: string): string {
// Remove any existing double quotes to prevent double-escaping
const cleaned = identifier.replace(/"/g, '')

// Validate identifier contains only safe characters
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(cleaned)) {
throw new Error(
`Invalid identifier: ${identifier}. Identifiers must start with a letter or underscore and contain only letters, numbers, and underscores.`
)
}

// Wrap in double quotes for PostgreSQL
return `"${cleaned}"`
}

Expand Down Expand Up @@ -146,6 +159,8 @@ export async function executeUpdate(
data: Record<string, unknown>,
where: string
): Promise<{ rows: unknown[]; rowCount: number }> {
validateWhereClause(where)

const sanitizedTable = sanitizeIdentifier(table)
const columns = Object.keys(data)
const sanitizedColumns = columns.map((col) => sanitizeIdentifier(col))
Expand All @@ -166,6 +181,8 @@ export async function executeDelete(
table: string,
where: string
): Promise<{ rows: unknown[]; rowCount: number }> {
validateWhereClause(where)

const sanitizedTable = sanitizeIdentifier(table)
const query = `DELETE FROM ${sanitizedTable} WHERE ${where} RETURNING *`
const result = await sql.unsafe(query, [])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ export function Code({

<div
className={cn(
'group relative min-h-[100px] rounded-md border-2 border-border bg-background font-mono text-sm transition-colors',
'group relative min-h-[100px] rounded-md border border-input bg-background font-mono text-sm transition-colors',
isConnecting && 'ring-2 ring-blue-500 ring-offset-2',
!isValidJson && 'border-destructive bg-destructive/10'
)}
Expand All @@ -394,7 +394,7 @@ export function Code({
onDrop={handleDrop}
>
<div className='absolute top-2 right-3 z-10 flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100'>
{!isCollapsed && !isAiStreaming && !isPreview && (
{wandConfig?.enabled && !isCollapsed && !isAiStreaming && !isPreview && (
<Button
variant='ghost'
size='icon'
Expand Down
Loading