Skip to content

feat(api): return 1 on disruption, add bulk headers, add coraza_free_string#94

Merged
fzipi merged 9 commits intocorazawaf:mainfrom
ppomes:feat/api-ergonomics
Apr 24, 2026
Merged

feat(api): return 1 on disruption, add bulk headers, add coraza_free_string#94
fzipi merged 9 commits intocorazawaf:mainfrom
ppomes:feat/api-ergonomics

Conversation

@ppomes
Copy link
Copy Markdown
Member

@ppomes ppomes commented Apr 22, 2026

Summary

Implements the three API improvements from #93:

  1. Interruption signalingcoraza_process_request_headers, coraza_process_request_body, coraza_process_response_headers, coraza_process_response_body now return 1 when interrupted, -1 on error, 0 on success. Callers can skip subsequent phases without polling coraza_intervention() on every phase.

  2. Bulk header APIcoraza_add_request_headers() and coraza_add_response_headers() accept a packed buffer ([name_len u16][name_bytes][value_len u32][value_bytes] × count) for single-call header injection.

  3. coraza_free_string() — safe deallocation for strings returned by libcoraza (e.g. coraza_matched_rule_get_error_log), avoiding allocator mismatch on Windows.

Ref #93

cc @jptosso @fzipi

Summary by CodeRabbit

  • New Features

    • Bulk-add headers for request and response flows
    • Library helper to free strings allocated by the library
  • Improvements

    • Standardized processing return codes for error, interruption, and success
  • Tests

    • Example tests updated to accept non-negative processing return values and to expect interruption results where applicable

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 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

Standardizes C API result codes (error/ok/interruption), changes processing functions to return them, adds bulk header-import functions that decode length-prefixed packed buffers with bounds checks, and exposes a string-free helper. Example tests updated to align with new return semantics.

Changes

Cohort / File(s) Summary
Core C API & implementation
libcoraza/coraza.go
Add exported enum coraza_result_t (CORAZA_ERROR=-1, CORAZA_OK=0, CORAZA_INTERRUPTION=1); change coraza_process_request_* and coraza_process_response_* to return these codes (error/interruption/ok semantics); add coraza_add_request_headers and coraza_add_response_headers to decode and bulk-add length-prefixed headers with bounds validation; add coraza_free_string.
SWIG interface
coraza.i
Declare coraza_result_t, coraza_add_request_headers, coraza_add_response_headers, and coraza_free_string (with coraza_free_string marked %ignore for wrapping).
Examples / Tests (Java)
examples/java/SimpleGet.java
Update assertions: expect interruption (1) for request-headers processing; relax other processing assertions to accept non-negative results (ret >= 0).
Examples / Tests (Python)
examples/python/simple_get.py
Same test adjustments as Java: expect 1 for request headers interruption; accept ret >= 0 for other processing calls; update messages accordingly.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hop through packed headers, byte by byte,
count and length checked before the light.
-1 for trouble, 1 when alarm bells ring,
0 when calm returns — I tidy everything. 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately summarizes the three main changes: return 1 on disruption, add bulk headers (coraza_add_request_headers/coraza_add_response_headers), and add coraza_free_string.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown

@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: 3

🧹 Nitpick comments (1)
libcoraza/coraza.go (1)

357-389: Duplicate decode loop — extract a shared helper.

coraza_add_request_headers and coraza_add_response_headers differ only in the AddRequestHeader vs AddResponseHeader call. Extract the decode/iteration into a small helper that yields (name, value) (or takes a callback), so any fix (e.g., the uint32 overflow above) only needs to be made once.

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

