Skip to content

fix(tools): pin resolved IP in DB connectors to prevent DNS-rebinding SSRF - #4725

Merged
waleedlatif1 merged 6 commits into
stagingfrom
waleedlatif1/fix-dns-rebind-ssrf
May 22, 2026
Merged

fix(tools): pin resolved IP in DB connectors to prevent DNS-rebinding SSRF#4725
waleedlatif1 merged 6 commits into
stagingfrom
waleedlatif1/fix-dns-rebind-ssrf

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • validateDatabaseHost resolved an IP that was then discarded; the drivers re-resolved the hostname at connect time, leaving a DNS-rebinding TOCTOU window
  • MongoDB: pass resolved IP via the MongoClient lookup option
  • MySQL: pin the TCP socket via the stream factory; keep config.host for TLS servername
  • PostgreSQL: connect to resolved IP; pass ssl as object with servername so SNI works against the pinned IP
  • Redis: parse the URL explicitly and pass options-only (URL+options doesn't override host due to ioredis's lodash.defaults); set tls.servername for rediss://
  • Neo4j: pin IP only for plain bolt://; leave bolt+s/neo4j+s unchanged so Aura cert validation keeps working (driver hardcodes servername with no override)

Type of Change

  • Bug fix (security)

Testing

Tested manually. Type-check, biome lint, and bun run check:api-validation all pass. Driver source code reviewed to verify each pinning mechanism reaches the underlying net.connect/tls.connect call.

Pre-merge verification still needed against live managed providers (Atlas, RDS, Heroku PG, ElastiCache TLS, Aura).

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

