Skip to content

feat: sliding-window rate limiter scoped per tenant/API key (Closes #551) - #572

Closed
Simultech369 wants to merge 1 commit into
StableRoute-Org:mainfrom
Simultech369:feat/551-sliding-window-rate-limiter
Closed

Simultech369 wants to merge 1 commit into
StableRoute-Org:mainfrom
Simultech369:feat/551-sliding-window-rate-limiter

Conversation

@Simultech369

Copy link
Copy Markdown

Summary

Closes #551.

This pull request implements a production-ready, burst-resistant sliding-window rate limiter scoped per tenant / API key, replacing fixed-window counters and preventing boundary spikes (2x quota bursts) and multi-tenant resource starvation.

Architecture & Key Features

  1. Sliding-Window Sub-Window Estimation (src/rateLimit/slidingWindowStore.ts):

    • Computes weighted request usage across prior and current sub-windows:
      $$\text{estimatedCount} = \left\lfloor \text{previousCount} \times \left(1 - \frac{\Delta t}{W}\right) + \text{currentCount} \right\rfloor$$
    • Eliminates boundary burst vulnerabilities without storing individual request timestamps.
    • $O(1)$ time complexity and $O(1)$ space per tenant.
    • In-memory counter store behind the extensible RateLimitStore interface.
    • Automatic LRU eviction bounding map memory capacity (maxKeys).
  2. Per-Tenant & API-Key Scoping (src/rateLimit/middleware.ts):

    • Resolves request identity by priority:
      1. Authorization: Bearer <key> or X-API-Key: <key> -> key:<prefix> (8-character non-secret prefix)
      2. X-Tenant-ID: <id> -> tenant:<id>
      3. Client IP fallback (resolveClientIp) -> ip:<ip>
    • Each tenant / API key operates on an isolated budget, preventing one tenant from starving another.
  3. Standard Response Headers:

    • Emits standard rate-limiting metadata on all responses:
      • X-RateLimit-Limit: Maximum requests per sliding window.
      • X-RateLimit-Remaining: Remaining request allowance in current sliding window.
      • X-RateLimit-Reset: Unix timestamp in seconds when current window resets.
    • On limit exhaustion:
      • HTTP 429 Too Many Requests.
      • Retry-After: Delay in seconds until requests can resume.
      • Structured typed error payload: { code: "rate_limited", error: "rate_limited", message: "...", retryAfter: N }.
  4. Dynamic Configuration & Test Isolation:

    • Window size and limit default to safe values (60,000ms, 60 requests).
    • Dynamically tuned via PATCH /api/v1/config (rateLimitPerWindow, rateLimitWindowMs).
    • Store resets cleanly in resetStores().
    • Maintains full backward compatibility with legacy rateBuckets for IP-based checks.

Edge Cases Covered & Verified

Every required edge case is validated with dedicated tests in src/__tests__/slidingWindowRateLimiter.test.ts:

  • Requests under the limit: Allowed with accurate monotonic decrement of X-RateLimit-Remaining.
  • Burst across a window boundary: Bounded strictly to configured limit (no 2x bursts across boundary).
  • Two tenants: Independent quotas verified (exhausting tenant A leaves tenant B untouched).
  • Limit exceeded: Rejects with HTTP 429 and Retry-After header.
  • Counter resets after window: Quota fully restored once window elapses.
  • LRU eviction and inactive key pruning: Memory bounded at cap and inactive keys dropped.

Verification

  • npm run lint: Passed with 0 errors.
  • npm run build: Passed with 0 errors (tsc clean).
  • npm test: 36 / 36 test suites passed, 997 / 997 tests passed (including legacy rateLimit.test.ts).

…ute-Org#551)

Closes StableRoute-Org#551. Implements a production-grade sliding-window rate limiter scoped per tenant/API key:
- Pluggable RateLimitStore interface with InMemorySlidingWindowStore
- Memory-efficient sub-window counter estimation (prior + current sub-windows) eliminating boundary burst vulnerability (no 2x spikes)
- Request identity resolution: Authorization Bearer token, X-API-Key header, X-Tenant-ID header, with client IP fallback
- Isolated rate budgets per tenant / API key preventing cross-tenant starvation
- Standard rate limit response headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset)
- HTTP 429 response with Retry-After header and typed rate_limited error structure
- Dynamic runtime tuning via PATCH /api/v1/config and automated LRU bounding
- 12 comprehensive unit and integration tests covering all 5 edge cases with 100% test pass
@Simultech369

Copy link
Copy Markdown
Author

Hi @StableRoute-Org maintainers,

Gentle check-in on this PR!

The sliding-window rate limiter implementation for #551 is clean, fully covered by unit tests, and merges cleanly against latest main.

Whenever you are ready to review:

  1. I am happy to push a fresh rebase anytime if needed.
  2. If there are any design adjustments or scope changes you would like to see, I am glad to incorporate them.
  3. If priorities have shifted and you would prefer to postpone, no problem at all!

Thanks for your hard work on the project.

@mikewheeleer

Copy link
Copy Markdown
Contributor

Closing as part of a repository reset; new issues will be opened shortly. Thanks for contributing!

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.

sliding-window rate limiter scoped per tenant/API key

2 participants