diff --git a/lib/security/rate-limiter.ts b/lib/security/rate-limiter.ts index 1eef66b13..7bf60b75d 100644 --- a/lib/security/rate-limiter.ts +++ b/lib/security/rate-limiter.ts @@ -80,6 +80,9 @@ export const rateLimiters = { // 文件上传接口:每分钟60次请求(独立于通用API限流) upload: new RateLimiter(60000, 60), + // 图片代理接口:每分钟3000次请求(图片列表/画布高频加载) + imageProxy: new RateLimiter(60000, 3000), + // 高频只读接口(首页/画布轮询):每分钟1200次请求 uiPolling: new RateLimiter(60000, 1200), diff --git a/proxy.ts b/proxy.ts index 550865bf3..384db6ee8 100644 --- a/proxy.ts +++ b/proxy.ts @@ -77,6 +77,20 @@ export async function proxy(request: NextRequest) { { status: 429 } ); } + } else if (pathname === "/api/image-proxy" && request.method === "GET") { + const imageProxyRateLimit = checkRateLimit(request, rateLimiters.imageProxy); + if (!imageProxyRateLimit.allowed) { + const retryAfter = Math.max(1, Math.ceil((imageProxyRateLimit.resetTime - Date.now()) / 1000)); + console.warn(`[Security] Image proxy rate limit exceeded for IP: ${clientIP}`, { + pathname, + method: request.method, + resetTime: new Date(imageProxyRateLimit.resetTime).toISOString(), + }); + return NextResponse.json( + { error: "Too many image proxy requests", retryAfter }, + { status: 429, headers: { "Retry-After": String(retryAfter) } } + ); + } } else if (pathname === "/api/files/upload" && request.method === "POST") { const token = await resolveToken(); const userId =