In `@libcoraza/coraza.go` around lines 357 - 389, Both coraza_add_request_headers
and coraza_add_response_headers share the same decode loop; extract that logic
into a single helper (e.g., iteratePackedHeaders or decodePackedHeaders) that
accepts the packed buffer and count and either returns a slice of (name,value)
pairs or accepts a callback func(name, value string). Replace the duplicate
loops in coraza_add_request_headers and coraza_add_response_headers with calls
to this helper and call tx.AddRequestHeader / tx.AddResponseHeader inside the
callback; ensure the helper centralizes all bounds checks and uses safe
unsigned-to-int conversions (avoid uint32 overflow by validating lengths before
shifting/combining) so future fixes apply once.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@libcoraza/coraza.go`:
- Around line 288-290: Add defensive validation at the top of
coraza_add_request_headers and coraza_add_response_headers: check if packed_len
< 0 or count < 0 and immediately return C.int(-1); additionally if packed == nil
&& packed_len > 0 return C.int(-1) to avoid dereferencing a null pointer; only
call C.GoBytes(unsafe.Pointer(packed), packed_len) after these validations pass
so you never pass a negative length or a nil pointer to C.GoBytes.
- Around line 288-316: The value length decoding in coraza_add_request_headers
(and similarly in coraza_add_response_headers) uses int shifts which can
overflow on 32-bit platforms; change the decode to read valueLen as a uint32
(e.g. valueLenU := uint32(buf[off])<<24 | uint32(buf[off+1])<<16 |
uint32(buf[off+2])<<8 | uint32(buf[off+3])), perform bounds checks using that
uint32 (ensure off and nameLen are converted and compared safely), verify off +
int(valueLenU) does not exceed len(buf) and only then convert valueLenU to int
for slicing; update all corresponding checks and conversions in both
coraza_add_request_headers and coraza_add_response_headers to avoid signed
overflow panics.
- Around line 500-507: Add extern declarations for the three new exported
functions so SWIG knows about them: add extern prototypes for
coraza_add_request_headers, coraza_add_response_headers, and coraza_free_string
to the coraza.i function declarations section (matching the //export lines in
coraza.go). After adding them, run the sync check (make check-swig-sync) to
verify. Optional: in the coraza_free_string implementation consider guarding
against nil (mirror the internal freeString helper) before calling C.free to be
consistent.

---

Nitpick comments:
In `@libcoraza/coraza.go`:
- Around line 357-389: Both coraza_add_request_headers and
coraza_add_response_headers share the same decode loop; extract that logic into
a single helper (e.g., iteratePackedHeaders or decodePackedHeaders) that accepts
the packed buffer and count and either returns a slice of (name,value) pairs or
accepts a callback func(name, value string). Replace the duplicate loops in
coraza_add_request_headers and coraza_add_response_headers with calls to this
helper and call tx.AddRequestHeader / tx.AddResponseHeader inside the callback;
ensure the helper centralizes all bounds checks and uses safe unsigned-to-int
conversions (avoid uint32 overflow by validating lengths before
shifting/combining) so future fixes apply once.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d09b8753-b618-4ed2-9b89-a941d77dc204

📥 Commits

Reviewing files that changed from the base of the PR and between dc37de6 and e9bd453.

📒 Files selected for processing (1)
  • libcoraza/coraza.go

Comment thread libcoraza/coraza.go
Comment thread libcoraza/coraza.go
Comment thread libcoraza/coraza.go
@ppomes
Copy link
Copy Markdown
Member Author

ppomes commented Apr 22, 2026

Tested locally with the full CRS 4.x regression suite using the coraza_httpd server from PR #92: all 4721 tests pass (920 excluded, same as caddy).

@fzipi
Copy link
Copy Markdown
Member

fzipi commented Apr 22, 2026

Can we use an enum instead? E.g. LIBCORAZA_ERROR = -1, LIBCORAZA_OK = 0, LIBCORAZA_INTERRUPTION = 1 ?

@ppomes
Copy link
Copy Markdown
Member Author

ppomes commented Apr 23, 2026

Done — added coraza_result_t enum with CORAZA_ERROR, CORAZA_OK, CORAZA_INTERRUPTION. Phase functions and bulk header functions use it. Also added to coraza.i for SWIG.

Copy link
Copy Markdown

@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: 3

♻️ Duplicate comments (1)
libcoraza/coraza.go (1)

294-321: ⚠️ Potential issue | 🟠 Major

Finish hardening the packed-buffer bounds checks.

Two crash paths remain: packed == nil && packed_len > 0 still reaches C.GoBytes, and int(uint32(...)) can wrap negative on 32-bit builds before the bounds check. This is the remaining unresolved part of the earlier bulk-header validation finding.

Proposed fix for both parsers
 func coraza_add_request_headers(t C.coraza_transaction_t, packed *C.char, packed_len C.int, count C.int) C.int {
 	if packed_len < 0 || count < 0 {
 		return -1
 	}
+	if packed == nil && packed_len > 0 {
+		return -1
+	}
 	tx := fromRaw[types.Transaction](t)
 	buf := C.GoBytes(unsafe.Pointer(packed), packed_len)
@@
-		valueLen := int(uint32(buf[off])<<24 | uint32(buf[off+1])<<16 | uint32(buf[off+2])<<8 | uint32(buf[off+3]))
+		valueLenU := uint32(buf[off])<<24 | uint32(buf[off+1])<<16 | uint32(buf[off+2])<<8 | uint32(buf[off+3])
 		off += 4
-		if off+valueLen > len(buf) {
+		if uint64(valueLenU) > uint64(len(buf)-off) {
 			return -1
 		}
+		valueLen := int(valueLenU)
 		value := string(buf[off : off+valueLen])
@@
 func coraza_add_response_headers(t C.coraza_transaction_t, packed *C.char, packed_len C.int, count C.int) C.int {
 	if packed_len < 0 || count < 0 {
 		return -1
 	}
+	if packed == nil && packed_len > 0 {
+		return -1
+	}
 	tx := fromRaw[types.Transaction](t)
 	buf := C.GoBytes(unsafe.Pointer(packed), packed_len)
@@
-		valueLen := int(uint32(buf[off])<<24 | uint32(buf[off+1])<<16 | uint32(buf[off+2])<<8 | uint32(buf[off+3]))
+		valueLenU := uint32(buf[off])<<24 | uint32(buf[off+1])<<16 | uint32(buf[off+2])<<8 | uint32(buf[off+3])
 		off += 4
-		if off+valueLen > len(buf) {
+		if uint64(valueLenU) > uint64(len(buf)-off) {
 			return -1
 		}
+		valueLen := int(valueLenU)
 		value := string(buf[off : off+valueLen])

Verify both parsers have the nil guard and avoid converting uint32 lengths to int before range checks:

#!/bin/bash
# Description: Inspect the bulk header parsers for nil guards and safe uint32 length checks.
set -euo pipefail

rg -n -C4 'func coraza_add_(request|response)_headers|packed == nil|C\.GoBytes\(unsafe\.Pointer\(packed\), packed_len\)|valueLenU|int\(uint32\(buf\[off\]\)<<24' libcoraza/coraza.go

Also applies to: 370-397

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

In `@libcoraza/coraza.go` around lines 294 - 321, The request parser
coraza_add_request_headers (and the sibling coraza_add_response_headers) must
add a nil check for packed before calling C.GoBytes and must stop converting
big-endian uint32 length fields to int before validating bounds; read the 4-byte
length into a uint32 (e.g. valueLenU), check that off+int(valueLenU) (or compare
remaining bytes as uint32) is within buf length, then convert to int for
slicing; likewise handle the 2-byte nameLen as uint16-first if not already, and
ensure the initial guard covers the case packed == nil && packed_len > 0 to
return -1 before C.GoBytes is invoked.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@coraza.i`:
- Line 416: The extern deallocator coraza_free_string(char *s) must not be
exposed to SWIG; add a SWIG ignore directive for coraza_free_string so bindings
are not generated for that function (e.g., add "%ignore coraza_free_string;" in
the interface before or above the extern declaration) to prevent managed
languages from calling the raw C deallocator and causing double-free/memory
corruption; ensure no other wrapper or %newobject is applied to
coraza_free_string.

