Skip to content

Commit ea92ee7

Browse files
ericfitzclaude
andcommitted
fix(api): remove root endpoint from IP rate limiting
The / health/info endpoint was included in IP-based rate limiting (10 req/min), causing 429s in cloud deployments where ALB health checks, kubelet probes, and frontend status polls all hit this endpoint. Discovery endpoints (/.well-known/*) remain rate-limited. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7b44dd2 commit ea92ee7

4 files changed

Lines changed: 14 additions & 28 deletions

File tree

.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"major": 1,
33
"minor": 2,
4-
"patch": 5,
4+
"patch": 6,
55
"prerelease": ""
66
}

api/rate_limit_middleware.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,11 @@ func RateLimitMiddleware(server *Server) gin.HandlerFunc {
8787
}
8888
}
8989

90-
// isPublicEndpoint checks if the path is a public discovery endpoint
90+
// isPublicEndpoint checks if the path is a public discovery endpoint.
91+
// Note: "/" (health/info) is intentionally excluded — it is hit by ALB health
92+
// checks, kubelet probes, and frontend status polls, so IP-based rate limiting
93+
// on it causes spurious 429s in cloud deployments.
9194
func isPublicEndpoint(path string) bool {
92-
// Exact matches for specific public paths
93-
exactMatches := []string{
94-
"/",
95-
}
96-
if slices.Contains(exactMatches, path) {
97-
return true
98-
}
99-
10095
// Prefix matches for public path prefixes
10196
prefixes := []string{
10297
"/.well-known/",

api/rate_limit_middleware_test.go

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,24 +47,15 @@ func TestRateLimitMiddleware(t *testing.T) {
4747

4848
router := gin.New()
4949
router.Use(RateLimitMiddleware(server))
50-
router.GET("/", func(c *gin.Context) {
51-
c.JSON(http.StatusOK, gin.H{"status": "ok"})
52-
})
5350
router.GET("/.well-known/openid-configuration", func(c *gin.Context) {
5451
c.JSON(http.StatusOK, gin.H{"status": "ok"})
5552
})
5653

57-
// Test root endpoint
58-
req := httptest.NewRequest("GET", "/", nil)
54+
// Test .well-known endpoint (public, should skip API rate limiting)
55+
req := httptest.NewRequest("GET", "/.well-known/openid-configuration", nil)
5956
w := httptest.NewRecorder()
6057
router.ServeHTTP(w, req)
6158
assert.Equal(t, http.StatusOK, w.Code)
62-
63-
// Test .well-known endpoint
64-
req = httptest.NewRequest("GET", "/.well-known/openid-configuration", nil)
65-
w = httptest.NewRecorder()
66-
router.ServeHTTP(w, req)
67-
assert.Equal(t, http.StatusOK, w.Code)
6859
})
6960

7061
t.Run("skips rate limiting for auth flow endpoints", func(t *testing.T) {
@@ -321,20 +312,20 @@ func TestIPRateLimitMiddleware(t *testing.T) {
321312

322313
router := gin.New()
323314
router.Use(IPRateLimitMiddleware(server))
324-
router.GET("/", func(c *gin.Context) {
315+
router.GET("/.well-known/openid-configuration", func(c *gin.Context) {
325316
c.JSON(http.StatusOK, gin.H{"status": "ok"})
326317
})
327318

328319
// Make 10 requests to exhaust the limit
329320
for i := range 10 {
330-
req := httptest.NewRequest("GET", "/", nil)
321+
req := httptest.NewRequest("GET", "/.well-known/openid-configuration", nil)
331322
w := httptest.NewRecorder()
332323
router.ServeHTTP(w, req)
333324
assert.Equal(t, http.StatusOK, w.Code, "Request %d should be allowed", i+1)
334325
}
335326

336327
// 11th request should be blocked
337-
req := httptest.NewRequest("GET", "/", nil)
328+
req := httptest.NewRequest("GET", "/.well-known/openid-configuration", nil)
338329
w := httptest.NewRecorder()
339330
router.ServeHTTP(w, req)
340331

@@ -353,12 +344,12 @@ func TestIPRateLimitMiddleware(t *testing.T) {
353344

354345
router := gin.New()
355346
router.Use(IPRateLimitMiddleware(server))
356-
router.GET("/", func(c *gin.Context) {
347+
router.GET("/.well-known/openid-configuration", func(c *gin.Context) {
357348
c.JSON(http.StatusOK, gin.H{"status": "ok"})
358349
})
359350

360351
// Request with X-Forwarded-For
361-
req := httptest.NewRequest("GET", "/", nil)
352+
req := httptest.NewRequest("GET", "/.well-known/openid-configuration", nil)
362353
req.Header.Set("X-Forwarded-For", "203.0.113.195, 70.41.3.18, 150.172.238.178")
363354
w := httptest.NewRecorder()
364355
router.ServeHTTP(w, req)
@@ -488,7 +479,7 @@ func TestIsPublicEndpoint(t *testing.T) {
488479
path string
489480
expected bool
490481
}{
491-
{"/", true},
482+
{"/", false}, // health/info endpoint excluded from IP rate limiting
492483
{"/.well-known/openid-configuration", true},
493484
{"/.well-known/jwks.json", true},
494485
{"/api/test", false},

api/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ var (
4646
// Minor version number
4747
VersionMinor = "2"
4848
// Patch version number
49-
VersionPatch = "5"
49+
VersionPatch = "6"
5050
// VersionPreRelease is the pre-release label (e.g., "rc.0", "beta.1"), empty for stable releases
5151
VersionPreRelease = ""
5252
// GitCommit is the git commit hash from build

0 commit comments

Comments
 (0)