Skip to content

🚦 fix: ERR_ERL_INVALID_IP_ADDRESS and IPv6 Key Collisions in IP Rate Limiters#12319

Merged
danny-avila merged 3 commits into
danny-avila:devfrom
bprussell:fix/rate-limiter-ip-keyGenerator
Mar 20, 2026
Merged

🚦 fix: ERR_ERL_INVALID_IP_ADDRESS and IPv6 Key Collisions in IP Rate Limiters#12319
danny-avila merged 3 commits into
danny-avila:devfrom
bprussell:fix/rate-limiter-ip-keyGenerator

Conversation

@bprussell

Copy link
Copy Markdown
Contributor

Summary

Six IP-based rate limiters (uploadLimiters, messageLimiters, ttsLimiters, sttLimiters, importLimiters, forkLimiters) are missing the keyGenerator: removePorts option, causing ERR_ERL_INVALID_IP_ADDRESS errors when deployed behind reverse proxies that include port numbers in X-Forwarded-For headers.

The removePorts utility already exists at api/server/utils/removePorts.js and is already used by the four auth-related limiters (loginLimiter, registerLimiter, resetPasswordLimiter, verifyEmailLimiter). This PR applies the same pattern to the remaining six IP-based limiters for consistency.

Fixes #12318

Changes

For each of the 6 affected files:

  1. Added const { removePorts } = require('~/server/utils'); import
  2. Added keyGenerator: removePorts to the ipLimiterOptions object

Testing

  • Reproduced the bug on an unmodified upstream deployment (ghcr.io/danny-avila/librechat-dev:latest) on Azure App Service with MongoDB Atlas
  • Verified the fix resolves ERR_ERL_INVALID_IP_ADDRESS errors behind Azure App Service reverse proxy
  • No behavior change when not behind a reverse proxy (removePorts gracefully handles IPs without ports)

Six IP-based rate limiters are missing the `keyGenerator: removePorts`
option that is already used by the auth-related limiters (login,
register, resetPassword, verifyEmail). Without it, reverse proxies that
include ports in X-Forwarded-For headers cause
ERR_ERL_INVALID_IP_ADDRESS errors from express-rate-limit.

Fixes danny-avila#12318
Copilot AI review requested due to automatic review settings March 19, 2026 23:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes ERR_ERL_INVALID_IP_ADDRESS errors for IP-based express-rate-limit middleware when LibreChat is deployed behind reverse proxies that include port numbers in X-Forwarded-For/req.ip. It does this by applying the existing removePorts key generator (already used by auth limiters) to the remaining IP-based limiters for consistent behavior.

Changes:

  • Imported removePorts from ~/server/utils in six limiter modules.
  • Added keyGenerator: removePorts to each IP-based limiter options object to normalize req.ip values that include ports.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
api/server/middleware/limiters/uploadLimiters.js Add removePorts as keyGenerator for the file-upload IP limiter.
api/server/middleware/limiters/messageLimiters.js Add removePorts as keyGenerator for the message IP limiter.
api/server/middleware/limiters/ttsLimiters.js Add removePorts as keyGenerator for the TTS IP limiter.
api/server/middleware/limiters/sttLimiters.js Add removePorts as keyGenerator for the STT IP limiter.
api/server/middleware/limiters/importLimiters.js Add removePorts as keyGenerator for the import IP limiter.
api/server/middleware/limiters/forkLimiters.js Add removePorts as keyGenerator for the fork IP limiter.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@bprussell bprussell changed the title fix: Add removePorts keyGenerator to all IP-based rate limiters 🩹 fix: Add removePorts keyGenerator to all IP-based rate limiters Mar 19, 2026
The original regex `/:\d+[^:]*$/` treated the last colon-delimited
segment of bare IPv6 addresses as a port, mangling valid IPs
(e.g. `::1` → `::`, `2001:db8::1` → `2001:db8::`). Distinct IPv6
clients could collapse into the same rate-limit bucket.

Use `net.isIP()` as a fast path for already-valid IPs, then match
bracketed IPv6+port and IPv4+port explicitly. Bare IPv6 addresses
are now returned unchanged.

Also fixes pre-existing property ordering inconsistency in
ttsLimiters.js userLimiterOptions (keyGenerator before store).
@danny-avila
danny-avila force-pushed the fix/rate-limiter-ip-keyGenerator branch 3 times, most recently from 2f32798 to 38a8842 Compare March 19, 2026 23:48
@danny-avila
danny-avila changed the base branch from main to dev March 20, 2026 00:09
@danny-avila danny-avila changed the title 🩹 fix: Add removePorts keyGenerator to all IP-based rate limiters 🚦 fix: ERR_ERL_INVALID_IP_ADDRESS and IPv6 Key Collisions in IP Rate Limiters Mar 20, 2026
…order