In `@examples/java/SimpleGet.java`:
- Around line 105-107: The current check uses check(ret >= 0, ...) which allows
a returned 0 to pass and masks regressions; instead assert the explicit
interruption return code from coraza_process_request_headers (it returns 1 when
interrupted by a deny rule). Replace the loose check with a precise assertion
that ret == 1 (or otherwise explicitly handle the non-interruption case) for the
coraza.coraza_process_request_headers(tx) call, updating the error message to
mention the unexpected interruption return value; reference the symbols
coraza_process_request_headers and tx when locating the code to change.

In `@examples/python/simple_get.py`:
- Around line 111-113: The current check after calling
coraza_process_request_headers(tx) uses a loose assertion (ret >= 0) which would
miss regressions where the API returns 0 instead of the expected interruption
code; change the assertion to explicitly verify the expected interruption return
(use ret == 1) and update the error message in the _check call to include both
the function name coraza_process_request_headers, the tx identifier, and the
actual returned value to aid debugging.

---

Duplicate comments:
In `@libcoraza/coraza.go`:
- Around line 294-321: The request parser coraza_add_request_headers (and the
sibling coraza_add_response_headers) must add a nil check for packed before
calling C.GoBytes and must stop converting big-endian uint32 length fields to
int before validating bounds; read the 4-byte length into a uint32 (e.g.
valueLenU), check that off+int(valueLenU) (or compare remaining bytes as uint32)
is within buf length, then convert to int for slicing; likewise handle the
2-byte nameLen as uint16-first if not already, and ensure the initial guard
covers the case packed == nil && packed_len > 0 to return -1 before C.GoBytes is
invoked.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 66cc7f0f-c22b-4b4f-895d-74242ae859ed

