the universal sandbox runtime for agents and humans.
pocketenv.io
sandbox
openclaw
agent
claude-code
vercel-sandbox
deno-sandbox
cloudflare-sandbox
atproto
sprites
daytona
1import type { RequestHandler } from "express";
2
3interface RateLimiterOptions {
4 windowMs?: number;
5 max?: number;
6 keyPrefix?: string;
7}
8
9export function createRateLimiter(
10 options: RateLimiterOptions = {},
11): RequestHandler {
12 const windowMs = options.windowMs ?? 60_000; // 1 minute
13 const max = options.max ?? 100;
14 const keyPrefix = options.keyPrefix ?? "rl";
15
16 return async (req, res, next) => {
17 const ip =
18 (req.headers["x-forwarded-for"] as string | undefined)
19 ?.split(",")[0]
20 ?.trim() ??
21 req.socket.remoteAddress ??
22 "unknown";
23
24 const key = `${keyPrefix}:${ip}`;
25 const now = Date.now();
26 const windowStart = now - windowMs;
27
28 try {
29 const redis = req.ctx.redis;
30
31 const [, , count] = (await redis
32 .multi()
33 .zRemRangeByScore(key, 0, windowStart)
34 .zAdd(key, { score: now, value: `${now}:${Math.random()}` })
35 .zCard(key)
36 .pExpire(key, windowMs)
37 .exec()) as [unknown, unknown, number, unknown];
38
39 const remaining = Math.max(0, max - count);
40
41 res.setHeader("X-RateLimit-Limit", max);
42 res.setHeader("X-RateLimit-Remaining", remaining);
43 res.setHeader("X-RateLimit-Reset", Math.ceil((now + windowMs) / 1000));
44
45 if (count > max) {
46 res.setHeader("Retry-After", Math.ceil(windowMs / 1000));
47 res
48 .status(429)
49 .json({ error: "Too many requests, please try again later." });
50 return;
51 }
52
53 next();
54 } catch {
55 // Fail open — don't block traffic if Redis is unavailable
56 next();
57 }
58 };
59}