… SSRF
`validateDatabaseHost` resolved an IP that was then discarded — drivers re-resolved
the hostname at connect time, enabling DNS-rebinding TOCTOU.
- mongodb: pass resolved IP via MongoClient `lookup` option
- mysql: pin TCP socket via `stream` factory; keep hostname for TLS servername
- postgresql: connect to resolved IP; pass `ssl` object with `servername` for SNI
- redis: parse URL explicitly and pass options-only (URL+options breaks override
due to ioredis's lodash.defaults); pin host and set `tls.servername` for rediss
- neo4j: pin IP for plain `bolt://`; leave `bolt+s`/`neo4j+s` unchanged to keep
Aura cert validation working (driver hardcodes servername with no override)
@vercel

vercelBot commented May 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
docsSkippedSkippedMay 22, 2026 5:21pm

Request Review

@cursor

cursorBot commented May 22, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Moderate risk because it changes low-level connection behavior across multiple database drivers (MongoDB/MySQL/Postgres/Redis/Neo4j), which could break connectivity or TLS/SNI expectations in some environments, but the change is localized and security-motivated.

Overview
Closes a DNS-rebinding/TOCTOU gap in the database tools by pinning connections to the IP resolved during validateDatabaseHost instead of allowing drivers to re-resolve hostnames at connect time.

MongoDB now uses a custom lookup (createPinnedLookup) to force the resolved address; MySQL pins the TCP socket via a stream factory; Postgres connects to the resolved IP while preserving SNI via ssl.servername; Redis now parses the URL and instantiates ioredis with explicit host/port/credentials/db (including TLS servername) so the pinned host actually takes effect; Neo4j pins only for non-+s protocols (plain bolt://) to avoid breaking managed TLS behavior.

Reviewed by Cursor Bugbot for commit 32c4b1a. Configure here.

@greptile-apps

greptile-appsBot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR closes the DNS-rebinding TOCTOU window across all five database connectors by ensuring the IP resolved during the SSRF validation phase is the same IP used for the actual TCP connection — each driver's pinning mechanism is chosen to be compatible with its TLS SNI requirements.

  • MongoDB: lookup option intercepts the driver's internal DNS calls and always returns the pre-resolved IP; the URI keeps config.host so TLS SNI continues to work.
  • MySQL: a stream factory opens the TCP socket directly to the resolved IP, while config.host stays in connectionConfig for mysql2 to use as the TLS servername during the protocol-level SSL upgrade.
  • PostgreSQL: host is overridden to the resolved IP and an ssl object with servername: config.host preserves SNI; preferred mode intentionally skips IP pinning since TLS is optional there.
  • Redis: the URL is parsed manually into discrete ioredis options to avoid the lodash.defaults merge bug that prevented host from being overridden; tls.servername is set to the original hostname for rediss://.
  • Neo4j: IP pinning is applied only for plain bolt://; bolt+s/neo4j+s connections are left unchanged because the driver hardcodes servername from the URI host with no public override, which would break Aura certificate validation.

Confidence Score: 5/5

Safe to merge; each connector's pinning mechanism correctly preserves TLS SNI semantics and the overall security posture is meaningfully improved.

All five connectors correctly thread the resolved IP into the underlying TCP connection while keeping the original hostname available for TLS SNI. The one concrete finding (Redis db-index validation accepting negative integers) is a minor UX issue that surfaces as a Redis-level error rather than a security or data-integrity problem. No regressions in SSL behaviour were introduced.

apps/sim/app/api/tools/redis/execute/route.ts — the db-index path parsing accepts negative numbers; all other files look correct.

Important Files Changed

FilenameOverview
apps/sim/app/api/tools/mongodb/utils.tsAdds lookup: createPinnedLookup(...) to MongoClient options to intercept DNS resolution at the driver level; URI still uses config.host so TLS SNI is preserved correctly.
apps/sim/app/api/tools/mysql/utils.tsIntroduces a stream factory that opens the TCP socket directly to the resolved IP; original hostname is kept in connectionConfig.host so mysql2 uses it for TLS SNI during the protocol-level SSL upgrade.
apps/sim/app/api/tools/postgresql/utils.tsPins connection host to resolved IP for all SSL modes except preferred; passes servername: config.host in the ssl object so SNI still uses the original hostname when connecting to a pinned IP.
apps/sim/app/api/tools/redis/execute/route.tsReplaces URL+options construction with explicit option fields to avoid ioredis lodash.defaults merge issue; db path parsing uses parseInt strict-equality check but does not reject negative indices.
apps/sim/app/api/tools/neo4j/utils.tsPins IP in the Bolt URI for plain bolt:// connections; deliberately skips pinning for bolt+s/neo4j+s because neo4j-driver hardcodes TLS servername from the URI host with no public override — documented trade-off.

Sequence Diagram

sequenceDiagram
participant Client
participant Validator as validateDatabaseHost
participant DNS
participant Connector as DB Connector
participant DB as Database Server
Client->>Validator: host string
Validator->>DNS: dns.lookup(host)
DNS-->>Validator: resolvedIP
Validator->>Validator: isPrivateOrReservedIP check
Validator-->>Client: resolvedIP
rect rgb(200, 230, 255)
Note over Connector,DB: MongoDB - lookup option
Client->>Connector: createMongoDBConnection
Connector->>DB: MongoClient with lookup pinned to resolvedIP
end
rect rgb(220, 245, 200)
Note over Connector,DB: MySQL - stream factory
Client->>Connector: createMySQLConnection
Connector->>DB: net.connect to resolvedIP then TLS SNI via config.host
end
rect rgb(255, 235, 200)
Note over Connector,DB: PostgreSQL - host override plus servername
Client->>Connector: createPostgresConnection
Connector->>DB: postgres host set to resolvedIP with ssl servername config.host
end
rect rgb(240, 220, 255)
Note over Connector,DB: Redis - explicit options object
Client->>Connector: Redis POST route
Connector->>DB: new Redis with host resolvedIP and tls servername hostname
end
rect rgb(255, 215, 215)
Note over Connector,DB: Neo4j - URI host replacement
Client->>Connector: createNeo4jDriver
alt bolt plain
Connector->>DB: bolt://resolvedIP:port
else bolt+s or neo4j+s TLS
Connector->>DB: bolt+s://config.host:port no IP pinning
end
end
Loading

Reviews (4): Last reviewed commit: "fix(tools): reject non-numeric Redis URL..." | Re-trigger Greptile

Comment threadapps/sim/app/api/tools/neo4j/utils.ts
Comment threadapps/sim/app/api/tools/mysql/utils.ts Outdated
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

Comment threadapps/sim/app/api/tools/postgresql/utils.ts Outdated
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

Comment threadapps/sim/app/api/tools/postgresql/utils.ts Outdated
Comment threadapps/sim/app/api/tools/redis/execute/route.ts Outdated
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 32c4b1a. Configure here.

@waleedlatif1
waleedlatif1 merged commit afcbcf2 into stagingMay 22, 2026
14 checks passed
@waleedlatif1
waleedlatif1 deleted the waleedlatif1/fix-dns-rebind-ssrf branch May 22, 2026 17:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@waleedlatif1