From f900f8a68e674fbf95e557bdcf8232d282f4b501 Mon Sep 17 00:00:00 2001 From: wiaamm Date: Sat, 29 Nov 2025 22:08:38 +0200 Subject: [PATCH] try chunk hashing --- .../open-appsec-waf-kong-plugin/handler.lua | 79 +++++++++---------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/attachments/kong/plugins/open-appsec-waf-kong-plugin/handler.lua b/attachments/kong/plugins/open-appsec-waf-kong-plugin/handler.lua index 4fa3bdc..2db3b80 100755 --- a/attachments/kong/plugins/open-appsec-waf-kong-plugin/handler.lua +++ b/attachments/kong/plugins/open-appsec-waf-kong-plugin/handler.lua @@ -185,41 +185,42 @@ function NanoHandler.body_filter(conf) -- Initialize chunk counter and timeout tracking on first call if not ctx.body_buffer_chunk then ctx.body_buffer_chunk = 0 - ctx.body_filter_start_time = ngx.now() * 1000 -- Current time in milliseconds + ctx.body_filter_start_time = ngx.now() * 1000 ctx.body_filter_timeout = false + ctx.failed_nano_send = false + ctx.num_body_filter_calls = 0 + ctx.last_chunk_hash = nil end + + ctx.num_body_filter_calls = ctx.num_body_filter_calls + 1 - -- Check if we've exceeded 150ms timeout - local elapsed_time = (ngx.now() * 1000) - ctx.body_filter_start_time - if elapsed_time > 150000 then - if not ctx.body_filter_timeout then - ctx.body_filter_timeout = true - kong.log.warn("body_filter timeout exceeded (150ms), failing open - no more chunks sent to nano-agent") - end - -- Fail-open: pass through remaining chunks without inspection + if ctx.failed_nano_send then + return + end + + local current_time = ngx.now() * 1000 + if current_time - ctx.body_filter_start_time > 150000 then + if not ctx.body_filter_timeout then + ctx.body_filter_timeout = true + ctx.failed_nano_send = true + kong.log.warn("body_filter timeout exceeded (2.5 minutes), failing open") + end return end - -- Get the current chunk from ngx.arg[1] (this is how Kong streams body data) local chunk = ngx.arg[1] - local eof = ngx.arg[2] -- true on last chunk - - -- Try Kong API for small in-memory bodies (backward compatibility) + local eof = ngx.arg[2] local full_body = kong.response.get_raw_body() - - -- Determine if we're dealing with a full body or streaming chunks local is_streaming = (full_body == nil and chunk ~= nil) - -- If no body content at all (no full_body, no chunk, and no EOF), just return - -- This prevents sending empty traffic to nano-agent if not full_body and not chunk and not eof then return end + kong.log.debug("body_filter call #", ctx.num_body_filter_calls, ", sent: ", ctx.body_buffer_chunk, " chunks") + if full_body and not ctx.body_seen then - -- Small response body - use Kong API (original behavior) ctx.body_seen = true - kong.log.debug("Processing in-memory response body, size: ", #full_body) local ok, result = pcall(function() return {nano.send_body(session_id, session_data, full_body, nano.HttpChunkType.HTTP_RESPONSE_BODY)} @@ -247,57 +248,55 @@ function NanoHandler.body_filter(conf) else kong.log.warn("nano.send_body failed for in-memory body: ", result) ctx.body_buffer_chunk = ctx.body_buffer_chunk + 1 + ctx.failed_nano_send = true end elseif is_streaming and chunk and type(chunk) == "string" and #chunk > 0 then - -- Large response body - streaming chunks (file-buffered or large in-memory) ctx.body_seen = true - local chunk_size = #chunk - kong.log.debug("Processing response body chunk #", ctx.body_buffer_chunk, ", size: ", chunk_size, ", eof: ", eof) + local chunk_hash = ngx.crc32_long(chunk) + + if chunk_hash == ctx.last_chunk_hash then + kong.log.debug("Duplicate chunk detected (same hash as previous), skipping") + return + end local ok, result = pcall(function() return {nano.send_body(session_id, session_data, chunk, nano.HttpChunkType.HTTP_RESPONSE_BODY)} end) if ok and result and result[1] then + ctx.last_chunk_hash = chunk_hash + ctx.body_buffer_chunk = ctx.body_buffer_chunk + 1 + local verdict = result[1] local response = result[2] local modifications = result[3] - -- Apply modifications to this chunk if modifications then - chunk = nano.handle_body_modifications(chunk, modifications, ctx.body_buffer_chunk) + chunk = nano.handle_body_modifications(chunk, modifications, ctx.body_buffer_chunk - 1) end - ctx.body_buffer_chunk = ctx.body_buffer_chunk + 1 - if verdict == nano.AttachmentVerdict.DROP then nano.fini_session(session_data) ctx.session_finalized = true - ngx.arg[1] = nil -- Clear the output - ngx.arg[2] = true -- Force EOF + ngx.arg[1] = nil + ngx.arg[2] = true local custom_result = nano.handle_custom_response(session_data, response) nano.cleanup_all() return custom_result end - -- Update the chunk that will be sent to client (CRITICAL for streaming) ngx.arg[1] = chunk else - kong.log.warn("nano.send_body failed for chunk #", ctx.body_buffer_chunk, ": ", result) + kong.log.warn("nano.send_body failed for chunk #", ctx.body_buffer_chunk + 1, ": ", result) ctx.body_buffer_chunk = ctx.body_buffer_chunk + 1 - -- Fail-open: pass chunk through unmodified - -- ngx.arg[1] is already set to the original chunk + ctx.failed_nano_send = true end end - -- Finalize session on last chunk (EOF) or when no body expected if eof or (ctx.expect_body == false and not ctx.body_seen) then - kong.log.debug("Finalizing response inspection, body_seen: ", ctx.body_seen, ", eof: ", eof, ", timeout: ", ctx.body_filter_timeout) - - -- Only send end_inspection if we haven't timed out - if not ctx.body_filter_timeout then + if not ctx.body_filter_timeout and not ctx.failed_nano_send then local ok, result = pcall(function() return {nano.end_inspection(session_id, session_data, nano.HttpChunkType.HTTP_RESPONSE_END)} end) @@ -309,8 +308,8 @@ function NanoHandler.body_filter(conf) if verdict == nano.AttachmentVerdict.DROP then nano.fini_session(session_data) ctx.session_finalized = true - ngx.arg[1] = nil -- Clear any remaining output - ngx.arg[2] = true -- Force EOF + ngx.arg[1] = nil + ngx.arg[2] = true local custom_result = nano.handle_custom_response(session_data, response) nano.cleanup_all() return custom_result @@ -318,8 +317,6 @@ function NanoHandler.body_filter(conf) else kong.log.warn("nano.end_inspection failed: ", result) end - else - kong.log.debug("Skipping end_inspection due to timeout - failing open") end nano.fini_session(session_data)