📥 Commits

Reviewing files that changed from the base of the PR and between e9bd453 and 29cc81f.

📒 Files selected for processing (4)
  • coraza.i
  • examples/java/SimpleGet.java
  • examples/python/simple_get.py
  • libcoraza/coraza.go

Comment thread coraza.i
Comment thread examples/java/SimpleGet.java Outdated
Comment thread examples/python/simple_get.py Outdated
Copy link
Copy Markdown

@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 (2)
examples/python/simple_get.py (2)

201-203: Consistency nit: test_request_body_from_file still asserts strict == 0.

These three phase calls on the pass-rule path legitimately return CORAZA_OK today, but for consistency with the relaxed checks in test_lifecycle (Lines 122/126/142) and to avoid brittle failures if a future non-deny interruption becomes possible on this path, consider loosening to >= 0 (or != CORAZA_ERROR). Not a functional bug.

Proposed tightening
-    _check(_c.coraza_process_request_body(tx) == 0, "coraza_process_request_body failed")
-    _check(_c.coraza_process_response_headers(tx, 200, "HTTP/1.1") == 0, "coraza_process_response_headers failed")
-    _check(_c.coraza_process_response_body(tx) == 0, "coraza_process_response_body failed")
+    _check(_c.coraza_process_request_body(tx) >= 0, "coraza_process_request_body failed")
+    _check(_c.coraza_process_response_headers(tx, 200, "HTTP/1.1") >= 0, "coraza_process_response_headers failed")
+    _check(_c.coraza_process_response_body(tx) >= 0, "coraza_process_response_body failed")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/python/simple_get.py` around lines 201 - 203, The three strict
equality checks using _check against 0 for coraza_process_request_body,
coraza_process_response_headers, and coraza_process_response_body are brittle;
change the assertions to accept any non-error return (e.g., use
_check(_c.coraza_process_request_body(tx) >= 0, "...") or
_check(_c.coraza_process_request_body(tx) != CORAZA_ERROR, "...")) for all three
calls (coraza_process_request_body, coraza_process_response_headers,
coraza_process_response_body) so the test matches the relaxed checks used in
test_lifecycle and avoids false failures if non-deny non-zero codes are
returned.

111-142: Optional: use the exposed enum constants instead of numeric literals.

Since coraza.i exposes CORAZA_ERROR, CORAZA_OK, and CORAZA_INTERRUPTION via SWIG, the assertions could reference _c.CORAZA_INTERRUPTION and _c.CORAZA_OK instead of hardcoded 1 and 0. This improves readability and makes regressions (e.g., an accidental return of 0 from a phase expected to interrupt) more self-documenting. The current literals are functionally correct.

Proposed refinement
-    _check(ret == 1, f"coraza_process_request_headers: expected CORAZA_INTERRUPTION (1), got {ret}")
+    _check(ret == _c.CORAZA_INTERRUPTION, f"coraza_process_request_headers: expected CORAZA_INTERRUPTION, got {ret}")

-    _check(ret >= 0, f"coraza_process_request_body failed: {ret}")
+    _check(ret >= _c.CORAZA_OK, f"coraza_process_request_body failed: {ret}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/python/simple_get.py` around lines 111 - 142, Replace hardcoded
numeric return checks with the SWIG-exposed enum constants to improve
readability: use _c.CORAZA_INTERRUPTION for the check on
coraza_process_request_headers instead of 1, use _c.CORAZA_OK for checks that
expect success (coraza_append_request_body, coraza_add_response_header,
coraza_append_response_body) instead of 0, and continue to use comparison
operators (>= 0) for processing functions (coraza_process_request_body,
coraza_process_response_headers, coraza_process_response_body) as before; update
the _check assertions accordingly referencing the functions
coraza_process_request_headers, coraza_append_request_body,
coraza_add_response_header, coraza_append_response_body, and
coraza_process_response_body.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@examples/python/simple_get.py`:
- Around line 201-203: The three strict equality checks using _check against 0
for coraza_process_request_body, coraza_process_response_headers, and
coraza_process_response_body are brittle; change the assertions to accept any
non-error return (e.g., use _check(_c.coraza_process_request_body(tx) >= 0,
"...") or _check(_c.coraza_process_request_body(tx) != CORAZA_ERROR, "...")) for
all three calls (coraza_process_request_body, coraza_process_response_headers,
coraza_process_response_body) so the test matches the relaxed checks used in
test_lifecycle and avoids false failures if non-deny non-zero codes are
returned.
- Around line 111-142: Replace hardcoded numeric return checks with the
SWIG-exposed enum constants to improve readability: use _c.CORAZA_INTERRUPTION
for the check on coraza_process_request_headers instead of 1, use _c.CORAZA_OK
for checks that expect success (coraza_append_request_body,
coraza_add_response_header, coraza_append_response_body) instead of 0, and
continue to use comparison operators (>= 0) for processing functions
(coraza_process_request_body, coraza_process_response_headers,
coraza_process_response_body) as before; update the _check assertions
accordingly referencing the functions coraza_process_request_headers,
coraza_append_request_body, coraza_add_response_header,
coraza_append_response_body, and coraza_process_response_body.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ffdd4072-93da-41e4-b4fd-9f9a9f507018

📥 Commits

Reviewing files that changed from the base of the PR and between 29cc81f and c6d9e15.

📒 Files selected for processing (3)
  • coraza.i
  • examples/java/SimpleGet.java
  • examples/python/simple_get.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • coraza.i
  • examples/java/SimpleGet.java

ppomes added 5 commits April 23, 2026 14:45
- Phase functions now return 1 when interrupted, -1 on error, 0 on success
- Add coraza_add_request_headers/coraza_add_response_headers for bulk packed headers
- Add coraza_free_string for safe deallocation of strings returned by libcoraza

Ref corazawaf#93
@fzipi fzipi force-pushed the feat/api-ergonomics branch from c6d9e15 to ea4fb4c Compare April 23, 2026 17:45
Copy link
Copy Markdown

@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 (2)
libcoraza/coraza.go (1)

290-401: Use the new CORAZA_ERROR / CORAZA_OK constants and deduplicate the two decoders.

Two small follow-ups for the bulk-header functions:

  1. Consistency. The phase functions now return C.CORAZA_ERROR / C.CORAZA_OK / C.CORAZA_INTERRUPTION, but coraza_add_request_headers and coraza_add_response_headers still hardcode -1 and 0. Since the whole point of the enum is to give every C-exported function a common vocabulary, swap the literals for the named constants.

  2. DRY. The two functions are byte-for-byte identical except for the terminal tx.AddRequestHeader / tx.AddResponseHeader call. Extracting a small parser avoids having to fix a decoder bug in two places next time.

♻️ Proposed refactor
+// decodePackedHeaders walks the [name_len u16][name][value_len u32][value] × count
+// format and invokes addFn for each decoded header. Returns CORAZA_OK on success
+// or CORAZA_ERROR on any bounds violation.
+func decodePackedHeaders(packed *C.char, packed_len C.int, count C.int, addFn func(name, value string)) C.int {
+	if packed_len < 0 || count < 0 {
+		return C.CORAZA_ERROR
+	}
+	buf := C.GoBytes(unsafe.Pointer(packed), packed_len)
+	off := 0
+	for i := 0; i < int(count); i++ {
+		if off+2 > len(buf) {
+			return C.CORAZA_ERROR
+		}
+		nameLen := int(uint16(buf[off])<<8 | uint16(buf[off+1]))
+		off += 2
+		if off+nameLen > len(buf) {
+			return C.CORAZA_ERROR
+		}
+		name := string(buf[off : off+nameLen])
+		off += nameLen
+		if off+4 > len(buf) {
+			return C.CORAZA_ERROR
+		}
+		vl := uint32(buf[off])<<24 | uint32(buf[off+1])<<16 | uint32(buf[off+2])<<8 | uint32(buf[off+3])
+		off += 4
+		if uint64(vl) > uint64(len(buf)-off) {
+			return C.CORAZA_ERROR
+		}
+		valueLen := int(vl)
+		value := string(buf[off : off+valueLen])
+		off += valueLen
+		addFn(name, value)
+	}
+	return C.CORAZA_OK
+}
+
 //export coraza_add_request_headers
 func coraza_add_request_headers(t C.coraza_transaction_t, packed *C.char, packed_len C.int, count C.int) C.int {
-	if packed_len < 0 || count < 0 {
-		return -1
-	}
 	tx := fromRaw[types.Transaction](t)
-	buf := C.GoBytes(unsafe.Pointer(packed), packed_len)
-	off := 0
-	for i := 0; i < int(count); i++ {
-		... duplicated decode loop ...
-	}
-	return 0
+	return decodePackedHeaders(packed, packed_len, count, tx.AddRequestHeader)
 }

(analogous change for coraza_add_response_headers)

Note the refactor also bounds-checks vl as uint64 before converting to int, which closes a narrow 32-bit residual: on 32-bit builds today, int(uint32(...)) at lines 315 / 391 wraps any value ≥ 2 GiB to a negative int, so the subsequent off+valueLen > len(buf) guard passes spuriously and the next slice expression panics. On 64-bit builds this is harmless because int is 64 bits, but doing the range check before the conversion makes the code portable and obviously correct.

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

In `@libcoraza/coraza.go` around lines 290 - 401, Replace the literal return codes
and deduplicate the duplicate parsers: change coraza_add_request_headers and
coraza_add_response_headers to return C.CORAZA_ERROR / C.CORAZA_OK (and
C.CORAZA_INTERRUPTION where appropriate) instead of -1/0, and extract the shared
byte-decoding logic into a helper (e.g., parsePackedHeaders) that accepts the
packed buffer, packed_len, count and a callback to invoke either
tx.AddRequestHeader or tx.AddResponseHeader; in the helper do robust bounds
checks including validating valueLen as an unsigned width (check uint64 valueLen
<= uint64(len(buf)) before converting to int) to avoid 32-bit int wraparound,
and call that helper from coraza_add_request_headers and
coraza_add_response_headers.
coraza.i (1)

386-400: Consider returning coraza_result_t for the phase functions in the extern declarations.

The phase functions coraza_process_request_headers, coraza_process_request_body, coraza_process_response_headers, and coraza_process_response_body should be declared as returning coraza_result_t instead of int. This improves SWIG-generated bindings: Java code gets typed enum constants to compare against, and Python users see symbolic constant names in context. The values remain ABI-compatible with int.

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

In `@coraza.i` around lines 386 - 400, Change the return type of the phase
functions from int to coraza_result_t to improve bindings: update the extern
declarations for coraza_process_request_headers, coraza_process_request_body,
coraza_process_response_headers, and coraza_process_response_body to return
coraza_result_t instead of int (leave argument lists unchanged); ensure you
update every matching forward declaration/prototype for these function names so
SWIG/Python/Java see the enum-typed result while preserving ABI compatibility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@coraza.i`:
- Around line 386-400: Change the return type of the phase functions from int to
coraza_result_t to improve bindings: update the extern declarations for
coraza_process_request_headers, coraza_process_request_body,
coraza_process_response_headers, and coraza_process_response_body to return
coraza_result_t instead of int (leave argument lists unchanged); ensure you
update every matching forward declaration/prototype for these function names so
SWIG/Python/Java see the enum-typed result while preserving ABI compatibility.

