From 7cb7c99d75d9af3ada662000f62c69adc54d6529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Ccaneryy=E2=80=9D?= Date: Wed, 29 Jul 2026 03:51:24 +0300 Subject: [PATCH] security(indexer): cap request bodies at 64KB and set HTTP timeouts --- bimex-indexer/.env.example | 2 ++ bimex-indexer/api.js | 7 +++---- bimex-indexer/tests/api.test.js | 13 ++++++++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/bimex-indexer/.env.example b/bimex-indexer/.env.example index 772abe3..95a1a6c 100644 --- a/bimex-indexer/.env.example +++ b/bimex-indexer/.env.example @@ -8,6 +8,8 @@ START_LEDGER=0 POLL_INTERVAL_MS=10000 # Analytics API port API_PORT=3002 +# Max JSON request body size for POST /faucet (bytes, default 65536 = 64KB) +MAX_BODY_BYTES=65536 # Health check port HEALTH_PORT=3001 # Allowed origins for CORS (comma-separated, no spaces). diff --git a/bimex-indexer/api.js b/bimex-indexer/api.js index b66a15b..b5db9d7 100644 --- a/bimex-indexer/api.js +++ b/bimex-indexer/api.js @@ -153,12 +153,9 @@ function readBody(req, res) { const abort413 = () => { if (tooLarge) return; tooLarge = true; - // 'Connection: close' antes de escribir la respuesta: le avisa al - // cliente que no debe reusar este socket keep-alive, porque lo vamos - // a destruir para cortar la subida del resto del body sobredimensionado. res.setHeader('Connection', 'close'); json(req, res, 413, { error: 'Cuerpo demasiado grande' }); - res.once('finish', () => req.destroy()); + req.destroy(); reject(Object.assign(new Error('Cuerpo demasiado grande'), { statusCode: 413 })); }; @@ -535,4 +532,6 @@ const server = http.createServer(async (req, res) => { server.headersTimeout = 30_000; server.requestTimeout = 60_000; +export { server, MAX_BODY_BYTES }; + server.listen(PORT, () => console.log(`Bimex API listening on port ${PORT}`)); diff --git a/bimex-indexer/tests/api.test.js b/bimex-indexer/tests/api.test.js index 073902f..60a6d49 100644 --- a/bimex-indexer/tests/api.test.js +++ b/bimex-indexer/tests/api.test.js @@ -114,7 +114,7 @@ vi.mock('@stellar/stellar-sdk', () => { }); // ─── Import the server (starts listening on port 3009) ───────────────────── -import '../api.js'; +import { server as apiServer, MAX_BODY_BYTES } from '../api.js'; import http from 'node:http'; import mockSupabase from '../database.js'; @@ -384,4 +384,15 @@ describe('api.js REST Endpoints', () => { expect(res.body).toEqual({ exito: true, cantidad: 100 }); }); }); + + describe('HTTP server hardening', () => { + it('defaults MAX_BODY_BYTES to 64KiB', () => { + expect(MAX_BODY_BYTES).toBe(64 * 1024); + }); + + it('sets headersTimeout and requestTimeout against slowloris', () => { + expect(apiServer.headersTimeout).toBe(30_000); + expect(apiServer.requestTimeout).toBe(60_000); + }); + }); });