- Move removePorts implementation to packages/api/src/utils/removePorts.ts
  with proper Express Request typing
- Reduce api/server/utils/removePorts.js to a thin re-export from
  @librechat/api for backward compatibility
- Consolidate removePorts import with limiterCache from @librechat/api
  in all 6 limiter files, fixing import order (package imports shortest
  to longest, local imports longest to shortest)
- Remove narrating inline comments per code style guidelines
@danny-avila
danny-avila force-pushed the fix/rate-limiter-ip-keyGenerator branch from 38a8842 to baa3b29 Compare March 20, 2026 00:13
@danny-avila
danny-avila merged commit ecd6d76 into danny-avila:dev Mar 20, 2026
14 of 15 checks passed
@bprussell
bprussell deleted the fix/rate-limiter-ip-keyGenerator branch March 20, 2026 02:15
jcbartle pushed a commit to jcbartle/LibreChat that referenced this pull request May 11, 2026
…Limiters (danny-avila#12319)

* fix: Add removePorts keyGenerator to all IP-based rate limiters

Six IP-based rate limiters are missing the `keyGenerator: removePorts`
option that is already used by the auth-related limiters (login,
register, resetPassword, verifyEmail). Without it, reverse proxies that
include ports in X-Forwarded-For headers cause
ERR_ERL_INVALID_IP_ADDRESS errors from express-rate-limit.

Fixes danny-avila#12318

* fix: make removePorts IPv6-safe to prevent rate-limit key collisions

The original regex `/:\d+[^:]*$/` treated the last colon-delimited
segment of bare IPv6 addresses as a port, mangling valid IPs
(e.g. `::1` → `::`, `2001:db8::1` → `2001:db8::`). Distinct IPv6
clients could collapse into the same rate-limit bucket.

Use `net.isIP()` as a fast path for already-valid IPs, then match
bracketed IPv6+port and IPv4+port explicitly. Bare IPv6 addresses
are now returned unchanged.

Also fixes pre-existing property ordering inconsistency in
ttsLimiters.js userLimiterOptions (keyGenerator before store).

* refactor: move removePorts to packages/api as TypeScript, fix import order

- Move removePorts implementation to packages/api/src/utils/removePorts.ts
  with proper Express Request typing
- Reduce api/server/utils/removePorts.js to a thin re-export from
  @librechat/api for backward compatibility
- Consolidate removePorts import with limiterCache from @librechat/api
  in all 6 limiter files, fixing import order (package imports shortest
  to longest, local imports longest to shortest)
- Remove narrating inline comments per code style guidelines

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
ThomasVuNguyen pushed a commit to ThomasVuNguyen/LibreChat that referenced this pull request Jul 15, 2026
…Limiters (danny-avila#12319)

* fix: Add removePorts keyGenerator to all IP-based rate limiters

Six IP-based rate limiters are missing the `keyGenerator: removePorts`
option that is already used by the auth-related limiters (login,
register, resetPassword, verifyEmail). Without it, reverse proxies that
include ports in X-Forwarded-For headers cause
ERR_ERL_INVALID_IP_ADDRESS errors from express-rate-limit.

Fixes danny-avila#12318

* fix: make removePorts IPv6-safe to prevent rate-limit key collisions

The original regex `/:\d+[^:]*$/` treated the last colon-delimited
segment of bare IPv6 addresses as a port, mangling valid IPs
(e.g. `::1` → `::`, `2001:db8::1` → `2001:db8::`). Distinct IPv6
clients could collapse into the same rate-limit bucket.

Use `net.isIP()` as a fast path for already-valid IPs, then match
bracketed IPv6+port and IPv4+port explicitly. Bare IPv6 addresses
are now returned unchanged.

Also fixes pre-existing property ordering inconsistency in
ttsLimiters.js userLimiterOptions (keyGenerator before store).

* refactor: move removePorts to packages/api as TypeScript, fix import order

- Move removePorts implementation to packages/api/src/utils/removePorts.ts
  with proper Express Request typing
- Reduce api/server/utils/removePorts.js to a thin re-export from
  @librechat/api for backward compatibility
- Consolidate removePorts import with limiterCache from @librechat/api
  in all 6 limiter files, fixing import order (package imports shortest
  to longest, local imports longest to shortest)
- Remove narrating inline comments per code style guidelines

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
Sign up for free to 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.

[Bug]: IP-based rate limiters throw ERR_ERL_INVALID_IP_ADDRESS behind reverse proxies that include ports in X-Forwarded-For

3 participants