From 0c71c8e9221444c50aec6e5823f66a4e3a47ca7f Mon Sep 17 00:00:00 2001 From: Vedhant26 Date: Sat, 30 May 2026 21:21:48 +0530 Subject: [PATCH] fix(security): add rate limiting to /api/extract to prevent API key exhaustion --- package-lock.json | 30 +++++++++++++++++++++++++++++- package.json | 1 + server.js | 11 ++++++++++- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5652882..50cd810 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,10 +13,11 @@ "cors": "^2.8.6", "dotenv": "^17.4.2", "express": "^5.2.1", + "express-rate-limit": "^8.5.2", "sqlite3": "^6.0.1" }, "engines": { - "node": "20.x" + "node": ">=20.x" } }, "node_modules/@google/genai": { @@ -612,6 +613,24 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-rate-limit": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", + "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -952,6 +971,15 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", diff --git a/package.json b/package.json index 37df9f0..1cf6683 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "cors": "^2.8.6", "dotenv": "^17.4.2", "express": "^5.2.1", + "express-rate-limit": "^8.5.2", "sqlite3": "^6.0.1" }, "engines": { diff --git a/server.js b/server.js index 47e1429..311268b 100644 --- a/server.js +++ b/server.js @@ -5,6 +5,7 @@ const { db, initDb } = require('./database'); const { GoogleGenAI } = require('@google/genai'); const path = require('path'); const csvDownloadRouter = require('./backend/routers/csvDownload.router.js'); +const rateLimit = require('express-rate-limit'); const app = express(); app.use(cors()); @@ -474,7 +475,15 @@ app.delete('/api/tasks/:id', (req, res) => { }); // ================= AI EXTRACTION ================= -app.post('/api/extract', async (req, res) => { +const extractLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 20, // limit each IP to 20 requests per windowMs + message: { error: 'Too many extraction requests from this IP, please try again after 15 minutes' }, + standardHeaders: true, + legacyHeaders: false, +}); + +app.post('/api/extract', extractLimiter, async (req, res) => { const { text } = req.body; if (!text) return res.status(400).json({ error: 'Text is required' });