Skip to content

fix http body parsing to respect Content-Length#733

Merged
matthyx merged 3 commits intomainfrom
fix/http-body-parsing
Mar 1, 2026
Merged

fix http body parsing to respect Content-Length#733
matthyx merged 3 commits intomainfrom
fix/http-body-parsing

Conversation

@YakirOren
Copy link
Contributor

@YakirOren YakirOren commented Mar 1, 2026

Truncate HTTP body to Content-Length in ParseHttpRequest and ParseHttpResponse to avoid BPF buffer garbage in parsed bodies.

Note: this only fixes the case where Content-Length is present. The proper fix is adding a buf_len field to the HTTP BPF gadget (syscall return value) so we can truncate at the source.

Summary by CodeRabbit

  • Bug Fixes

    • Request and response bodies now honor Content-Length and properly decode chunked Transfer-Encoding, trimming excess buffer data so payloads no longer include stray bytes.
  • Tests

    • Added comprehensive parsing tests that simulate fixed-size buffer garbage and cover requests/responses across Content-Length scenarios (zero, absent, truncated, oversized) and chunked decoding.

@YakirOren YakirOren requested review from matthyx and slashben March 1, 2026 16:24
@coderabbitai
Copy link

coderabbitai bot commented Mar 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Trim captured HTTP request and response bodies to explicit Content-Length when non-negative and smaller than the captured buffer; decode chunked Transfer-Encoding for both requests and responses, replace bodies accordingly, clear related headers, and add tests simulating BPF-style garbage buffers and multiple body scenarios.

Changes

Cohort / File(s) Summary
HTTP parsing logic
pkg/containerwatcher/v2/tracers/httpparse.go
Decode chunked request and response bodies via httputil.NewChunkedReader; when ContentLength >= 0 and smaller than captured bodyData, trim bodyData, set req/resp.Body and update ContentLength; clear Transfer-Encoding and related headers; response path replaces (and closes) previous resp.Body. Added net/http/httputil import.
Parsing tests
pkg/containerwatcher/v2/tracers/httpparse_test.go
New tests covering FromCString behavior, simulated 4096-byte BPF buffers with trailing garbage, request/response parsing for missing/zero/explicit Content-Length, chunked transfer decoding, and body extraction validations. Adds bpfBufSize and makeBPFBuffer helpers.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibble bytes and count each bite,

Chunked crumbs unrolled beneath the light.
When lengths are small I shear with care,
Tossing stray garbage into air.
Tests hop round — each body found just right.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix http body parsing to respect Content-Length' directly and clearly summarizes the main change: truncating HTTP body to Content-Length in ParseHttpRequest and ParseHttpResponse to avoid BPF buffer garbage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/http-body-parsing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/containerwatcher/v2/tracers/httpparse_test.go`:
- Around line 33-36: The test's expected body is wrong for the case with
"Content-Length: 0": update ParseHttpRequest to treat content-length zero as an
empty body by changing the conditional from "> 0" to ">= 0", and then change the
test case in httpparse_test.go (the case named "empty body with content-length
zero") to set expectedBody to the empty string ""; reference the
ParseHttpRequest function and the test case name to locate both spots to modify.
- Around line 87-90: The test case named "empty body with content-length zero"
in httpparse_test.go currently expects "GARBAGE" despite the response having
Content-Length: 0; update the test's expectedBody (and any related expected
payload fields) for that test case to an empty string to reflect that the parser
should return no body when Content-Length is 0 (locate the test case by the name
"empty body with content-length zero" in the table of cases).

