Skip to content

Commit 4310dd6

Browse files
authored
imporvement(pg): added wand config for writing sql queries for generic db blocks & supabase postgrest syntax (#1197)
* add parallel ai, postgres, mysql, slight modifications to dark mode styling * bun install frozen lockfile * new deps * improve security, add wand to short input and update wand config
1 parent 813a0fb commit 4310dd6

File tree

23 files changed

+756
-128
lines changed

23 files changed

+756
-128
lines changed

apps/sim/app/api/tools/mysql/delete/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { randomUUID } from 'crypto'
12
import { type NextRequest, NextResponse } from 'next/server'
23
import { z } from 'zod'
34
import { createLogger } from '@/lib/logs/console/logger'
@@ -17,7 +18,7 @@ const DeleteSchema = z.object({
1718
})
1819

1920
export async function POST(request: NextRequest) {
20-
const requestId = crypto.randomUUID().slice(0, 8)
21+
const requestId = randomUUID().slice(0, 8)
2122

2223
try {
2324
const body = await request.json()

apps/sim/app/api/tools/mysql/execute/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { randomUUID } from 'crypto'
12
import { type NextRequest, NextResponse } from 'next/server'
23
import { z } from 'zod'
34
import { createLogger } from '@/lib/logs/console/logger'
@@ -16,7 +17,7 @@ const ExecuteSchema = z.object({
1617
})
1718

1819
export async function POST(request: NextRequest) {
19-
const requestId = crypto.randomUUID().slice(0, 8)
20+
const requestId = randomUUID().slice(0, 8)
2021

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

29-
// Validate query before execution
3030
const validation = validateQuery(params.query)
3131
if (!validation.isValid) {
3232
logger.warn(`[${requestId}] Query validation failed: ${validation.error}`)

apps/sim/app/api/tools/mysql/insert/route.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { randomUUID } from 'crypto'
12
import { type NextRequest, NextResponse } from 'next/server'
23
import { z } from 'zod'
34
import { createLogger } from '@/lib/logs/console/logger'
@@ -38,13 +39,10 @@ const InsertSchema = z.object({
3839
})
3940

4041
export async function POST(request: NextRequest) {
41-
const requestId = crypto.randomUUID().slice(0, 8)
42+
const requestId = randomUUID().slice(0, 8)
4243

4344
try {
4445
const body = await request.json()
45-
46-
logger.info(`[${requestId}] Received data field type: ${typeof body.data}, value:`, body.data)
47-
4846
const params = InsertSchema.parse(body)
4947

5048
logger.info(

apps/sim/app/api/tools/mysql/query/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { randomUUID } from 'crypto'
12
import { type NextRequest, NextResponse } from 'next/server'
23
import { z } from 'zod'
34
import { createLogger } from '@/lib/logs/console/logger'
@@ -16,7 +17,7 @@ const QuerySchema = z.object({
1617
})
1718

1819
export async function POST(request: NextRequest) {
19-
const requestId = crypto.randomUUID().slice(0, 8)
20+
const requestId = randomUUID().slice(0, 8)
2021

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

29-
// Validate query before execution
3030
const validation = validateQuery(params.query)
3131
if (!validation.isValid) {
3232
logger.warn(`[${requestId}] Query validation failed: ${validation.error}`)

apps/sim/app/api/tools/mysql/update/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { randomUUID } from 'crypto'
12
import { type NextRequest, NextResponse } from 'next/server'
23
import { z } from 'zod'
34
import { createLogger } from '@/lib/logs/console/logger'
@@ -36,7 +37,7 @@ const UpdateSchema = z.object({
3637
})
3738

3839
export async function POST(request: NextRequest) {
39-
const requestId = crypto.randomUUID().slice(0, 8)
40+
const requestId = randomUUID().slice(0, 8)
4041

4142
try {
4243
const body = await request.json()

apps/sim/app/api/tools/mysql/utils.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,11 @@ export async function createMySQLConnection(config: MySQLConnectionConfig) {
1818
password: config.password,
1919
}
2020

21-
// Handle SSL configuration
2221
if (config.ssl === 'required') {
2322
connectionConfig.ssl = { rejectUnauthorized: true }
2423
} else if (config.ssl === 'preferred') {
2524
connectionConfig.ssl = { rejectUnauthorized: false }
2625
}
27-
// For 'disabled', we don't set the ssl property at all
2826

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

57-
// Block dangerous SQL operations
5855
const dangerousPatterns = [
5956
/drop\s+database/i,
6057
/drop\s+schema/i,
@@ -91,7 +88,6 @@ export function validateQuery(query: string): { isValid: boolean; error?: string
9188
}
9289
}
9390

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

118114
export function buildUpdateQuery(table: string, data: Record<string, unknown>, where: string) {
115+
validateWhereClause(where)
116+
119117
const sanitizedTable = sanitizeIdentifier(table)
120118
const columns = Object.keys(data)
121119
const values = Object.values(data)
@@ -127,14 +125,33 @@ export function buildUpdateQuery(table: string, data: Record<string, unknown>, w
127125
}
128126

129127
export function buildDeleteQuery(table: string, where: string) {
128+
validateWhereClause(where)
129+
130130
const sanitizedTable = sanitizeIdentifier(table)
131131
const query = `DELETE FROM ${sanitizedTable} WHERE ${where}`
132132

133133
return { query, values: [] }
134134
}
135135

136+
function validateWhereClause(where: string): void {
137+
const dangerousPatterns = [
138+
/;\s*(drop|delete|insert|update|create|alter|grant|revoke)/i,
139+
/union\s+select/i,
140+
/into\s+outfile/i,
141+
/load_file/i,
142+
/--/,
143+
/\/\*/,
144+
/\*\//,
145+
]
146+
147+
for (const pattern of dangerousPatterns) {
148+
if (pattern.test(where)) {
149+
throw new Error('WHERE clause contains potentially dangerous operation')
150+
}
151+
}
152+
}
153+
136154
export function sanitizeIdentifier(identifier: string): string {
137-
// Handle schema.table format
138155
if (identifier.includes('.')) {
139156
const parts = identifier.split('.')
140157
return parts.map((part) => sanitizeSingleIdentifier(part)).join('.')
@@ -144,16 +161,13 @@ export function sanitizeIdentifier(identifier: string): string {
144161
}
145162

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

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

157-
// Wrap in backticks for MySQL
158172
return `\`${cleaned}\``
159173
}

apps/sim/app/api/tools/postgresql/delete/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { randomUUID } from 'crypto'
12
import { type NextRequest, NextResponse } from 'next/server'
23
import { z } from 'zod'
34
import { createLogger } from '@/lib/logs/console/logger'
@@ -17,7 +18,7 @@ const DeleteSchema = z.object({
1718
})
1819

1920
export async function POST(request: NextRequest) {
20-
const requestId = crypto.randomUUID().slice(0, 8)
21+
const requestId = randomUUID().slice(0, 8)
2122

2223
try {
2324
const body = await request.json()

apps/sim/app/api/tools/postgresql/execute/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { randomUUID } from 'crypto'
12
import { type NextRequest, NextResponse } from 'next/server'
23
import { z } from 'zod'
34
import { createLogger } from '@/lib/logs/console/logger'
@@ -20,7 +21,7 @@ const ExecuteSchema = z.object({
2021
})
2122

2223
export async function POST(request: NextRequest) {
23-
const requestId = crypto.randomUUID().slice(0, 8)
24+
const requestId = randomUUID().slice(0, 8)
2425

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

33-
// Validate query before execution
3434
const validation = validateQuery(params.query)
3535
if (!validation.isValid) {
3636
logger.warn(`[${requestId}] Query validation failed: ${validation.error}`)

apps/sim/app/api/tools/postgresql/insert/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { randomUUID } from 'crypto'
12
import { type NextRequest, NextResponse } from 'next/server'
23
import { z } from 'zod'
34
import { createLogger } from '@/lib/logs/console/logger'
@@ -38,7 +39,7 @@ const InsertSchema = z.object({
3839
})
3940

4041
export async function POST(request: NextRequest) {
41-
const requestId = crypto.randomUUID().slice(0, 8)
42+
const requestId = randomUUID().slice(0, 8)
4243

4344
try {
4445
const body = await request.json()

apps/sim/app/api/tools/postgresql/query/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { randomUUID } from 'crypto'
12
import { type NextRequest, NextResponse } from 'next/server'
23
import { z } from 'zod'
34
import { createLogger } from '@/lib/logs/console/logger'
@@ -16,7 +17,7 @@ const QuerySchema = z.object({
1617
})
1718

1819
export async function POST(request: NextRequest) {
19-
const requestId = crypto.randomUUID().slice(0, 8)
20+
const requestId = randomUUID().slice(0, 8)
2021

2122
try {
2223
const body = await request.json()

0 commit comments

Comments
 (0)