diff --git a/apps/sim/app/api/tools/mongodb/utils.ts b/apps/sim/app/api/tools/mongodb/utils.ts index 33e6af90ae..7fb17e1742 100644 --- a/apps/sim/app/api/tools/mongodb/utils.ts +++ b/apps/sim/app/api/tools/mongodb/utils.ts @@ -1,5 +1,8 @@ import { MongoClient } from 'mongodb' -import { validateDatabaseHost } from '@/lib/core/security/input-validation.server' +import { + createPinnedLookup, + validateDatabaseHost, +} from '@/lib/core/security/input-validation.server' import type { MongoDBCollectionInfo, MongoDBConnectionConfig } from '@/tools/mongodb/types' export async function createMongoDBConnection(config: MongoDBConnectionConfig) { @@ -30,6 +33,7 @@ export async function createMongoDBConnection(config: MongoDBConnectionConfig) { connectTimeoutMS: 10000, socketTimeoutMS: 10000, maxPoolSize: 1, + lookup: createPinnedLookup(hostValidation.resolvedIP ?? config.host), }) await client.connect() diff --git a/apps/sim/app/api/tools/mysql/utils.ts b/apps/sim/app/api/tools/mysql/utils.ts index 30883aa7f2..971bc31ba2 100644 --- a/apps/sim/app/api/tools/mysql/utils.ts +++ b/apps/sim/app/api/tools/mysql/utils.ts @@ -1,3 +1,4 @@ +import net from 'node:net' import mysql from 'mysql2/promise' import { validateDatabaseHost } from '@/lib/core/security/input-validation.server' @@ -16,12 +17,19 @@ export async function createMySQLConnection(config: MySQLConnectionConfig) { throw new Error(hostValidation.error) } + const resolvedIP = hostValidation.resolvedIP ?? config.host + const connectionConfig: mysql.ConnectionOptions = { host: config.host, port: config.port, database: config.database, user: config.username, password: config.password, + stream: () => { + const socket = net.connect({ host: resolvedIP, port: config.port, timeout: 10000 }) + socket.setNoDelay(true) + return socket + }, } if (config.ssl === 'disabled') { diff --git a/apps/sim/app/api/tools/neo4j/utils.ts b/apps/sim/app/api/tools/neo4j/utils.ts index f843d723a0..ac0bdf0eb0 100644 --- a/apps/sim/app/api/tools/neo4j/utils.ts +++ b/apps/sim/app/api/tools/neo4j/utils.ts @@ -18,7 +18,14 @@ export async function createNeo4jDriver(config: Neo4jConnectionConfig) { protocol = config.encryption === 'enabled' ? 'bolt+s' : 'bolt' } - const uri = `${protocol}://${config.host}:${config.port}` + const useIPPinning = !protocol.endsWith('+s') + const resolvedIP = hostValidation.resolvedIP ?? config.host + const uriHost = useIPPinning + ? resolvedIP.includes(':') + ? `[${resolvedIP}]` + : resolvedIP + : config.host + const uri = `${protocol}://${uriHost}:${config.port}` const driverConfig: any = { maxConnectionPoolSize: 1, diff --git a/apps/sim/app/api/tools/postgresql/utils.ts b/apps/sim/app/api/tools/postgresql/utils.ts index 55f0bbe930..dfeeab9ead 100644 --- a/apps/sim/app/api/tools/postgresql/utils.ts +++ b/apps/sim/app/api/tools/postgresql/utils.ts @@ -8,17 +8,18 @@ export async function createPostgresConnection(config: PostgresConnectionConfig) throw new Error(hostValidation.error) } - const sslConfig = + const resolvedHost = hostValidation.resolvedIP ?? config.host + const pinIP = config.ssl !== 'preferred' + + const sslConfig: boolean | 'prefer' | { rejectUnauthorized: boolean; servername?: string } = config.ssl === 'disabled' ? false - : config.ssl === 'required' - ? 'require' - : config.ssl === 'preferred' - ? 'prefer' - : 'require' + : config.ssl === 'preferred' + ? 'prefer' + : { rejectUnauthorized: false, servername: config.host } const sql = postgres({ - host: config.host, + host: pinIP ? resolvedHost : config.host, port: config.port, database: config.database, username: config.username, diff --git a/apps/sim/app/api/tools/redis/execute/route.ts b/apps/sim/app/api/tools/redis/execute/route.ts index c3fb36c85b..7a38c676b9 100644 --- a/apps/sim/app/api/tools/redis/execute/route.ts +++ b/apps/sim/app/api/tools/redis/execute/route.ts @@ -36,7 +36,33 @@ export const POST = withRouteHandler(async (request: NextRequest) => { return NextResponse.json({ error: hostValidation.error }, { status: 400 }) } - client = new Redis(url, { + const resolvedIP = hostValidation.resolvedIP ?? hostname + const tlsEnabled = parsedUrl.protocol === 'rediss:' + const port = parsedUrl.port ? Number(parsedUrl.port) : 6379 + const username = parsedUrl.username ? decodeURIComponent(parsedUrl.username) : undefined + const password = parsedUrl.password ? decodeURIComponent(parsedUrl.password) : undefined + + let db = 0 + if (parsedUrl.pathname && parsedUrl.pathname.length > 1) { + const dbSegment = parsedUrl.pathname.slice(1) + const parsedDb = Number.parseInt(dbSegment, 10) + if (!Number.isFinite(parsedDb) || String(parsedDb) !== dbSegment) { + return NextResponse.json( + { error: `Invalid Redis database index in URL path: '${dbSegment}'` }, + { status: 400 } + ) + } + db = parsedDb + } + + client = new Redis({ + host: resolvedIP, + port, + username, + password, + db, + family: resolvedIP.includes(':') ? 6 : 4, + tls: tlsEnabled ? { servername: hostname } : undefined, connectTimeout: 10000, commandTimeout: 10000, maxRetriesPerRequest: 1,