Skip to content

Conversation

@shults
Copy link
Contributor

@shults shults commented Feb 2, 2026

Description

Related Issue

Motivation and Context

How This Has Been Tested

Screenshots (if appropriate)

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Refactoring or add test (improvements in base code or adds test coverage to functionality)

Checklist

  • I ensured that the documentation is up to date
  • I explained why this PR updates go.mod in detail with reasoning why it's required
  • I would like a code coverage CI quality gate exception and have explained why

Ticket Details

TT-6075
Status In Code Review
Summary Create option to populate the X-ratelimit headers from rate limits rather than quotas

Generated at: 2026-02-10 14:49:26

@github-actions
Copy link
Contributor

github-actions bot commented Feb 2, 2026

API Changes

--- prev.txt	2026-02-10 14:50:30.896254510 +0000
+++ current.txt	2026-02-10 14:50:20.311298647 +0000
@@ -6729,6 +6729,12 @@
 
 	// JWKS holds the configuration for Tyk JWKS functionalities
 	JWKS JWKSConfig `json:"jwks"`
+
+	// RateLimitHeadersSource source of rate limit headers
+	RateLimitHeadersSource RateLimitHeadersSource `json:"ratelimit_headers_source"`
+
+	// EnableContextVariables enables extending context data by rate-limit information
+	EnableContextVariables bool `json:"enable_context_variables"`
 }
     Config is the configuration object used by Tyk to set up various parameters.
 
@@ -7300,6 +7306,12 @@
 func (r *RateLimit) String() string
     String returns a readable setting for the rate limiter in effect.
 
+type RateLimitHeadersSource string
+
+const (
+	RateLimitHeadersSourceQuota     RateLimitHeadersSource = "quotas"
+	RateLimitHeadersSourceRateLimit RateLimitHeadersSource = "rate_limits"
+)
 type Reporter struct {
 	// URL connection url to the zipkin server
 	URL        string `json:"url"`
@@ -9990,6 +10002,8 @@
 	TimeStamp     int64
 }
 
+type CtxData = map[string]any
+
 type CustomMiddlewareResponseHook struct {
 	BaseTykResponseHandler
 	// Has unexported fields.
@@ -11672,7 +11686,7 @@
 
 func (k *RateLimitForAPI) Name() string
 
-func (k *RateLimitForAPI) ProcessRequest(_ http.ResponseWriter, r *http.Request, _ interface{}) (error, int)
+func (k *RateLimitForAPI) ProcessRequest(rw http.ResponseWriter, r *http.Request, _ interface{}) (error, int)
     ProcessRequest will run any checks on the request on the way through the
     system, return an error to have the chain fail
 
@@ -12174,7 +12188,12 @@
     SessionLimiter is the rate limiter for the API, use ForwardMessage() to
     check if a message should pass through or not
 
-func NewSessionLimiter(ctx context.Context, conf *config.Config, drlManager *drl.DRL, externalServicesConfig *config.ExternalServiceConfig) SessionLimiter
+func NewSessionLimiter(
+	ctx context.Context,
+	conf *config.Config,
+	drlManager *drl.DRL,
+	externalServicesConfig *config.ExternalServiceConfig,
+) SessionLimiter
     NewSessionLimiter initializes the session limiter.
 
     The session limiter initializes the storage required for rate limiters.
@@ -12196,8 +12215,8 @@
 	dryRun bool,
 ) sessionFailReason
     ForwardMessage will enforce rate limiting, returning a non-zero
-    sessionFailReason if session limits have been exceeded. Key values to manage
-    rate are Rate and Per, e.g. Rate of 10 messages Per 10 seconds
+    sessionFailReasonMarker if session limits have been exceeded. Key values to
+    manage rate are Rate and Per, e.g. Rate of 10 messages Per 10 seconds
 
 func (l *SessionLimiter) RateLimitInfo(r *http.Request, api *APISpec, endpoints user.Endpoints) (*user.EndpointRateLimitInfo, bool)
 
@@ -12877,9 +12896,14 @@
     upgrade and websocket
 
 const (
-	XRateLimitLimit     = "X-RateLimit-Limit"
+	// XRateLimitLimit The maximum number of requests that the client is allowed to make in a given time period
+	XRateLimitLimit = "X-RateLimit-Limit"
+
+	// XRateLimitRemaining The number of requests remaining in the current rate limit window.
 	XRateLimitRemaining = "X-RateLimit-Remaining"
-	XRateLimitReset     = "X-RateLimit-Reset"
+
+	// XRateLimitReset The number of seconds until the rate limit resets.
+	XRateLimitReset = "X-RateLimit-Reset"
 )
     Gateway's custom response headers
 

@probelabs
Copy link

probelabs bot commented Feb 2, 2026

This PR introduces a new feature allowing X-RateLimit-* headers to be populated from the specific rate limit being enforced, rather than the user's overall session quota. This provides more accurate and granular feedback to API clients upon hitting a rate limit. The functionality is controlled by a new global configuration setting, ratelimit_headers_source, which can be set to rate_limits to enable the new behavior or defaults to quotas for backward compatibility.