In `@pkg/containerwatcher/v2/tracers/httpparse.go`:
- Around line 133-135: The response-body trimming logic ignores the
Content-Length==0 edge case; update the block around resp.ContentLength and
bodyData in ParseHttpResponse (the snippet using resp.ContentLength and
bodyData) to explicitly handle zero-length bodies: if resp.ContentLength == 0
set bodyData to an empty slice (or nil), and otherwise trim when
resp.ContentLength >= 0 and less than len(bodyData) (i.e. use resp.ContentLength
>= 0 in the comparison so zero is handled), ensuring negative (-1) unknown
lengths remain untouched.
- Around line 102-104: The current check uses "if req.ContentLength > 0 &&
req.ContentLength < int64(len(bodyData)) { bodyData =
bodyData[:req.ContentLength] }" which fails to handle an explicit
"Content-Length: 0" (req.ContentLength == 0) and leaves garbage in bodyData;
update the conditional to use >= (i.e. req.ContentLength >= 0 &&
req.ContentLength < int64(len(bodyData))) so when req.ContentLength == 0 you
truncate bodyData to zero length; locate the conditional that references
req.ContentLength and bodyData in httpparse.go and make the comparison change
ensuring the slice expression still uses the int conversion already present.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 107f7a4 and eb2c085.

📒 Files selected for processing (2)
  • pkg/containerwatcher/v2/tracers/httpparse.go
  • pkg/containerwatcher/v2/tracers/httpparse_test.go

@YakirOren YakirOren force-pushed the fix/http-body-parsing branch 3 times, most recently from 99d47e4 to ac5b68f Compare March 1, 2026 17:02
Signed-off-by: Yakir Oren <yakiroren@gmail.com>
@YakirOren YakirOren force-pushed the fix/http-body-parsing branch from ac5b68f to 98d12b9 Compare March 1, 2026 17:05
matthyx
matthyx previously approved these changes Mar 1, 2026
Copy link
Contributor

@matthyx matthyx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice test coverage

slashben
slashben previously approved these changes Mar 1, 2026
Signed-off-by: Ben <ben@armosec.io>
@slashben slashben dismissed stale reviews from matthyx and themself via 7d503b2 March 1, 2026 18:50
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/containerwatcher/v2/tracers/httpparse.go (1)

145-156: ⚠️ Potential issue | 🟡 Minor

Response path missing chunked Transfer-Encoding decoding.

ParseHttpRequest decodes chunked bodies (lines 107-115), but ParseHttpResponse does not. HTTP responses commonly use chunked encoding, especially for dynamic content. Without decoding, responses with Transfer-Encoding: chunked will have raw chunked-formatted data in the body (e.g., 5\r\nhello\r\n0\r\n\r\n).

Consider adding the same chunked decoding logic here for consistency:

Proposed fix
 	// Set body directly without re-reading.
 	// See ParseHttpRequest for why we need the Content-Length guard.
 	bodyData := data[headerEnd:]