In `@libcoraza/coraza.go`:
- Around line 290-401: Replace the literal return codes and deduplicate the
duplicate parsers: change coraza_add_request_headers and
coraza_add_response_headers to return C.CORAZA_ERROR / C.CORAZA_OK (and
C.CORAZA_INTERRUPTION where appropriate) instead of -1/0, and extract the shared
byte-decoding logic into a helper (e.g., parsePackedHeaders) that accepts the
packed buffer, packed_len, count and a callback to invoke either
tx.AddRequestHeader or tx.AddResponseHeader; in the helper do robust bounds
checks including validating valueLen as an unsigned width (check uint64 valueLen
<= uint64(len(buf)) before converting to int) to avoid 32-bit int wraparound,
and call that helper from coraza_add_request_headers and
coraza_add_response_headers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 17d11efb-fbbb-4d4b-a1bd-a5296b213a70

📥 Commits

Reviewing files that changed from the base of the PR and between c6d9e15 and ea4fb4c.

📒 Files selected for processing (4)
  • coraza.i
  • examples/java/SimpleGet.java
  • examples/python/simple_get.py
  • libcoraza/coraza.go
✅ Files skipped from review due to trivial changes (2)
  • examples/java/SimpleGet.java
  • examples/python/simple_get.py

@fzipi
Copy link
Copy Markdown
Member

