Skip to content

Commit 1809f6e

Browse files
Copilotrenovate[bot]fzipiM4tteoPCopilot
authored
feat: add JSON Stream (NDJSON) body processor (#1563)
* fix(deps): update module golang.org/x/net to v0.45.0 [security] (#1487) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update go modules in go.mod (#1433) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * docs(actions): update format and add package (#1475) * docs(actions): update format and add package Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> * fix: update documentation for package Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> * fix: go fmt Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> --------- Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> * fix: add A-Z to auditlog (#1479) Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> * fix: SecRuleUpdateActionById should replace disruptive actions (#1471) * fix: SecRuleUpdateActionById should replace disruptive actions Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> * fix: multiphase test with bad expectations Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> * tests: improve coverage on engine Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> * refactor: address SecRuleUpdateActionById review comments (#1484) * Initial plan * Address code review comments: improve documentation, fix double parsing, and fix range logic Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * Refactor: Extract hasDisruptiveActions helper to avoid code duplication Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * docs: Improve applyParsedActions documentation Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * docs: Clarify body parsing logic in SetRawRequest Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * refactor: address review comments on SecRuleUpdateActionById - Rename ClearActionsOfType to ClearDisruptiveActions - Add comments explaining quote trimming in action parsing - Remove empty line after function brace in updateActionBySingleID - Split engine_test.go: move output/helper tests to engine_output_test.go * Apply suggestions from code review Co-authored-by: Matteo Pace <pace.matteo96@gmail.com> * fix: use index-based iteration for SecRuleUpdateActionById range updates The range loop variable copied each Rule, so modifications to disruptive actions were lost. Use index-based iteration to modify rules in place. Also adds a test case exercising the range update path. --------- Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Matteo Pace <pace.matteo96@gmail.com> * refactor: remove root package dependency on experimental (#1494) * refactor: remove root package dependency on experimental Replace experimental.Options with corazawaf.Options in waf.go, breaking the import cycle that prevented the experimental package from importing the root coraza package. This unblocks PR #1478 and lets experimental helpers use coraza.WAFConfig with proper type safety instead of any. * Update waf.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore: min go version to 1.25 (#1497) * No content wants no body * Update .github/workflows/regression.yml Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com> * one more place --------- Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com> * feat: add optional rule observer callback to WAF config (#1478) * feat: add optional rule observer callback to WAF config Introduce an optional rule observer callback that is invoked for each rule successfully added to the WAF during initialization. The observer receives rule metadata via the existing RuleMetadata interface. * Move to the experimental package * Do not use reflection to keep the compatibility with older Go versions * Use coraza.WAFConfig, move the test to where it belongs. --------- Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com> Co-authored-by: José Carlos Chávez <jcchavezs@gmail.com> * feat: add WAFWithRules interface with RulesCount() (#1492) Add WAFWithRules interface with RulesCount() * fix(deps): update module golang.org/x/net to v0.51.0 [security] (#1502) * fix(deps): update module golang.org/x/net to v0.51.0 [security] * chore: update go.work to 1.25.0 Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> * chore: update golang to 1.25.0 Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> --------- Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Felipe Zipitria <felipe.zipitria@owasp.org> * chore(deps): update module golang.org/x/net to v0.51.0 [security] (#1506) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix: lowercase regex patterns for case-insensitive variable collections (#1505) * fix: lowercase regex patterns for case-insensitive variable collections When a rule uses regex-based variable selection (e.g. TX:/PATTERN/), the regex pattern was compiled from the raw uppercase string before any case normalization. Since TX collection keys are stored lowercase, the uppercase regex would never match, causing rules like CRS 922110 (which uses TX:/MULTIPART_HEADERS_CONTENT_TYPES_*/) to silently fail. Now AddVariable and AddVariableNegation lowercase the regex pattern before compilation for case-insensitive variables, matching the existing behavior for string keys in newRuleVariableParams. * chore: update coreruleset to v4.24.0 Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> --------- Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> * chore: update libinjection-go and deps (#1496) * chore: update libinjection-go and deps Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> * chore: update coreruleset v4.24.0 Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> --------- Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> * fix: ctl:ruleRemoveTargetById to support whole-collection exclusion (#1495) * Initial plan * Fix ruleRemoveTargetById to support removing entire collection (empty key) Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * feat: add SecRequestBodyJsonDepthLimit directive (#1110) * feat: add SecRequestBodyJsonDepthLimit directive Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> * Apply suggestions from code review * fix: mage format Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> * Update internal/bodyprocessors/json_test.go * Update internal/bodyprocessors/json_test.go * fix: bad char Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> * fix: gofmt Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> * docs: add clarifying comments for JSON recursion limit behavior - Explain why ResponseBodyRecursionLimit = -1 (unlimited for responses) - Document dual purpose of body reading (TX vars + ARGS_POST) - Clarify DoS protection mechanism in readItems() - Note how negative values bypass recursion check * fix: address PR review comments for JSON depth limit - Always enforce a positive recursion limit: change ResponseBodyRecursionLimit from -1 (unlimited) to 1024, matching the request body default - Rename test case "broken1" to "unbalanced_brackets" for clarity - Extract error check from the key iteration loop in TestReadJSON * test: add benchmarks for gjson.Valid pre-validation overhead Measures the cost of gjson.Valid() in the full readJSON pipeline. gjson.Parse is lazy (~9ns), so the real overhead is Valid vs the readItems traversal. Results show ~10-16% overhead for validation, which is acceptable for WAF safety. No single-pass alternative exists in the gjson API. * Apply suggestions from code review * Apply suggestion from @fzipi --------- Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> Co-authored-by: José Carlos Chávez <jcchavezs@gmail.com> * fix: update constants for recursion limit (#1512) * fix: conflate the constants for recursion limit * fix: value setting * chore: remove panic from seclang compiler (#1514) * Initial plan * fix: replace panic with error return in parser.go evaluateLine Co-authored-by: jptosso <1236942+jptosso@users.noreply.github.com> * fix: revert go.sum changes - do not modify go.sum files in this PR Co-authored-by: jptosso <1236942+jptosso@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jptosso <1236942+jptosso@users.noreply.github.com> * ci: reduce regression matrix from 128 to 15 jobs (#1522) Replace dynamic 64-permutation tag matrix with a curated static list of 13 build-flag combinations. Run all combos on Go 1.25.x and only baseline + kitchen-sink on Go 1.26.x. Add concurrency groups to regression, lint, tinygo, and codeql workflows so stale PR runs are auto-cancelled on new pushes. * feat: ignore unexpected EOF in MIME multipart request body processor (#1453) * Ignore unexpected EOF in MIME multipart request body processor We need this behavior since we need to process an incomplete MIME multipart request body when SecRequestBodyLimitAction is set to ProcessPartial. * fix: add copilot code review comments Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> --------- Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> Co-authored-by: José Carlos Chávez <jcchavezs@gmail.com> Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com> Co-authored-by: Felipe Zipitria <felipe.zipitria@owasp.org> * fix: set changed flag in removeComments and escapeSeqDecode (#1532) Fix two bugs where transformation functions modified the input string but did not report changed=true: - removeComments: entering a C-style (/* */) or HTML (<!-- -->) comment block did not set changed=true, causing the multi-match optimization to skip the transformed result. - escapeSeqDecode: unrecognized escape sequences (e.g. \z) dropped the backslash but did not set changed=true. Add test coverage for both fixes including a new remove_comments_test.go and an additional unrecognized-escape test case for escape_seq_decode. * perf: use map for ruleRemoveByID for O(1) lookup (#1524) * perf: use map for ruleRemoveByID for O(1) lookup Replace []int slice with map[int]struct{} for the per-transaction rule exclusion list. The rule evaluation loop checks this list for every rule in every phase, making O(1) map lookup significantly faster than O(n) linear scan when rules are excluded via ctl actions. * test: add TestRemoveRuleByID for map-based rule exclusion * bench: add BenchmarkRuleEvalWithRemovedRules * refactor: use real unconditionalMatch operator from registry in tests * Fix HTTP middleware to process all Transfer-Encoding values (#1518) * Fix HTTP middleware to process all Transfer-Encoding values Co-authored-by: jptosso <1236942+jptosso@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jptosso <1236942+jptosso@users.noreply.github.com> Co-authored-by: Matteo Pace <pace.matteo96@gmail.com> * fix(deps): update module golang.org/x/sync to v0.20.0 in go.mod (#1543) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * feat: optimize ruleRemoveById range handling store ranges instead of expanding to int slices (#1538) * Initial plan * Optimize ruleRemoveById range handling to avoid generating massive int slices - Replace rangeToInts (which allocated []int of all matching rule IDs) with parseRange and parseIDOrRange helpers that return start/end integers - For ctlRuleRemoveByID with ranges: store the range in Transaction.ruleRemoveByIDRanges ([][2]int) and check it in the rule evaluation loop, avoiding both the intermediate []int and potentially large map expansions - For ctlRuleRemoveTargetByID: iterate rules once directly, eliminating the intermediate []int allocation - Add RemoveRuleByIDRange method to Transaction - Reset ruleRemoveByIDRanges on transaction pool reuse - Replace TestCtlParseRange with TestCtlParseIDOrRange to test the new helpers Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * Improve test coverage for range-based rule removal - Add TestRemoveRuleByIDRange in transaction_test.go: - range is stored in ruleRemoveByIDRanges - rules in range are skipped during Eval - multiple ranges work correctly - ruleRemoveByIDRanges is reset on transaction pool reuse - Add TestCtlParseRange in ctl_test.go to cover parseRange directly (including the no-separator and start>end error paths) - Add GetRuleRemoveByIDRanges() accessor on Transaction for cross-package test assertions - Enhance "ruleRemoveById range" TestCtl case to verify the range is stored - Add "ruleRemoveTargetById range" TestCtl case to verify range path works Coverage changes: parseRange: 83.3% → 100% parseIDOrRange: 100% (unchanged) RemoveRuleByIDRange: 0% → 100% Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * fix(testing): Correct use of ProcessURI in Benchmarks (#1546) * perf: prefix-based transformation cache with inline values (#1544) Redesign the transformation cache to share intermediate results across rules with common transformation prefixes (e.g. rules using t:lowercase,t:urlDecodeUni reuse the t:lowercase result cached by an earlier rule using just t:lowercase). Key changes: - Add transformationPrefixIDs to Rule for backward prefix search - Cache every intermediate transformation step, not just the final result - Store cache values inline (not pointers) to avoid heap allocations - Fix ClearTransformations (t:none) to reset transformationsID Benchmarked against full CRS v4 ruleset (8 runs, benchstat): Allocations: -2% (small) to -19% (30 params) Memory: -2% (small) to -12% (30 params) Timing: -5% (small/large), neutral (medium) No regressions on any metric. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * perf: bulk-allocate MatchData in collection Find methods (#1530) * perf: bulk-allocate MatchData in collection Find methods Pre-allocate a contiguous []corazarules.MatchData buffer and take pointers into it instead of individually heap-allocating each MatchData. This reduces per-result allocations from N to 2 (one buf slice + one result slice), improving GC pressure for large result sets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * perf: avoid double regex evaluation in FindRegex Collect matching data slices during the counting pass so the second pass only iterates over already-matched entries, eliminating redundant MatchString calls. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * bench: add FindAll/FindRegex/FindString benchmarks --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com> * perf: use FindStringSubmatchIndex to avoid capture allocations (#1547) * perf: use FindStringSubmatchIndex to avoid capture allocations Replace FindStringSubmatch (allocates a []string slice per match) with FindStringSubmatchIndex (returns index pairs). Substrings passed to CaptureField become slices of the original input — zero allocation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add BenchmarkRxCapture for submatch allocation comparison Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix(DetectionOnly): fixed RelevantOnly audit logs, improved matchedRules (#1549) * add detectedInterruption var for DetectionOnly mode * IsDetectionOnly, refactor, populate matchedRules * nit * Apply suggestions from code review Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com> --------- Co-authored-by: Romain SERVIERES <romain@madeformed.com> Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com> * fix(deps): update module golang.org/x/net to v0.52.0 in go.mod (#1553) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * ci: increase fuzztime (#1554) * more fuzztime * go mod * chore(ci): harden GHA workflows with least-privilege permissions (#1559) - Add top-level `permissions: {}` (deny-all) to every workflow - Add scoped per-job permissions granting only what each job needs - Fix expression injection in regression.yml by using env instead of inline shell interpolation for BUILD_TAGS - Restrict regression.yml pull_request trigger to main branch only - Add explicit permissions to fuzz.yml (issues: write for failure reports) - Add security-events: write to CodeQL workflow * feat: enable regex memoize by default (#1540) * feat: enable regex memoize by default Memoization of regex and aho-corasick builders was previously opt-in via the `memoize_builders` build tag. Most users didn't know to enable it, missing a critical performance optimization. This commit: - Enables memoization by default (opt-out via `coraza.no_memoize` tag) - Refactors internal/memoize from package-level Do() to Memoizer struct - Adds Memoizer interface to plugintypes.OperatorOptions - Wires WAF's Memoizer through to all operator and rule consumers - Replaces `memoize_builders` build tag with `coraza.no_memoize` opt-out Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: document cache tradeoffs and add noop memoize test - Update README and memoize README to document global cache behavior and point to WAF.Close() for live-reload scenarios. - Add test file for coraza.no_memoize build variant to verify no-op behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add WAF.Close() with per-owner memoize cache tracking and scale benchmarks (#1541) * feat: add WAF.Close() with per-owner memoize cache tracking Add WAFCloser interface and per-owner tracking to the memoize cache so that long-lived processes can release compiled regex entries when a WAF instance is destroyed. Each WAF gets a uint64 ID; Release() removes the owner and tombstones entries with no remaining owners. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add memoize scale benchmarks and CRS integration tests Add benchmarks demonstrating memoize value at scale (1-100 WAFs × 300 patterns) and CRS integration tests verifying Close() releases memory. Results show ~27x speedup for 100 WAFs and 27MiB released on Close(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add WAF.Close() calls to e2e and CRS tests Demonstrate proper WAFCloser usage in integration tests: e2e test, CRS FTW test, CRS benchmarks, and crsWAF helper. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * test: extend coraza.no_memoize coverage in noop_test.go (#1555) * Initial plan * test: extend noop_test.go coverage for coraza.no_memoize build tag Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * fix: check error return of m.Do in benchmark to resolve errcheck lint failure (#1556) * Initial plan * fix: check error return of m.Do in benchmark test to fix errcheck lint Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * fix: skip memoize scale tests in short mode The scale tests (TestMemoizeScaleMultipleOwners, TestCacheGrowthWithoutClose, TestCacheBoundedWithClose) compile hundreds of regexes across many owners/cycles. Under TinyGo's slower regex engine these take hours when run in CI with -short. Gate all three scale tests behind testing.Short() in both sync_test.go and nosync_test.go so TinyGo CI (which passes -short) completes in reasonable time. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(memoize): avoid deadlock in TinyGo's sync.Map during Release and Reset TinyGo's sync.Map.Range() holds its internal lock for the entire iteration. Calling cache.Delete() inside the Range callback tries to re-acquire the same non-reentrant lock, causing a deadlock. Defer all cache.Delete() calls until after Range returns by collecting keys first. This also fixes t.Skip() in tests which does not halt execution in TinyGo due to unimplemented runtime.Goexit(). On standard Go this is a net performance win for Release (up to 60% faster at 100 owners) with negligible temporary memory (~9KB slice). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Felipe Zipitria <felipe.zipitria@owasp.org> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * feat: implement SecUploadKeepFiles directive (#1557) * feat: implement SecUploadKeepFiles with RelevantOnly support Add UploadKeepFilesStatus type supporting On, Off, and RelevantOnly values for the SecUploadKeepFiles directive. When set to On, uploaded files are preserved after transaction close. When set to RelevantOnly, files are kept only if rules matched during the transaction. Closes #1550 * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @M4tteoP Co-authored-by: Matteo Pace <pace.matteo96@gmail.com> * docs: update SecUploadKeepFiles in coraza.conf-recommended Remove the "not supported" note and document the RelevantOnly option. * fix: filter nolog rules in RelevantOnly upload keep files check RelevantOnly now only considers rules with Log enabled, matching the same filtering used for audit log part K. This prevents CRS initialization rules (nolog) from making RelevantOnly behave like On. * fix: require SecUploadDir when SecUploadKeepFiles is enabled Add validation in WAF.Validate() to ensure SecUploadDir is configured when SecUploadKeepFiles is set to On or RelevantOnly, matching the ModSecurity requirement. * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix: directive docs Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Matteo Pace <pace.matteo96@gmail.com> * fix: correct two compile errors in SecUploadKeepFiles implementation (#1560) * Initial plan * fix: correct lint errors - HasAccessToFS is a bool not a function, fix wrong constant name Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * fix: gofmt Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> * fix: skip SecUploadKeepFiles tests when no_fs_access build tag is set The upload keep files tests expected success for On/RelevantOnly modes, but the implementation correctly rejects these when filesystem access is disabled. Guard these test cases behind environment.HasAccessToFS. --------- Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Matteo Pace <pace.matteo96@gmail.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> * feat: add regex support to ctl:ruleRemoveTargetById, ruleRemoveTargetByTag, and ruleRemoveTargetByMsg collection keys (#1561) * Initial plan * Add regex support to ctl:ruleRemoveTargetById for URI-scoped exclusions Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * Use memoization for regex compilation in parseCtl Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * Add benchmarks for short and medium regex exceptions in GetField Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * refactor: add HasRegex shared utility and use it in rule.go and ctl.go Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * test: add POST JSON body test for ruleRemoveTargetById regex key exclusion Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * docs: update RemoveRuleTargetByID comment to document keyRx parameter Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * docs: update ctl action doc comment to describe regex key syntax with example Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * test: add ruleRemoveTargetByTag and ruleRemoveTargetByMsg regex key integration tests Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * style: apply gofmt to internal/actions/ctl.go Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * test: add memoizer coverage to TestParseCtl for ctl regex path Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> * Initial plan * test: add e2e tests for JSONSTREAM body processor Co-authored-by: fzipi <3012076+fzipi@users.noreply.github.com> Agent-Logs-Url: https://github.com/corazawaf/coraza/sessions/bebca76e-344f-4966-8675-8bf4e5fda0cb --------- Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Matteo Pace <pace.matteo96@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Alexander S. <126732+heaven@users.noreply.github.com> Co-authored-by: José Carlos Chávez <jcchavezs@gmail.com> Co-authored-by: Pierre POMES <pierre.pomes@gmail.com> Co-authored-by: Felipe Zipitria <felipe.zipitria@owasp.org> Co-authored-by: jptosso <1236942+jptosso@users.noreply.github.com> Co-authored-by: Juan Pablo Tosso <jptosso@gmail.com> Co-authored-by: Hiroaki Nakamura <hnakamur@gmail.com> Co-authored-by: Marc W. <113890636+MarcWort@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Romain SERVIERES <romain@madeformed.com>
1 parent d4502d1 commit 1809f6e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

91 files changed

+5493
-781
lines changed

.github/workflows/close-issues.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ on:
33
schedule:
44
- cron: "30 1 * * *"
55

6+
permissions: {}
7+
68
jobs:
79
close-issues:
810
runs-on: ubuntu-latest

.github/workflows/codeql-analysis.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,20 @@ on:
33
pull_request:
44
schedule:
55
- cron: '0 6 * * 6'
6+
7+
permissions: {}
8+
9+
concurrency:
10+
group: codeql-${{ github.ref }}
11+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
12+
613
jobs:
714
analyze:
815
name: Analyze
916
runs-on: ubuntu-latest
17+
permissions:
18+
security-events: write
19+
contents: read
1020

1121
steps:
1222
- name: Checkout repository

.github/workflows/fuzz.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,20 @@ on:
66
- cron: "05 14 * * *"
77
workflow_dispatch:
88

9+
permissions: {}
10+
911
jobs:
1012
fuzz:
1113
name: Fuzz tests
1214
runs-on: ubuntu-latest
15+
permissions:
16+
contents: read
17+
issues: write
1318
steps:
1419
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
1520
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6
1621
with:
17-
go-version: ">=1.24.0"
22+
go-version: ">=1.25.0"
1823
- run: go run mage.go fuzz
1924
- run: |
2025
gh issue create --title "$GITHUB_WORKFLOW #$GITHUB_RUN_NUMBER failed" \

.github/workflows/lint.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,17 @@ on:
1414
- "**/*.md"
1515
- "LICENSE"
1616

17+
permissions: {}
18+
19+
concurrency:
20+
group: lint-${{ github.ref }}
21+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
22+
1723
jobs:
1824
lint:
1925
runs-on: ubuntu-latest
26+
permissions:
27+
contents: read
2028
steps:
2129
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
2230
- name: Install Go

.github/workflows/regression.yml

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,51 @@ on:
88
- "**/*.md"
99
- "LICENSE"
1010
pull_request:
11+
branches:
12+
- main
1113
paths-ignore:
1214
- "**/*.md"
1315
- "LICENSE"
1416

15-
jobs:
16-
# Generate matrix of tags for all permutations of the tests
17-
generate-matrix:
18-
runs-on: ubuntu-latest
19-
outputs:
20-
tags: ${{ steps.generate.outputs.tags }}
21-
steps:
22-
- name: Checkout code
23-
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
17+
permissions: {}
18+
19+
concurrency:
20+
group: regression-${{ github.ref }}
21+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
2422

25-
- name: Generate tag combinations
26-
id: generate
27-
run: |
28-
go run mage.go tagsmatrix > tags.json
29-
echo "tags=$(cat tags.json)" >> "$GITHUB_OUTPUT"
30-
shell: bash
23+
jobs:
3124
test:
32-
needs: generate-matrix
3325
strategy:
3426
fail-fast: false
3527
matrix:
36-
go-version: [1.24.x, 1.25.x]
28+
go-version: [1.25.x]
3729
os: [ubuntu-latest]
38-
build-flag: ${{ fromJson(needs.generate-matrix.outputs.tags) }}
30+
build-flag:
31+
- ""
32+
- "coraza.rule.mandatory_rule_id_check"
33+
- "coraza.rule.case_sensitive_args_keys"
34+
- "coraza.rule.no_regex_multiline"
35+
- "coraza.no_memoize"
36+
- "coraza.rule.multiphase_evaluation"
37+
- "no_fs_access"
38+
- "coraza.rule.multiphase_evaluation,coraza.rule.mandatory_rule_id_check"
39+
- "coraza.rule.multiphase_evaluation,coraza.rule.case_sensitive_args_keys"
40+
- "coraza.rule.multiphase_evaluation,coraza.rule.no_regex_multiline"
41+
- "no_fs_access,coraza.no_memoize"
42+
- "coraza.rule.mandatory_rule_id_check,coraza.rule.case_sensitive_args_keys,coraza.rule.no_regex_multiline"
43+
- "coraza.rule.multiphase_evaluation,coraza.rule.mandatory_rule_id_check,coraza.rule.case_sensitive_args_keys,coraza.rule.no_regex_multiline,coraza.no_memoize,no_fs_access"
44+
include:
45+
- go-version: 1.26.x
46+
os: ubuntu-latest
47+
build-flag: ""
48+
- go-version: 1.26.x
49+
os: ubuntu-latest
50+
build-flag: "coraza.rule.multiphase_evaluation,coraza.rule.mandatory_rule_id_check,coraza.rule.case_sensitive_args_keys,coraza.rule.no_regex_multiline,coraza.no_memoize,no_fs_access"
3951
runs-on: ${{ matrix.os }}
52+
permissions:
53+
contents: read
4054
env:
41-
GOLANG_BASE_VERSION: "1.24.x"
55+
GOLANG_BASE_VERSION: "1.25.x"
4256
steps:
4357
- name: Checkout code
4458
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
@@ -48,9 +62,9 @@ jobs:
4862
go-version: ${{ matrix.go-version }}
4963
cache: true
5064
- name: Tests and coverage
51-
run: |
52-
export BUILD_TAGS=${{ matrix.build-flag }}
53-
go run mage.go coverage
65+
env:
66+
BUILD_TAGS: ${{ matrix.build-flag }}
67+
run: go run mage.go coverage
5468
- name: "Codecov: General"
5569
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5
5670
if: ${{ matrix.go-version == env.GOLANG_BASE_VERSION }}
@@ -84,12 +98,13 @@ jobs:
8498
runs-on: ubuntu-latest
8599
permissions:
86100
checks: read
101+
contents: read
87102
steps:
88103
- name: GitHub Checks
89104
uses: poseidon/wait-for-status-checks@899c768d191b56eef585c18f8558da19e1f3e707 # v0.6.0
90105
with:
91106
token: ${{ secrets.GITHUB_TOKEN }}
92-
delay: 120s # give some time to matrix jobs
107+
delay: 30s
93108
interval: 10s # default value
94109
timeout: 3600s # default value
95110
ignore: "codecov/patch,codecov/project"

.github/workflows/tinygo.yml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,23 @@ on:
1414
- "**/*.md"
1515
- "LICENSE"
1616

17+
permissions: {}
18+
19+
concurrency:
20+
group: tinygo-${{ github.ref }}
21+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
22+
1723
jobs:
1824
test:
1925
strategy:
2026
matrix:
21-
go-version: [1.24.x]
27+
go-version: [1.25.x]
2228
# tinygo-version is meant to stay aligned with the one used in corazawaf/coraza-proxy-wasm
2329
tinygo-version: [0.40.1]
2430
os: [ubuntu-latest]
2531
runs-on: ${{ matrix.os }}
32+
permissions:
33+
contents: read
2634
steps:
2735
- name: Checkout code
2836
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
@@ -53,5 +61,5 @@ jobs:
5361
- name: Tests
5462
run: tinygo test -v -short ./internal/...
5563

56-
- name: Tests memoize
57-
run: tinygo test -v -short -tags=memoize_builders ./internal/...
64+
- name: Tests no_memoize
65+
run: tinygo test -v -short -tags=coraza.no_memoize ./internal/...

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,11 @@ have compatibility guarantees across minor versions - use with care.
100100
the operator with `plugins.RegisterOperator` to reduce binary size / startup overhead.
101101
* `coraza.rule.multiphase_evaluation` - enables evaluation of rule variables in the phases that they are ready, not
102102
only the phase the rule is defined for.
103-
* `memoize_builders` - enables memoization of builders for regex and aho-corasick
104-
dictionaries to reduce memory consumption in deployments that launch several coraza
105-
instances. For more context check [this issue](https://github.com/corazawaf/coraza-caddy/issues/76)
103+
* `coraza.no_memoize` - disables the default memoization of regex and aho-corasick builders.
104+
Memoization is enabled by default and uses a global cache to reuse compiled patterns across WAF
105+
instances, reducing memory consumption and startup overhead. In long-lived processes that perform
106+
live reloads, use `WAF.Close()` (via `experimental.WAFCloser`) to release cached entries when a
107+
WAF is destroyed, or use this tag to opt out of memoization entirely.
106108
* `no_fs_access` - indicates that the target environment has no access to FS in order to not leverage OS' filesystem related functionality e.g. file body buffers.
107109
* `coraza.rule.case_sensitive_args_keys` - enables case-sensitive matching for ARGS keys, aligning Coraza behavior with RFC 3986 specification. It will be enabled by default in the next major version.
108110
* `coraza.rule.no_regex_multiline` - disables enabling by default regexes multiline modifiers in `@rx` operator. It aligns with CRS expected behavior, reduces false positives and might improve performances. No multiline regexes by default will be enabled in the next major version. For more context check [this PR](https://github.com/corazawaf/coraza/pull/876)

config.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ type wafRule struct {
9494
// int is a signed integer type that is at least 32 bits in size (platform-dependent size).
9595
// We still basically assume 64-bit usage where int are big sizes.
9696
type wafConfig struct {
97+
ruleObserver func(rule types.RuleMetadata)
9798
rules []wafRule
9899
auditLog *auditLogConfig
99100
requestBodyAccess bool
@@ -119,6 +120,12 @@ func (c *wafConfig) WithRules(rules ...*corazawaf.Rule) WAFConfig {
119120
return ret
120121
}
121122

123+
func (c *wafConfig) WithRuleObserver(observer func(rule types.RuleMetadata)) WAFConfig {
124+
ret := c.clone()
125+
ret.ruleObserver = observer
126+
return ret
127+
}
128+
122129
func (c *wafConfig) WithDirectivesFromFile(path string) WAFConfig {
123130
ret := c.clone()
124131
ret.rules = append(ret.rules, wafRule{file: path})

coraza.conf-recommended

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ SecRule REQUEST_HEADERS:Content-Type "^application/json" \
3434
SecRule REQUEST_HEADERS:Content-Type "^application/[a-z0-9.-]+[+]json" \
3535
"id:'200006',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
3636

37+
# Configures the maximum JSON recursion depth limit Coraza will accept.
38+
SecRequestBodyJsonDepthLimit 1024
39+
3740
# Enable JSON stream request body parser for NDJSON (Newline Delimited JSON) format.
3841
# This processor handles streaming JSON where each line contains a complete JSON object.
3942
# Commonly used for bulk data imports, log streaming, and batch API endpoints.
@@ -48,14 +51,6 @@ SecRule REQUEST_HEADERS:Content-Type "^application/[a-z0-9.-]+[+]json" \
4851
#SecRule REQUEST_HEADERS:Content-Type "^application/json-seq" \
4952
# "id:'200010',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSONSTREAM"
5053

51-
# Optional: Limit the number of JSON objects in NDJSON/JSON Sequence streams to prevent abuse
52-
# Uncomment and adjust the limit as needed for your bulk endpoints
53-
#
54-
#SecRule TX:jsonstream_request_line_count "@gt 1000" \
55-
# "id:'200009',phase:2,t:none,deny,status:413,\
56-
# msg:'Too many JSON objects in stream',\
57-
# logdata:'Line count: %{TX.jsonstream_request_line_count}'"
58-
5954
# Maximum request body size we will accept for buffering. If you support
6055
# file uploads, this value must has to be as large as the largest file
6156
# you are willing to accept.
@@ -143,11 +138,11 @@ SecDataDir /tmp/
143138
#
144139
#SecUploadDir /opt/coraza/var/upload/
145140

146-
# If On, the WAF will store the uploaded files in the SecUploadDir
147-
# directory.
148-
# Note: SecUploadKeepFiles is currently NOT supported by Coraza
141+
# Controls whether intercepted uploaded files will be kept after
142+
# transaction is processed. Possible values: On, Off, RelevantOnly.
143+
# RelevantOnly will keep files only when a matching rule is logged (rules with 'nolog' do not qualify).
149144
#
150-
#SecUploadKeepFiles Off
145+
#SecUploadKeepFiles RelevantOnly
151146

152147
# Uploaded files are by default created with permissions that do not allow
153148
# any other user to access them. You may need to relax that if you want to

examples/http-server/go.mod

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
module github.com/corazawaf/coraza/v3/examples/http-server
22

3-
go 1.24.0
3+
go 1.25.0
44

55
require github.com/corazawaf/coraza/v3 v3.3.3
66

77
require (
8-
github.com/corazawaf/libinjection-go v0.2.2 // indirect
8+
github.com/corazawaf/libinjection-go v0.3.2 // indirect
99
github.com/google/go-cmp v0.6.0 // indirect
1010
github.com/magefile/mage v1.15.1-0.20250615140142-78acbaf2e3ae // indirect
1111
github.com/petar-dambovaliev/aho-corasick v0.0.0-20250424160509-463d218d4745 // indirect
1212
github.com/tidwall/gjson v1.18.0 // indirect
1313
github.com/tidwall/match v1.1.1 // indirect
1414
github.com/tidwall/pretty v1.2.1 // indirect
1515
github.com/valllabh/ocsf-schema-golang v1.0.3 // indirect
16-
golang.org/x/net v0.43.0 // indirect
17-
golang.org/x/sync v0.16.0 // indirect
18-
golang.org/x/tools v0.35.0 // indirect
16+
golang.org/x/net v0.52.0 // indirect
17+
golang.org/x/sync v0.20.0 // indirect
18+
golang.org/x/tools v0.42.0 // indirect
1919
google.golang.org/protobuf v1.35.1 // indirect
2020
rsc.io/binaryregexp v0.2.0 // indirect
2121
)

0 commit comments

Comments
 (0)