File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 11import { MongoClient } from 'mongodb'
2- import { validateDatabaseHost } from '@/lib/core/security/input-validation.server'
2+ import {
3+ createPinnedLookup ,
4+ validateDatabaseHost ,
5+ } from '@/lib/core/security/input-validation.server'
36import type { MongoDBCollectionInfo , MongoDBConnectionConfig } from '@/tools/mongodb/types'
47
58export async function createMongoDBConnection ( config : MongoDBConnectionConfig ) {
@@ -30,6 +33,7 @@ export async function createMongoDBConnection(config: MongoDBConnectionConfig) {
3033 connectTimeoutMS : 10000 ,
3134 socketTimeoutMS : 10000 ,
3235 maxPoolSize : 1 ,
36+ lookup : createPinnedLookup ( hostValidation . resolvedIP ?? config . host ) ,
3337 } )
3438
3539 await client . connect ( )
Original file line number Diff line number Diff line change 1+ import net from 'node:net'
12import mysql from 'mysql2/promise'
23import { validateDatabaseHost } from '@/lib/core/security/input-validation.server'
34
@@ -16,12 +17,20 @@ export async function createMySQLConnection(config: MySQLConnectionConfig) {
1617 throw new Error ( hostValidation . error )
1718 }
1819
20+ const resolvedIP = hostValidation . resolvedIP ?? config . host
21+
1922 const connectionConfig : mysql . ConnectionOptions = {
2023 host : config . host ,
2124 port : config . port ,
2225 database : config . database ,
2326 user : config . username ,
2427 password : config . password ,
28+ // Pin socket to resolved IP to prevent DNS rebinding; mysql2 still uses config.host for TLS servername.
29+ stream : ( ) => {
30+ const socket = net . connect ( config . port , resolvedIP )
31+ socket . setNoDelay ( true )
32+ return socket
33+ } ,
2534 }
2635
2736 if ( config . ssl === 'disabled' ) {
Original file line number Diff line number Diff line change @@ -18,7 +18,15 @@ export async function createNeo4jDriver(config: Neo4jConnectionConfig) {
1818 protocol = config . encryption === 'enabled' ? 'bolt+s' : 'bolt'
1919 }
2020
21- const uri = `${ protocol } ://${ config . host } :${ config . port } `
21+ // neo4j-driver hardcodes TLS servername to the URI host with no override, so we only pin the IP for non-TLS to preserve Aura cert validation.
22+ const useIPPinning = ! protocol . endsWith ( '+s' )
23+ const resolvedIP = hostValidation . resolvedIP ?? config . host
24+ const uriHost = useIPPinning
25+ ? resolvedIP . includes ( ':' )
26+ ? `[${ resolvedIP } ]`
27+ : resolvedIP
28+ : config . host
29+ const uri = `${ protocol } ://${ uriHost } :${ config . port } `
2230
2331 const driverConfig : any = {
2432 maxConnectionPoolSize : 1 ,
Original file line number Diff line number Diff line change @@ -8,17 +8,18 @@ export async function createPostgresConnection(config: PostgresConnectionConfig)
88 throw new Error ( hostValidation . error )
99 }
1010
11- const sslConfig =
11+ const resolvedHost = hostValidation . resolvedIP ?? config . host
12+
13+ // `rejectUnauthorized: false` matches postgres.js's `'require'` string semantics; `servername` is set so SNI works with the pinned IP host.
14+ const sslConfig : boolean | 'prefer' | { rejectUnauthorized : boolean ; servername ?: string } =
1215 config . ssl === 'disabled'
1316 ? false
14- : config . ssl === 'required'
15- ? 'require'
16- : config . ssl === 'preferred'
17- ? 'prefer'
18- : 'require'
17+ : config . ssl === 'preferred'
18+ ? 'prefer'
19+ : { rejectUnauthorized : false , servername : config . host }
1920
2021 const sql = postgres ( {
21- host : config . host ,
22+ host : resolvedHost ,
2223 port : config . port ,
2324 database : config . database ,
2425 username : config . username ,
Original file line number Diff line number Diff line change @@ -36,7 +36,25 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
3636 return NextResponse . json ( { error : hostValidation . error } , { status : 400 } )
3737 }
3838
39- client = new Redis ( url , {
39+ const resolvedIP = hostValidation . resolvedIP ?? hostname
40+ const tlsEnabled = parsedUrl . protocol === 'rediss:'
41+ const port = parsedUrl . port ? Number ( parsedUrl . port ) : 6379
42+ const username = parsedUrl . username ? decodeURIComponent ( parsedUrl . username ) : undefined
43+ const password = parsedUrl . password ? decodeURIComponent ( parsedUrl . password ) : undefined
44+ const dbIndex =
45+ parsedUrl . pathname && parsedUrl . pathname . length > 1
46+ ? Number . parseInt ( parsedUrl . pathname . slice ( 1 ) , 10 )
47+ : Number . NaN
48+
49+ // Pin to resolved IP to prevent DNS rebinding; for `rediss://`, pass original hostname as TLS servername.
50+ client = new Redis ( {
51+ host : resolvedIP ,
52+ port,
53+ username,
54+ password,
55+ db : Number . isFinite ( dbIndex ) ? dbIndex : 0 ,
56+ family : resolvedIP . includes ( ':' ) ? 6 : 4 ,
57+ tls : tlsEnabled ? { servername : hostname } : undefined ,
4058 connectTimeout : 10000 ,
4159 commandTimeout : 10000 ,
4260 maxRetriesPerRequest : 1 ,
You can’t perform that action at this time.
0 commit comments