Skip to content

Commit a73ebbc

Browse files
GeneralJerelclaude
andcommitted
feat: add per-IP rate limiting to /api/copilotkit endpoint
Sliding-window rate limiter (20 req/min per IP) to prevent individual abuse of the public CopilotKit endpoint. In-memory with periodic cleanup to prevent unbounded Map growth. No new dependencies. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b811fa1 commit a73ebbc

1 file changed

Lines changed: 28 additions & 0 deletions

File tree

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

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

9+
// Simple sliding-window rate limiter (per IP)
10+
const RATE_LIMIT_WINDOW_MS = 60_000; // 1 minute
11+
const RATE_LIMIT_MAX = 20; // max requests per window
12+
const hits = new Map<string, number[]>();
13+
14+
function isRateLimited(ip: string): boolean {
15+
const now = Date.now();
16+
const timestamps = hits.get(ip)?.filter(t => t > now - RATE_LIMIT_WINDOW_MS) ?? [];
17+
timestamps.push(now);
18+
hits.set(ip, timestamps);
19+
return timestamps.length > RATE_LIMIT_MAX;
20+
}
21+
22+
// 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);
31+
932
// Normalize Render's fromService hostport (bare host:port) into a full URL
1033
const raw = process.env.LANGGRAPH_DEPLOYMENT_URL;
1134
const deploymentUrl = !raw
@@ -23,6 +46,11 @@ const defaultAgent = new LangGraphAgent({
2346

2447
// 3. Define the route and CopilotRuntime for the agent
2548
export const POST = async (req: NextRequest) => {
49+
const ip = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? "unknown";
50+
if (isRateLimited(ip)) {
51+
return new Response("Too many requests", { status: 429 });
52+
}
53+
2654
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
2755
endpoint: "/api/copilotkit",
2856
serviceAdapter: new ExperimentalEmptyAdapter(),

0 commit comments

Comments
 (0)