fzipi commented Apr 24, 2026

I like both nitpicks from the rabbit 😄

Copy link
Copy Markdown

@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.

♻️ Duplicate comments (1)
libcoraza/coraza.go (1)

315-321: ⚠️ Potential issue | 🟠 Major

Signed-overflow in valueLen decode is still reachable on 32-bit builds.

The previous fix moved the decode to uint32 but then narrows to int before the bounds check:

valueLen := int(uint32(buf[off])<<24 | uint32(buf[off+1])<<16 | uint32(buf[off+2])<<8 | uint32(buf[off+3]))
off += 4
if off+valueLen > len(buf) { ... }

On platforms where int is 32-bit (e.g. GOARCH=386, arm, mips), any value with the high bit set (≥ 2 GiB, legal for a u32) becomes a negative int during the int(...) conversion. The check off+valueLen > len(buf) then passes with a negative sum, and buf[off : off+valueLen] panics — a crafted packed buffer can crash the host process. coraza_add_response_headers has the same issue at Line 391.

Keep the length as uint32 and range-check against the remaining buffer space before converting to int:

🛡️ Proposed fix
-		if off+4 > len(buf) {
-			return C.CORAZA_ERROR
-		}
-		valueLen := int(uint32(buf[off])<<24 | uint32(buf[off+1])<<16 | uint32(buf[off+2])<<8 | uint32(buf[off+3]))
-		off += 4
-		if off+valueLen > len(buf) {
-			return C.CORAZA_ERROR
-		}
-		value := string(buf[off : off+valueLen])
-		off += valueLen
+		if off+4 > len(buf) {
+			return C.CORAZA_ERROR
+		}
+		vl := uint32(buf[off])<<24 | uint32(buf[off+1])<<16 | uint32(buf[off+2])<<8 | uint32(buf[off+3])
+		off += 4
+		if uint64(vl) > uint64(len(buf)-off) {
+			return C.CORAZA_ERROR
+		}
+		valueLen := int(vl)
+		value := string(buf[off : off+valueLen])
+		off += valueLen

Apply the equivalent change in coraza_add_response_headers.

Also applies to: 391-397

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

In `@libcoraza/coraza.go` around lines 315 - 321, The current decode converts the
4-byte big-endian length into int, allowing negative values on 32-bit platforms;
instead decode into a uint32 (e.g., valueLen uint32 = uint32(buf[off])<<24 |
...), advance off by 4, then check that uint64(off)+uint64(valueLen) <=
uint64(len(buf)) (or compare valueLen against remaining uint32(len(buf)-off)) to
prevent overflow before converting; only after the bounds check convert valueLen
to int for slicing and use it to build value :=
string(buf[off:off+int(valueLen)]). Apply the identical change in
coraza_add_response_headers for its length decode and bounds check.
🧹 Nitpick comments (2)
libcoraza/coraza.go (2)

327-334: Header-processing functions: note the missing CORAZA_ERROR path.

These phase functions can only return CORAZA_OK or CORAZA_INTERRUPTION because tx.ProcessRequestHeaders / tx.ProcessResponseHeaders do not return an error. That matches the current Coraza v3 API and aligns with the test expectations (ret >= 0 is acceptable), so no change needed — worth calling out in the doc comment for the public header so callers understand that -1 is not a possible result from these two entry points but is possible from the body-processing ones.

Also applies to: 425-432

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

In `@libcoraza/coraza.go` around lines 327 - 334, The header-processing exported
functions (coraza_process_request_headers and coraza_process_response_headers)
currently only return CORAZA_OK or CORAZA_INTERRUPTION because
tx.ProcessRequestHeaders / tx.ProcessResponseHeaders do not return errors;
update the public doc comment above these exported functions to explicitly state
that CORAZA_ERROR (−1) is not possible for these entry points whereas
body-processing functions can return CORAZA_ERROR, so callers and tests know
that valid return values are only CORAZA_OK or CORAZA_INTERRUPTION for these two
functions.

294-324: Consider pre-computing capacity when adding headers.

The loop calls tx.AddRequestHeader per iteration without any pre-allocation hint, but count is known up-front. If types.Transaction exposes a bulk/append-with-capacity variant (or if the underlying implementation grows a map/slice), passing count once would avoid repeated reallocations on large header sets. If no such API exists, this is safe to ignore — keeping as a non-blocking note.

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

In `@libcoraza/coraza.go` around lines 294 - 324, The loop in
coraza_add_request_headers calls tx.AddRequestHeader repeatedly while count is
known up-front; to avoid repeated reallocations, pre-allocate capacity before
the loop by either calling a capacity-reserving method on types.Transaction
(e.g., tx.ReserveRequestHeaders(int(count)) if available) or by collecting
headers into a slice with pre-allocated capacity (make([]Header, 0, int(count))
or similar) and then calling a bulk add method (e.g.,
tx.AddRequestHeaders(headers)) after the loop; update code references
coraza_add_request_headers, types.Transaction, and tx.AddRequestHeader
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@libcoraza/coraza.go`:
- Around line 315-321: The current decode converts the 4-byte big-endian length
into int, allowing negative values on 32-bit platforms; instead decode into a
uint32 (e.g., valueLen uint32 = uint32(buf[off])<<24 | ...), advance off by 4,
then check that uint64(off)+uint64(valueLen) <= uint64(len(buf)) (or compare
valueLen against remaining uint32(len(buf)-off)) to prevent overflow before
converting; only after the bounds check convert valueLen to int for slicing and
use it to build value := string(buf[off:off+int(valueLen)]). Apply the identical
change in coraza_add_response_headers for its length decode and bounds check.

---

Nitpick comments:
In `@libcoraza/coraza.go`:
- Around line 327-334: The header-processing exported functions
(coraza_process_request_headers and coraza_process_response_headers) currently
only return CORAZA_OK or CORAZA_INTERRUPTION because tx.ProcessRequestHeaders /
tx.ProcessResponseHeaders do not return errors; update the public doc comment
above these exported functions to explicitly state that CORAZA_ERROR (−1) is not
possible for these entry points whereas body-processing functions can return
CORAZA_ERROR, so callers and tests know that valid return values are only
CORAZA_OK or CORAZA_INTERRUPTION for these two functions.
- Around line 294-324: The loop in coraza_add_request_headers calls
tx.AddRequestHeader repeatedly while count is known up-front; to avoid repeated
reallocations, pre-allocate capacity before the loop by either calling a
capacity-reserving method on types.Transaction (e.g.,
tx.ReserveRequestHeaders(int(count)) if available) or by collecting headers into
a slice with pre-allocated capacity (make([]Header, 0, int(count)) or similar)
and then calling a bulk add method (e.g., tx.AddRequestHeaders(headers)) after
the loop; update code references coraza_add_request_headers, types.Transaction,
and tx.AddRequestHeader accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 19d63a5c-ccd4-42e9-bee3-e97159993010

📥 Commits

Reviewing files that changed from the base of the PR and between ea4fb4c and 7691628.

📒 Files selected for processing (2)
  • examples/python/simple_get.py
  • libcoraza/coraza.go

@ppomes
Copy link
Copy Markdown
Member Author

ppomes commented Apr 24, 2026

Done — bulk header functions now use CORAZA_ERROR/CORAZA_OK, and Python example assertions are consistent.

@ppomes
Copy link
Copy Markdown
Member Author

ppomes commented Apr 24, 2026

Fixed the 32-bit overflow: valueLen is now validated as uint32 before narrowing to int.

Comment thread examples/python/simple_get.py Outdated
@fzipi fzipi changed the title api: return 1 on disruption, add bulk headers, add coraza_free_string feat(api): return 1 on disruption, add bulk headers, add coraza_free_string Apr 24, 2026
@fzipi fzipi merged commit 737af66 into corazawaf:main Apr 24, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants