From 4c44f4f191366571a674eee9370dd4425a22aa12 Mon Sep 17 00:00:00 2001 From: kabbohus Date: Thu, 23 Apr 2026 15:27:50 +0200 Subject: [PATCH 1/8] Fill ARGS_POST variable even if body bytes were invalid JSON when JSON body processor is running --- internal/bodyprocessors/json.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/bodyprocessors/json.go b/internal/bodyprocessors/json.go index 5176d9369..3face991b 100644 --- a/internal/bodyprocessors/json.go +++ b/internal/bodyprocessors/json.go @@ -30,12 +30,12 @@ func (js *jsonBodyProcessor) ProcessRequest(reader io.Reader, v plugintypes.Tran // Process with recursion limit col := v.ArgsPost() data, err := readJSON(ss, bpo.RequestBodyRecursionLimit) - if err != nil { - return err - } for key, value := range data { col.SetIndex(key, 0, value) } + if err != nil { + return err + } // Store the raw JSON in the TX variable for validateSchema // This is needed because RequestBody is a Single interface without a Set method @@ -59,12 +59,12 @@ func (js *jsonBodyProcessor) ProcessResponse(reader io.Reader, v plugintypes.Tra // Process with no recursion limit as we don't have a directive for response body col := v.ResponseArgs() data, err := readJSON(ss, ignoreJSONRecursionLimit) - if err != nil { - return err - } for key, value := range data { col.SetIndex(key, 0, value) } + if err != nil { + return err + } // Store the raw JSON in the TX variable for validateSchema // This is needed because ResponseBody is a Single interface without a Set method @@ -80,11 +80,11 @@ func readJSON(s string, maxRecursion int) (map[string]string, error) { res := make(map[string]string) key := []byte("json") + json := gjson.Parse(s) + err := readItems(json, key, maxRecursion, res) if !gjson.Valid(s) { return res, errors.New("invalid JSON") } - json := gjson.Parse(s) - err := readItems(json, key, maxRecursion, res) return res, err } From 4915775dd2298e45922086f477e3d993c76169d2 Mon Sep 17 00:00:00 2001 From: kabbohus Date: Thu, 23 Apr 2026 16:28:40 +0200 Subject: [PATCH 2/8] Add e2e test to check if process ProcessPartial and JSON body processor can work together --- testing/engine/json.go | 86 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/testing/engine/json.go b/testing/engine/json.go index 512b9507e..9ff5b7a48 100644 --- a/testing/engine/json.go +++ b/testing/engine/json.go @@ -74,3 +74,89 @@ SecRule ARGS:json.test3 "@eq 3" "id: 1010, phase:2, log, block" SecRule RESPONSE_ARGS:json.test4 "@eq 3" "id: 1011, phase:4, log, block" `, }) + +var _ = profile.RegisterProfile(profile.Profile{ + Meta: profile.Meta{ + Author: "jptosso", + Description: "Test if truncated JSON request/response body work", + Enabled: true, + Name: "truncatedjsonyaml", + }, + Tests: []profile.Test{ + { + Title: "json", + Stages: []profile.Stage{ + { + Stage: profile.SubStage{ + Input: profile.StageInput{ + URI: "/index.php?json.test=456", + Method: "POST", + Headers: map[string]string{ + "content-type": "application/json", + }, + Data: `{"test":123, "test2": 456, "test3": [22, 44, 55]}`, + }, + Output: profile.ExpectedOutput{ + TriggeredRules: []int{ + 100, + 101, + 1100, + 1101, + 1104, + 1010, + 1000, + 1011, + 1200, + 1201, + }, + NonTriggeredRules: []int{ + 103, + 1102, + 1103, + 1111, + 1202, + 1203, + }, + Headers: map[string]string{ + "Content-Type": "application/json", + }, + Data: `{"test": 123, "test2": 456, "test3": [22, 44, 55], "test4": 3}`, + }, + }, + }, + }, + }, + }, + Rules: ` +SecRequestBodyAccess On +SecResponseBodyAccess On +SecResponseBodyMimeType application/json +SecRequestBodyLimitAction ProcessPartial +SecRequestBodyLimit 40 +SecRule REQUEST_HEADERS:content-type "application/json" "id: 100, phase:1, pass, log, ctl:requestBodyProcessor=JSON" +SecRule RESPONSE_HEADERS:content-type "application/json" "id:1000, phase:3, pass, log, ctl:responseBodyProcessor=JSON" +SecRule REQBODY_PROCESSOR "JSON" "id: 101,phase:2,log,block" + +# This is commented our because we want to check if body processor can work with partial JSON +# SecRule REQBODY_ERROR "!@eq 0" "id:1111, phase:2, log, block" + +SecRule REQUEST_BODY "456" "id:103, phase:2, log" +SecRule ARGS:json.test "@eq 123" "id:1100, phase:2, log, block" +SecRule ARGS:json.test3.0 "@eq 22" "id:1101, phase:2, log, block" +SecRule ARGS:json.test3.1 "@eq 44" "id:1102, phase:2, log, block" +SecRule ARGS:json.test3.2 "@eq 55" "id:1103, phase:2, log, block" + +# Both GET and POST can be matched for the same key +SecRule ARGS:json.test "@eq 456" "id:1104, phase:2, log, block" + +SecRule ARGS:json.test3 "@eq 1" "id: 1010, phase:2, log, block" + +# Check ARGS_POST +SecRule ARGS_POST:json.test "@eq 123" "id:1200, phase:2, log, block" +SecRule ARGS_POST:json.test3.0 "@eq 22" "id:1201, phase:2, log, block" +SecRule ARGS_POST:json.test3.1 "@eq 44" "id:1202, phase:2, log, block" +SecRule ARGS_POST:json.test3.2 "@eq 55" "id:1203, phase:2, log, block" + +SecRule RESPONSE_ARGS:json.test4 "@eq 3" "id: 1011, phase:4, log, block" +`, +}) From 553395494fce282e32dbd09c5a0118783425c13c Mon Sep 17 00:00:00 2001 From: kabbohus Date: Thu, 23 Apr 2026 16:48:40 +0200 Subject: [PATCH 3/8] Typo Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- testing/engine/json.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/engine/json.go b/testing/engine/json.go index 9ff5b7a48..f1cd59335 100644 --- a/testing/engine/json.go +++ b/testing/engine/json.go @@ -137,7 +137,7 @@ SecRule REQUEST_HEADERS:content-type "application/json" "id: 100, phase:1, pass, SecRule RESPONSE_HEADERS:content-type "application/json" "id:1000, phase:3, pass, log, ctl:responseBodyProcessor=JSON" SecRule REQBODY_PROCESSOR "JSON" "id: 101,phase:2,log,block" -# This is commented our because we want to check if body processor can work with partial JSON +# This is commented out because we want to check if body processor can work with partial JSON # SecRule REQBODY_ERROR "!@eq 0" "id:1111, phase:2, log, block" SecRule REQUEST_BODY "456" "id:103, phase:2, log" From 879cd8dd69159931b0524772006542a0e5ba2e07 Mon Sep 17 00:00:00 2001 From: kabbohus Date: Thu, 9 Jul 2026 16:07:33 +0200 Subject: [PATCH 4/8] Return gjson error instead of silently dropping it Signed-off-by: kabbohus --- internal/bodyprocessors/json.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/bodyprocessors/json.go b/internal/bodyprocessors/json.go index 3face991b..168f85baa 100644 --- a/internal/bodyprocessors/json.go +++ b/internal/bodyprocessors/json.go @@ -82,10 +82,13 @@ func readJSON(s string, maxRecursion int) (map[string]string, error) { json := gjson.Parse(s) err := readItems(json, key, maxRecursion, res) + if err != nil { + return res, err + } if !gjson.Valid(s) { return res, errors.New("invalid JSON") } - return res, err + return res, nil } // Transform JSON to a map[string]string From cb392d5498b56f7a1f0b073649125ad994eb8f39 Mon Sep 17 00:00:00 2001 From: kabbohus Date: Thu, 9 Jul 2026 16:11:06 +0200 Subject: [PATCH 5/8] Add comments for more clearance --- internal/bodyprocessors/json.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/bodyprocessors/json.go b/internal/bodyprocessors/json.go index 168f85baa..4e9ef643b 100644 --- a/internal/bodyprocessors/json.go +++ b/internal/bodyprocessors/json.go @@ -30,6 +30,7 @@ func (js *jsonBodyProcessor) ProcessRequest(reader io.Reader, v plugintypes.Tran // Process with recursion limit col := v.ArgsPost() data, err := readJSON(ss, bpo.RequestBodyRecursionLimit) + // The collection is populated before checking the error to still perform a best effort inspection of the payload for key, value := range data { col.SetIndex(key, 0, value) } @@ -59,6 +60,7 @@ func (js *jsonBodyProcessor) ProcessResponse(reader io.Reader, v plugintypes.Tra // Process with no recursion limit as we don't have a directive for response body col := v.ResponseArgs() data, err := readJSON(ss, ignoreJSONRecursionLimit) + // The collection is populated before checking the error to still perform a best effort inspection of the payload for key, value := range data { col.SetIndex(key, 0, value) } From daff6e7b9c5daa1d7961c63625d6b321fac78fe9 Mon Sep 17 00:00:00 2001 From: kabbohus Date: Thu, 9 Jul 2026 16:11:17 +0200 Subject: [PATCH 6/8] Fix wrong author --- testing/engine/json.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/engine/json.go b/testing/engine/json.go index f1cd59335..cf542c91c 100644 --- a/testing/engine/json.go +++ b/testing/engine/json.go @@ -77,7 +77,7 @@ SecRule RESPONSE_ARGS:json.test4 "@eq 3" "id: 1011, phase:4, log, block" var _ = profile.RegisterProfile(profile.Profile{ Meta: profile.Meta{ - Author: "jptosso", + Author: "kabbohus", Description: "Test if truncated JSON request/response body work", Enabled: true, Name: "truncatedjsonyaml", From b59a645872fa21e2656235b6eb46ef4278785baa Mon Sep 17 00:00:00 2001 From: kabbohus Date: Thu, 9 Jul 2026 16:14:39 +0200 Subject: [PATCH 7/8] Uncomment rule because we expect REQBODY_ERROR AND ARGS_POST to be set --- testing/engine/json.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/engine/json.go b/testing/engine/json.go index cf542c91c..640c2ab46 100644 --- a/testing/engine/json.go +++ b/testing/engine/json.go @@ -106,6 +106,7 @@ var _ = profile.RegisterProfile(profile.Profile{ 1010, 1000, 1011, + 1111, 1200, 1201, }, @@ -113,7 +114,6 @@ var _ = profile.RegisterProfile(profile.Profile{ 103, 1102, 1103, - 1111, 1202, 1203, }, @@ -138,7 +138,7 @@ SecRule RESPONSE_HEADERS:content-type "application/json" "id:1000, phase:3, pass SecRule REQBODY_PROCESSOR "JSON" "id: 101,phase:2,log,block" # This is commented out because we want to check if body processor can work with partial JSON -# SecRule REQBODY_ERROR "!@eq 0" "id:1111, phase:2, log, block" +SecRule REQBODY_ERROR "!@eq 0" "id:1111, phase:2, log, block" SecRule REQUEST_BODY "456" "id:103, phase:2, log" SecRule ARGS:json.test "@eq 123" "id:1100, phase:2, log, block" From d45432107459055a2f1e03afbc77a0aadc9a0e40 Mon Sep 17 00:00:00 2001 From: kabbohus Date: Thu, 9 Jul 2026 16:30:45 +0200 Subject: [PATCH 8/8] Remove obsolete comment --- testing/engine/json.go | 1 - 1 file changed, 1 deletion(-) diff --git a/testing/engine/json.go b/testing/engine/json.go index 640c2ab46..e10d678de 100644 --- a/testing/engine/json.go +++ b/testing/engine/json.go @@ -137,7 +137,6 @@ SecRule REQUEST_HEADERS:content-type "application/json" "id: 100, phase:1, pass, SecRule RESPONSE_HEADERS:content-type "application/json" "id:1000, phase:3, pass, log, ctl:responseBodyProcessor=JSON" SecRule REQBODY_PROCESSOR "JSON" "id: 101,phase:2,log,block" -# This is commented out because we want to check if body processor can work with partial JSON SecRule REQBODY_ERROR "!@eq 0" "id:1111, phase:2, log, block" SecRule REQUEST_BODY "456" "id:103, phase:2, log"