Skip to content

fix(rate-limit): add Retry-After header to 429 responses and fix TOCTOU race in Redis rate limiter#6156

Closed
Aamod-Dev wants to merge 1 commit into
JhaSourav07:mainfrom
Aamod-Dev:fix/rate-limit-retry-after-5857
Closed

fix(rate-limit): add Retry-After header to 429 responses and fix TOCTOU race in Redis rate limiter#6156
Aamod-Dev wants to merge 1 commit into
JhaSourav07:mainfrom
Aamod-Dev:fix/rate-limit-retry-after-5857

Conversation

@Aamod-Dev

Copy link
Copy Markdown
Collaborator

Summary

Fixes the rate-limiting issues documented in #6149. Addresses the missing \Retry-After\ header (as originally requested in #5857) and fixes two bugs in the Redis rate-limit path.

Changes

1. Added \Retry-After\ header to all 429 responses

Files: \lib/rate-limit.ts, \middleware.ts\

The \getRateLimitHeaders()\ helper now includes the standard \Retry-After\ HTTP header in addition to the existing \X-RateLimit-*\ headers. The middleware now uses this helper instead of manually constructing headers.

Before: 429 responses had \X-RateLimit-Limit, \X-RateLimit-Remaining, \X-RateLimit-Reset\ but no \Retry-After.

After: 429 responses include:
\
Retry-After: 42
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1712345678000
\\

2. Fixed TOCTOU race condition in Redis rate-limit path

File: \lib/rate-limit.ts:72-121\

The \RateLimiter.checkWithResult()\ method previously used a non-atomic read-then-write pattern:

  1. \GET\ current count
  2. \TTL\ check
  3. If under limit, \INCR\ + \EXPIRE\

Under concurrent load, multiple requests could all read the same sub-limit count and all pass through. Replaced with a single atomic \INCR\ + \EXPIRE\ pipeline that matches the approach used in the
ateLimit()\ helper function.

3. Fixed \

emaining()\ method for Redis-backed deployments

File: \lib/rate-limit.ts:195-198\

The
emaining()\ method was reading from local cache key
atelimit:\ while the Redis path stored under
atelimit_class:\. When Redis was active, this always returned the full limit. Now queries Redis directly when available using the correct key prefix.

Related Issues

Closes: #6149
Related: #5857

…ng() key prefix

- Add Retry-After header to getRateLimitHeaders() for RFC-compliant 429 responses
- Fix middleware.ts to use getRateLimitHeaders() instead of manual headers
- Fix TOCTOU race in RateLimiter.checkWithResult() Redis path
  (replaced GET+TTL+INCR with single atomic INCR+EXPIRE pipeline)
- Fix remaining() to use correct key prefix (ratelimit:) and query Redis when available

Related to JhaSourav07#5857
@Aamod-Dev Aamod-Dev added bug Something isn't working security labels Jun 21, 2026
@vercel

vercel Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

@Aamod-Dev is attempting to deploy a commit to the jhasourav07's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

Copy link
Copy Markdown
Contributor

👋 Hey @Aamod-Dev! Thanks for your interest in contributing to CommitPulse! 🙏

Unfortunately, this PR has been automatically closed because you are not assigned to the linked issue #6149 — fix(rate-limit): add Retry-After header to 429 responses and fix TOCTOU race in Redis rate limiter.

To avoid this in the future, please follow these steps:

  1. Claim the issue — Comment /claim on #6149 if you are the issue author, or ask a maintainer to /assign you.
  2. Wait for confirmation — The bot will confirm your assignment with a ✅ reply.
  3. Then open your PR — Link the issue with Fixes #6149 in your description.

💡 You can be assigned to up to 5 open issues at a time. Check your current assignments before claiming a new one.

We look forward to your contribution once you're assigned! 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working security type:bug Something isn't working as expected

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(rate-limit): add Retry-After header to 429 responses and fix TOCTOU race in Redis rate limiter

1 participant