Additionally, a new enable_context_variables option is introduced. When enabled, it enriches the request context with detailed rate-limit and quota information (limit, remaining, reset time), making this data accessible to other middleware and plugins for advanced use cases.

Files Changed Analysis

The changes span 30 files, indicating a significant refactoring of the rate-limiting and response-handling logic. Key modifications include:

  • Configuration (config/config.go, cli/linter/schema.json): Introduces the new global settings ratelimit_headers_source and enable_context_variables.
  • Core Rate-Limiting (gateway/session_manager.go): This file sees the most substantial changes. The sessionFailReason is refactored from a simple type into an interface that carries detailed context about the rate limit failure (e.g., limit, period, reset time). Logic is also added to populate the request context when enable_context_variables is true.
  • Header Abstraction (internal/rate/headers.go): A new HeaderSender interface is introduced, implementing a Strategy pattern. quotaSender preserves the existing behavior, while rateLimitSender implements the new logic.
  • Gateway & Middleware (gateway/server.go, gateway/mw_*.go, gateway/reverse_proxy.go): The Gateway is initialized with the appropriate HeaderSender based on the configuration. All call sites for the old sendRateLimitHeaders function (which was removed) are updated to use this new abstraction.
  • Limiter Logic (internal/rate/limiter/*.go): The underlying limiter functions are updated to return the remaining time-to-live (TTL), enabling accurate X-RateLimit-Reset header population.

Architecture & Impact Assessment

  • What this PR accomplishes: It decouples the source of X-RateLimit-* headers from session quotas, providing more precise, actionable feedback to API clients when a rate limit is hit. It also enhances the gateway's internal observability by making live rate-limit data available to other middleware via request context variables.

  • Key technical changes introduced:

    1. Context-Rich Failure Signal: The signal for a rate limit failure has been evolved from a simple type to a context-rich interface (sessionFailReason), allowing detailed data (limit, period, reset time) to be passed from the point of failure to the response-writing middleware.
    2. Header Sender Abstraction (Strategy Pattern): A new HeaderSender interface cleanly separates the logic for populating rate limit headers based on the selected source (quotas or rate_limits).
    3. Opt-In Configurability: The new behavior is opt-in via global configuration, ensuring backward compatibility by preserving the existing functionality as the default.
  • Affected system components: The changes impact the core request processing pipeline, including configuration loading, session management, all rate-limiting middleware, and the reverse proxy.

  • Component Interaction Flow:

sequenceDiagram
    participant Client
    participant Gateway
    participant APIRateLimit Middleware
    participant SessionLimiter
    participant HeaderSender

    Client->>+Gateway: HTTP Request
    Gateway->>+APIRateLimit Middleware: ProcessRequest
    APIRateLimit Middleware->>+SessionLimiter: ForwardMessage (check limits)
    alt Rate Limit Exceeded
        SessionLimiter-->>-APIRateLimit Middleware: return sessionFailRateLimit{reset, limit, per}
        APIRateLimit Middleware->>+HeaderSender: SendRateLimits(headers, {reset, limit, per})
        HeaderSender-->>-APIRateLimit Middleware: Populates X-RateLimit-* headers
        APIRateLimit Middleware-->>-Gateway: return Rate Limit Exceeded error
        Gateway-->>-Client: 429 Too Many Requests (with headers)
    else Request Allowed
        SessionLimiter-->>-APIRateLimit Middleware: return sessionFailNone
        APIRateLimit Middleware->>Gateway: Continue processing
        Note over Gateway,Client: X-RateLimit headers sent from quota on final response
    end
Loading

Scope Discovery & Context Expansion

The introduction of enable_context_variables significantly expands the scope of this change beyond just header manipulation. By populating the request context (gateway/model.go), this PR makes live rate-limit and quota data available to any middleware that leverages this shared data map.

This has a direct impact on other powerful features:

  • Custom Middleware: Custom Go plugins, JavaScript (JSVM), and gRPC (CoProcess) middleware can now read this data to implement complex, stateful logic based on a user's real-time rate limit or quota status. For example, they could dynamically degrade service or provide different responses based on the remaining quota.
  • Observability: Middleware responsible for logging or metrics can now access and export detailed rate-limiting and quota data per request, providing deeper insights into API usage and traffic patterns.
Metadata
  • Review Effort: 4 / 5
  • Primary Label: feature

Powered by Visor from Probelabs

Last updated: 2026-02-10T14:52:01.243Z | Triggered by: pr_updated | Commit: ac0fc8b

💡 TIP: You can chat with Visor using /visor ask <your question>

@probelabs
Copy link

probelabs bot commented Feb 2, 2026

Security Issues (1)

Severity Location Issue
🟢 Info gateway/session_manager.go:575-593
The new 'enable_context_variables' feature, when enabled, writes rate-limit and quota details (limit, remaining, reset time) into the request context. This data is then accessible to other middleware and custom plugins (e.g., JSVM, gRPC). While this enables powerful custom logic, it also creates a risk of sensitive information leakage if custom plugins are not implemented carefully. An insecure plugin could inadvertently reflect this context data in API responses or error messages, exposing internal rate-limiting policies and API capacity details to external clients.
💡 SuggestionTo mitigate the risk of unintentional data exposure, the documentation for the 'enable_context_variables' feature should include a clear security warning. This warning should advise plugin developers against reflecting the context data directly in user-facing responses and provide guidance on handling this information securely.

Architecture Issues (1)

Severity Location Issue
🟢 Info gateway/session_manager.go:156-179
Comments at lines 156 and 177-179 are unclear, seem incomplete, or are grammatically incorrect. Clear comments are crucial for maintainability, especially in complex logic like rate limiting.
💡 SuggestionPlease review and rewrite these comments to be clear and complete. For example, the comment at line 156 (`// value can be next from the`) appears to be a leftover fragment and should be completed or removed. The comment block at lines 177-179 should be rephrased into a complete sentence that clearly explains the logic for determining the reset value. For example: 'The reset value is the TTL of the sentinel key, which represents the remaining time in the blocked window. This is an approximation of the actual reset time in a sliding window context.'

Performance Issues (1)

Severity Location Issue
🟡 Warning internal/rate/limiter/limiter_leaky_bucket.go:32-34
The use of `time.Sleep` in this function blocks the request-handling goroutine. In a high-throughput service like an API gateway, blocking worker goroutines is a severe performance anti-pattern. It prevents the goroutine from processing other requests, reducing the gateway's overall throughput and increasing latency.
💡 SuggestionThe leaky bucket algorithm should be implemented in a non-blocking way. Instead of sleeping, the limiter should return an error indicating that the request is too early and should be retried after a certain duration. The `time.Sleep` call should be removed. Although this issue is pre-existing and reportedly behind a build flag, its presence is a significant risk to performance if ever enabled in a production environment.

Quality Issues (2)

Severity Location Issue
🟡 Warning gateway/mw_api_rate_limit.go:98
A broad `//nolint:staticcheck` directive is used without justification. To improve maintainability, `nolint` directives should be specific to the rule being suppressed and include a comment explaining why the suppression is necessary. Consider replacing it with a more specific directive and explanation, or removing it if it's no longer required after the refactoring.
💡 SuggestionReplace the generic `//nolint:staticcheck` with a more specific directive that includes a justification, for example: `//nolint:SA1019 // reason for using deprecated symbol`. If the directive is no longer needed, remove it.
🟡 Warning gateway/session_manager.go:150
The comment `// value can be next from the` appears to be incomplete or is a leftover from editing. Unclear or partial comments can cause confusion for future maintainers.
💡 SuggestionPlease complete the comment to clarify its meaning or remove it if it is no longer relevant.

Powered by Visor from Probelabs

Last updated: 2026-02-10T14:52:09.976Z | Triggered by: pr_updated | Commit: ac0fc8b

💡 TIP: You can chat with Visor using /visor ask <your question>

@shults shults force-pushed the TT-6075-create-option-to-populate-the-x-ratelimit-headers-from-rate-limits-rather-than-quotas branch 2 times, most recently from bfa0891 to 6cdd71e Compare February 9, 2026 18:48
@shults
Copy link
Contributor Author

shults commented Feb 10, 2026

/visor Rate limit header should be returned only if 429 is blocked. Here is part of task: "When customers hit rate limits (HTTP 429), they need rate limit information to understand retry timing, but we are showing them quota data instead". Current module (SessionLimiter) needs deeper refactoring. Regarding time.Sleep inside of leacky bucket implementation. It is hidden behind the compilation flag and probably is not used in the production env. Another one ticket has to be created to resolve all those issues.

@probelabs
Copy link

probelabs bot commented Feb 10, 2026

Thanks for the clarification. The approach to return rate limit headers specifically on 429 Too Many Requests responses is clearly implemented.

The key change appears to be in the SessionLimiter, which now returns a sessionFailRateLimit struct containing the necessary context (limit, per, reset). This information is then used by the limitHeaderSender to populate the correct headers when a rate limit is exceeded.

Acknowledged on creating a follow-up ticket to address the deeper refactoring of SessionLimiter and the issues with the leaky bucket implementation, including the time.Sleep call.

References:


Powered by Visor from Probelabs

Last updated: 2026-02-10T11:04:02.933Z | Triggered by: shults | Commit: edb8c01

💡 TIP: You can chat with Visor using /visor ask <your question>

@shults shults force-pushed the TT-6075-create-option-to-populate-the-x-ratelimit-headers-from-rate-limits-rather-than-quotas branch from 99a4cb7 to 24c85a1 Compare February 10, 2026 13:21
@sonarqubecloud
Copy link

Quality Gate Passed Quality Gate passed

Issues
10 New issues
0 Accepted issues

Measures
0 Security Hotspots
80.2% Coverage on New Code
0.0% Duplication on New Code

See analysis details on SonarQube Cloud

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.

1 participant