+	if len(resp.TransferEncoding) > 0 && resp.TransferEncoding[0] == "chunked" {
+		decodedBody, err := io.ReadAll(httputil.NewChunkedReader(bytes.NewReader(bodyData)))
+		if err == nil {
+			bodyData = decodedBody
+		}
+	}
 	if resp.ContentLength >= 0 && resp.ContentLength < int64(len(bodyData)) {
 		bodyData = bodyData[:resp.ContentLength]
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/containerwatcher/v2/tracers/httpparse.go` around lines 145 - 156,
ParseHttpResponse currently slices bodyData and assigns it to resp.Body without
decoding chunked Transfer-Encoding, so responses with "Transfer-Encoding:
chunked" will retain raw chunked framing; update the block around resp, bodyData
(the code that currently does resp.Body =
io.NopCloser(bytes.NewReader(bodyData)), resp.ContentLength = ...,
resp.TransferEncoding = nil, resp.Header.Del(...)) to detect if
resp.Header.Get("Transfer-Encoding") contains "chunked" (or
resp.TransferEncoding includes "chunked") and decode the chunked stream into a
contiguous body (reuse the same chunked decoding logic used in ParseHttpRequest
lines ~107-115), then assign the decoded bytes to resp.Body, set
resp.ContentLength to the decoded length, clear Transfer-Encoding, and remove
the Content-Length header as done now; ensure the decoding handles
truncated/invalid chunks by falling back to the raw body or returning the
appropriate error consistent with ParseHttpRequest behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@pkg/containerwatcher/v2/tracers/httpparse.go`:
- Around line 145-156: ParseHttpResponse currently slices bodyData and assigns
it to resp.Body without decoding chunked Transfer-Encoding, so responses with
"Transfer-Encoding: chunked" will retain raw chunked framing; update the block
around resp, bodyData (the code that currently does resp.Body =
io.NopCloser(bytes.NewReader(bodyData)), resp.ContentLength = ...,
resp.TransferEncoding = nil, resp.Header.Del(...)) to detect if
resp.Header.Get("Transfer-Encoding") contains "chunked" (or
resp.TransferEncoding includes "chunked") and decode the chunked stream into a
contiguous body (reuse the same chunked decoding logic used in ParseHttpRequest
lines ~107-115), then assign the decoded bytes to resp.Body, set
resp.ContentLength to the decoded length, clear Transfer-Encoding, and remove
the Content-Length header as done now; ensure the decoding handles
truncated/invalid chunks by falling back to the raw body or returning the
appropriate error consistent with ParseHttpRequest behavior.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 99d47e4 and 7d503b2.

📒 Files selected for processing (2)
  • pkg/containerwatcher/v2/tracers/httpparse.go
  • pkg/containerwatcher/v2/tracers/httpparse_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/containerwatcher/v2/tracers/httpparse_test.go

Signed-off-by: Ben <ben@armosec.io>
Copy link
Contributor

@matthyx matthyx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perfect !

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
pkg/containerwatcher/v2/tracers/httpparse.go (2)

107-118: Silently ignoring chunked decoding errors may propagate malformed bodies.

When httputil.NewChunkedReader fails (e.g., BPF buffer truncated mid-chunk), the error is silently swallowed and the raw chunked-encoded data (with garbage) becomes the body. Downstream consumers would receive malformed data with no indication of failure.

Consider logging the error or returning it when decoding fails.

Proposed improvement to log decoding failures
 	if len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked" {
 		decodedBody, err := io.ReadAll(httputil.NewChunkedReader(bytes.NewReader(bodyData)))
 		if err == nil {
 			bodyData = decodedBody
 			req.TransferEncoding = nil
 			req.Header.Del("Transfer-Encoding")
 			req.Header.Del("Content-Length")
+		} else {
+			// Chunked decoding failed; body may be truncated or malformed.
+			// Fall back to using raw body data.
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/containerwatcher/v2/tracers/httpparse.go` around lines 107 - 118, The
chunked-decoding error is currently swallowed in the block that uses
httputil.NewChunkedReader, which can leave raw/malformed chunked data in
bodyData; update the error path in the chunked decoding branch (the code that
calls httputil.NewChunkedReader and io.ReadAll) to surface failures instead of
ignoring them: if io.ReadAll returns an error, either log the error with the
tracer/logger used in this package (include context: that chunked decode failed
for this request) or return the error to the caller so downstream consumers
don’t receive malformed bodyData; ensure you still only clear
req.TransferEncoding/headers when decoding succeeds and keep the subsequent
ContentLength truncation unchanged (refer to symbols req, bodyData,
req.TransferEncoding, httputil.NewChunkedReader, req.Header.Del and
req.ContentLength).

148-152: Same silent error handling concern for response chunked decoding.

Consistent with the request handling, consider logging when chunked decoding fails so operators can diagnose issues with truncated BPF captures.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/containerwatcher/v2/tracers/httpparse.go` around lines 148 - 152, The
chunked response decoding branch in tracers/httpparse.go currently swallows
errors when io.ReadAll(httputil.NewChunkedReader(bytes.NewReader(bodyData)))
fails; update that branch to log the error instead of ignoring it so operators
can diagnose truncated BPF captures — when err != nil, emit a clear log message
including resp (or resp.TransferEncoding) and the err (e.g., via the package
logger or log.Printf), while keeping the existing behavior of replacing bodyData
only on success.
pkg/containerwatcher/v2/tracers/httpparse_test.go (1)

76-91: Consider adding a test for truncated/malformed chunked data.

The chunked decoding tests all use well-formed chunked data. Given that BPF buffers may truncate mid-chunk, consider adding a test case where chunked encoding is incomplete (e.g., "4\r\nWi" without the terminating 0\r\n\r\n) to document the fallback behavior when decoding fails.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/containerwatcher/v2/tracers/httpparse_test.go` around lines 76 - 91, Add
a unit test that covers truncated/malformed chunked responses: create a dummy
request via ParseHttpRequest, build a BPF buffer with an incomplete chunked
response (e.g., "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nWi"
wrapped via makeBPFBuffer and FromCString), call ParseHttpResponse(cleaned,
dummyReq) and assert the observed fallback behavior (either require an error
from ParseHttpResponse or read resp.Body and assert it contains the expected
partial data like "Wi" and no panic). Name the test something like
TestBPFBufferGarbageResponse_Chunked_Truncated and reference ParseHttpRequest,
makeBPFBuffer, FromCString and ParseHttpResponse in the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/containerwatcher/v2/tracers/httpparse_test.go`:
- Around line 76-91: Add a unit test that covers truncated/malformed chunked
responses: create a dummy request via ParseHttpRequest, build a BPF buffer with
an incomplete chunked response (e.g., "HTTP/1.1 200 OK\r\nTransfer-Encoding:
chunked\r\n\r\n4\r\nWi" wrapped via makeBPFBuffer and FromCString), call
ParseHttpResponse(cleaned, dummyReq) and assert the observed fallback behavior
(either require an error from ParseHttpResponse or read resp.Body and assert it
contains the expected partial data like "Wi" and no panic). Name the test
something like TestBPFBufferGarbageResponse_Chunked_Truncated and reference
ParseHttpRequest, makeBPFBuffer, FromCString and ParseHttpResponse in the test.

In `@pkg/containerwatcher/v2/tracers/httpparse.go`:
- Around line 107-118: The chunked-decoding error is currently swallowed in the
block that uses httputil.NewChunkedReader, which can leave raw/malformed chunked
data in bodyData; update the error path in the chunked decoding branch (the code
that calls httputil.NewChunkedReader and io.ReadAll) to surface failures instead
of ignoring them: if io.ReadAll returns an error, either log the error with the
tracer/logger used in this package (include context: that chunked decode failed
for this request) or return the error to the caller so downstream consumers
don’t receive malformed bodyData; ensure you still only clear
req.TransferEncoding/headers when decoding succeeds and keep the subsequent
ContentLength truncation unchanged (refer to symbols req, bodyData,
req.TransferEncoding, httputil.NewChunkedReader, req.Header.Del and
req.ContentLength).
- Around line 148-152: The chunked response decoding branch in
tracers/httpparse.go currently swallows errors when
io.ReadAll(httputil.NewChunkedReader(bytes.NewReader(bodyData))) fails; update
that branch to log the error instead of ignoring it so operators can diagnose
truncated BPF captures — when err != nil, emit a clear log message including
resp (or resp.TransferEncoding) and the err (e.g., via the package logger or
log.Printf), while keeping the existing behavior of replacing bodyData only on
success.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d503b2 and 2d23122.

📒 Files selected for processing (2)
  • pkg/containerwatcher/v2/tracers/httpparse.go
  • pkg/containerwatcher/v2/tracers/httpparse_test.go

@slashben slashben added the release Create release label Mar 1, 2026
@matthyx matthyx merged commit 3e083ae into main Mar 1, 2026
27 checks passed
@matthyx matthyx deleted the fix/http-body-parsing branch March 1, 2026 19:38
@coderabbitai coderabbitai bot mentioned this pull request Mar 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release Create release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants