Skip to content

Commit e45e254

Browse files
committed
fix: feature-flag in-memory rate limiter, disable by default
In-memory rate limiting doesn't scale across multiple instances for high-traffic deployments. Disable by default via RATE_LIMIT_ENABLED env var so it doesn't silently misbehave at scale. Can be re-enabled for single-instance or low-traffic deployments.
1 parent 66603a0 commit e45e254

File tree

2 files changed

+18
-11
lines changed

2 files changed

+18
-11
lines changed

.env.example

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ OPENAI_API_KEY=
44
# Recommended: gpt-5.4, gpt-5.4-pro, claude-opus-4-6, gemini-3.1-pro
55
LLM_MODEL=gpt-5.4-2026-03-05
66

7-
# Rate limiting (per IP)
7+
# Rate limiting (per IP) — disabled by default
8+
RATE_LIMIT_ENABLED=false
89
RATE_LIMIT_WINDOW_MS=60000
9-
RATE_LIMIT_MAX=40
10+
RATE_LIMIT_MAX=40

apps/app/src/app/api/copilotkit/route.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ import {
66
import { LangGraphAgent } from "@copilotkit/runtime/langgraph";
77
import { NextRequest } from "next/server";
88

9-
// Simple sliding-window rate limiter (per IP)
9+
// Simple in-memory sliding-window rate limiter (per IP)
10+
// Enable via RATE_LIMIT_ENABLED=true — off by default.
11+
// For high-traffic deployments, consider Redis-backed rate limiting instead.
12+
const RATE_LIMIT_ENABLED = process.env.RATE_LIMIT_ENABLED === "true";
1013
const RATE_LIMIT_WINDOW_MS = Number(process.env.RATE_LIMIT_WINDOW_MS) || 60_000;
1114
const RATE_LIMIT_MAX = Number(process.env.RATE_LIMIT_MAX) || 40;
1215
const hits = new Map<string, number[]>();
1316

1417
function isRateLimited(ip: string): boolean {
18+
if (!RATE_LIMIT_ENABLED) return false;
1519
const now = Date.now();
1620
const timestamps = hits.get(ip)?.filter(t => t > now - RATE_LIMIT_WINDOW_MS) ?? [];
1721
timestamps.push(now);
@@ -20,14 +24,16 @@ function isRateLimited(ip: string): boolean {
2024
}
2125

2226
// Prune stale entries every 5 min to prevent unbounded memory growth
23-
setInterval(() => {
24-
const cutoff = Date.now() - RATE_LIMIT_WINDOW_MS;
25-
hits.forEach((timestamps, ip) => {
26-
const recent = timestamps.filter(t => t > cutoff);
27-
if (recent.length === 0) hits.delete(ip);
28-
else hits.set(ip, recent);
29-
});
30-
}, 300_000);
27+
if (RATE_LIMIT_ENABLED) {
28+
setInterval(() => {
29+
const cutoff = Date.now() - RATE_LIMIT_WINDOW_MS;
30+
hits.forEach((timestamps, ip) => {
31+
const recent = timestamps.filter(t => t > cutoff);
32+
if (recent.length === 0) hits.delete(ip);
33+
else hits.set(ip, recent);
34+
});
35+
}, 300_000);
36+
}
3137

3238
// Normalize Render's fromService hostport (bare host:port) into a full URL
3339
const raw = process.env.LANGGRAPH_DEPLOYMENT_URL;

0 commit comments

Comments
 (0)