File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 11'use strict'
22
33const pump = require ( 'pump' )
4- const toArray = require ( 'stream-to-array' )
54const TRANSFER_ENCODING_HEADER_NAME = 'transfer-encoding'
65
6+ // Maximum size in bytes for buffered chunked responses (Connection: close)
7+ // Prevents memory exhaustion from unbounded upstream response bodies
8+ const MAX_BUFFER_SIZE = 1 * 1024 * 1024 // 1MB
9+
710module . exports = {
811 websocket : {
912 onOpenNoOp ( ws , searchParams ) { }
@@ -38,8 +41,22 @@ module.exports = {
3841 }
3942
4043 if ( ! stream . headers [ 'content-length' ] ) {
41- // pack all pieces into 1 buffer to calculate content length
42- const resBuffer = Buffer . concat ( await toArray ( stream ) )
44+ // Collect chunks with size limit to prevent OOM
45+ const chunks = [ ]
46+ let totalSize = 0
47+
48+ for await ( const chunk of stream ) {
49+ totalSize += chunk . length
50+ if ( totalSize > MAX_BUFFER_SIZE ) {
51+ stream . destroy ( )
52+ res . statusCode = 502
53+ res . end ( 'Response body exceeds maximum allowed size' )
54+ return
55+ }
56+ chunks . push ( chunk )
57+ }
58+
59+ const resBuffer = Buffer . concat ( chunks )
4360
4461 // add content-length header and send the merged response buffer
4562 res . setHeader ( 'content-length' , '' + Buffer . byteLength ( resBuffer ) )
Original file line number Diff line number Diff line change 2828 },
2929 "homepage" : " https://github.com/jkyberneees/fast-gateway#readme" ,
3030 "dependencies" : {
31- "fast-proxy-lite" : " ^1.1.2 " ,
31+ "fast-proxy-lite" : " ^1.1.3 " ,
3232 "http-cache-middleware" : " ^1.4.1" ,
3333 "micromatch" : " ^4.0.8" ,
3434 "restana" : " ^6.0.0" ,
4242 " LICENSE"
4343 ],
4444 "devDependencies" : {
45- "@types/node" : " ^25.7.0" ,
4645 "@types/express" : " ^5.0.6" ,
46+ "@types/node" : " ^25.7.0" ,
4747 "artillery" : " ^2.0.31" ,
4848 "aws-sdk" : " ^2.1693.0" ,
4949 "chai" : " ^6.2.2" ,
6464 "response-time" : " ^2.3.4" ,
6565 "supertest" : " ^7.2.2"
6666 }
67- }
67+ }
Original file line number Diff line number Diff line change @@ -31,6 +31,13 @@ describe('API Gateway', () => {
3131 res . write ( '1' )
3232 res . end ( )
3333 } )
34+ remote . get ( '/large-chunked' , ( req , res ) => {
35+ // Sends a 2MB chunked response to test buffer overflow protection
36+ const buf = Buffer . alloc ( 2 * 1024 * 1024 , 'x' )
37+ res . writeHead ( 200 , { 'content-type' : 'application/octet-stream' } )
38+ res . write ( buf )
39+ res . end ( )
40+ } )
3441 remote . get ( '/cache' , ( req , res ) => {
3542 res . setHeader ( 'x-cache-timeout' , '1 second' )
3643 res . send ( {
@@ -369,6 +376,20 @@ describe('API Gateway', () => {
369376 } )
370377 } )
371378
379+ it ( 'large chunked rejected with 502 when Connection close (buffer limit)' , async ( ) => {
380+ await request ( gateway )
381+ . get ( '/users/large-chunked' )
382+ . set ( { Connection : 'close' } )
383+ . expect ( 502 )
384+ } )
385+
386+ it ( 'large chunked streamed normally when Connection keep-alive' , async ( ) => {
387+ await request ( gateway )
388+ . get ( '/users/large-chunked' )
389+ . set ( 'Connection' , 'keep-alive' )
390+ . expect ( 200 )
391+ } )
392+
372393 it ( '(Should overwrite query string using req.query) GET /qs - 200' , async ( ) => {
373394 await request ( gateway )
374395 . get ( '/qs?name=nodejs&category=js' )
You can’t perform that action at this time.
0 commit comments