Repository: corazawaf/coraza
Pull request #1125 (2024-08-05 08:08:33)

Bug Fix: The value in the setvar should be able to start with - or +. 
> Thank you for contributing to Coraza WAF, your effort is greatly appreciated
> Before submitting check if what you want to add to `coraza` list meets [quality standards](https://github.com/corazawaf/coraza/blob/master/CONTRIBUTING.md#quality-standards) before sending pull request. Thanks!

**Make sure that you've checked the boxes below before you submit PR:**

- [✅] My code includes positive and negative tests.
- [✅] I have an appropriate description with correct grammar.
- [✅] I have read [Contribution guidelines](https://github.com/corazawaf/coraza/blob/master/CONTRIBUTING.md#contribution-guidelines), [maintainers note](https://github.com/corazawaf/coraza/blob/master/CONTRIBUTING.md#maintainers) and [Quality standard](https://github.com/github.com/corazawaf/coraza/v3sso/coraza-waf/blob/master/CONTRIBUTING.md#quality-standards).
- [✅] My code is properly linted and passes pre-commit tests.

Thanks for your contribution :heart:

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/corazawaf/coraza/pull/1125?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 83.14%. Comparing base [(`4ff1f76`)](https://app.codecov.io/gh/corazawaf/coraza/commit/4ff1f760e9a54f61263b6687fdc3a86336c3b76e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf) to head [(`278fd77`)](https://app.codecov.io/gh/corazawaf/coraza/commit/278fd7707ada367ecd06a015cfbfaf09724b2f45?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf).
> Report is 66 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1125      +/-   ##
==========================================
+ Coverage   82.72%   83.14%   +0.42%     
==========================================
  Files         162      164       +2     
  Lines        9080     7690    -1390     
==========================================
- Hits         7511     6394    -1117     
+ Misses       1319     1042     -277     
- Partials      250      254       +4     
```

| [Flag](https://app.codecov.io/gh/corazawaf/coraza/pull/1125/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf) | Coverage Δ | |
|---|---|---|
| [default](https://app.codecov.io/gh/corazawaf/coraza/pull/1125/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf) | `83.14% <100.00%> (+5.30%)` | :arrow_up: |
| [examples](https://app.codecov.io/gh/corazawaf/coraza/pull/1125/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf) | `83.14% <100.00%> (+56.71%)` | :arrow_up: |
| [ftw](https://app.codecov.io/gh/corazawaf/coraza/pull/1125/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf) | `83.14% <100.00%> (+35.77%)` | :arrow_up: |
| [ftw-multiphase](https://app.codecov.io/gh/corazawaf/coraza/pull/1125/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf) | `83.14% <100.00%> (+33.60%)` | :arrow_up: |
| [tinygo](https://app.codecov.io/gh/corazawaf/coraza/pull/1125/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf) | `?` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/corazawaf/coraza/pull/1125?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf).

jcchavezs: thanks, please update this line https://github.com/corazawaf/coraza/pull/1125/files#diff-4f480854f968ff92545ff3bc611fb34a15a657f6caf11dc203c64b76c0861827R148 with the appropriate comment.

Patch:
diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml
index 3ff1a96..546d0dd 100644
--- a/.github/workflows/fuzz.yml
+++ b/.github/workflows/fuzz.yml
@@ -14,7 +14,7 @@ jobs:
       - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
       - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5
         with:
-          go-version: ">=1.21.0"
+          go-version: ">=1.22.0"
       - run: go run mage.go fuzz
       - run: |
           gh issue create --title "$GITHUB_WORKFLOW #$GITHUB_RUN_NUMBER failed" \
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index a3d43e3..52142b6 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -22,6 +22,6 @@ jobs:
       - name: Install Go
         uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5
         with:
-          go-version: v1.21.x
+          go-version: v1.22.x
           cache: true
       - run: go run mage.go lint
diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml
index 3bb0020..bc703b1 100644
--- a/.github/workflows/regression.yml
+++ b/.github/workflows/regression.yml
@@ -16,7 +16,7 @@ jobs:
   test:
     strategy:
       matrix:
-        go-version: [1.21.x, 1.22.x]
+        go-version: [1.22.x, 1.23.x]
         os: [ubuntu-latest]
     runs-on: ${{ matrix.os }}
     steps:
@@ -31,31 +31,31 @@ jobs:
         run: go run mage.go coverage
       - name: "Codecov: General"
         uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4
-        if: ${{ matrix.go-version == '1.21.x' }}
+        if: ${{ matrix.go-version == '1.22.x' }}
         with:
           files: build/coverage.txt
           flags: default
       - name: "Codecov: Examples"
         uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4
-        if: ${{ matrix.go-version == '1.21.x' }}
+        if: ${{ matrix.go-version == '1.22.x' }}
         with:
           files: build/coverage-examples.txt
           flags: examples
       - name: "Codecov: FTW"
         uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4
-        if: ${{ matrix.go-version == '1.21.x' }}
+        if: ${{ matrix.go-version == '1.22.x' }}
         with:
           files: build/coverage-ftw.txt
           flags: ftw
       - name: "Codecov: FTW Multiphase tag"
         uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4
-        if: ${{ matrix.go-version == '1.21.x' }}
+        if: ${{ matrix.go-version == '1.22.x' }}
         with:
           files: build/coverage-ftw-multiphase.txt
           flags: ftw-multiphase
       - name: "Codecov: Tinygo"
         uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4
-        if: ${{ matrix.go-version == '1.21.x' }}
+        if: ${{ matrix.go-version == '1.22.x' }}
         with:
           files: build/coverage-tinygo.txt
           flags: tinygo
diff --git a/.github/workflows/tinygo.yml b/.github/workflows/tinygo.yml
index 8659eb6..0782eed 100644
--- a/.github/workflows/tinygo.yml
+++ b/.github/workflows/tinygo.yml
@@ -18,7 +18,8 @@ jobs:
   test:
     strategy:
       matrix:
-        go-version: [1.21.x]
+        go-version: [1.22.x]
+        tinygo-version: [0.33.0]
         os: [ubuntu-latest]
     runs-on: ${{ matrix.os }}
     steps:
@@ -34,7 +35,7 @@ jobs:
       - name: setup tinygo
         uses: acifani/setup-tinygo@b2ba42b249c7d3efdfe94166ec0f48b3191404f7 # v2
         with:
-          tinygo-version: '0.31.2'
+          tinygo-version: ${{ matrix.tinygo-version }}
 
       - name: Cache TinyGo build
         uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c # v3
diff --git a/.golangci.yml b/.golangci.yml
index 6edd2ac..e7c44cf 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -19,4 +19,4 @@ issues:
   exclude-rules:
     - path: magefile\.go
       linters:
-        - deadcode
+        - deadcode
\ No newline at end of file
diff --git a/README.md b/README.md
index fe03279..18e3461 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@ The Coraza Project maintains implementations and plugins for the following serve
 
 ## Prerequisites
 
-* Go v1.21+ or tinygo compiler
+* Go v1.22+ or tinygo compiler
 * Linux distribution (Debian or Centos recommended), Windows or Mac.
 
 ## Coraza Core Usage
diff --git a/coraza.conf-recommended b/coraza.conf-recommended
index ce311ec..ccc5884 100644
--- a/coraza.conf-recommended
+++ b/coraza.conf-recommended
@@ -161,8 +161,11 @@ SecAuditLogParts ABIJDEFHZ
 #
 SecAuditLogType Serial
 
-# The following settings are not supported by Coraza
+# The format used to write the audit log.
+# Can be one of JSON|JsonLegacy|Native|OCSF
+SecAuditLogFormat Native
 
+# The following settings are not supported by Coraza
 # SecCookieFormat 0
 # SecArgumentSeparator &
 # SecRule MULTIPART_UNMATCHED_BOUNDARY "@eq 1" \
diff --git a/examples/http-server/go.mod b/examples/http-server/go.mod
index 3024f3d..11ab0d9 100644
--- a/examples/http-server/go.mod
+++ b/examples/http-server/go.mod
@@ -1,6 +1,6 @@
 module github.com/corazawaf/coraza/v3/examples/http-server
 
-go 1.21
+go 1.22
 
 require github.com/corazawaf/coraza/v3 v3.2.1
 
@@ -8,10 +8,11 @@ require (
 	github.com/corazawaf/libinjection-go v0.2.1 // indirect
 	github.com/magefile/mage v1.15.0 // indirect
 	github.com/petar-dambovaliev/aho-corasick v0.0.0-20240411101913-e07a1f0e8eb4 // indirect
-	github.com/tidwall/gjson v1.17.1 // indirect
+	github.com/tidwall/gjson v1.17.3 // indirect
 	github.com/tidwall/match v1.1.1 // indirect
 	github.com/tidwall/pretty v1.2.1 // indirect
-	golang.org/x/net v0.27.0 // indirect
-	golang.org/x/sync v0.7.0 // indirect
+	golang.org/x/net v0.29.0 // indirect
+	golang.org/x/sync v0.8.0 // indirect
+	golang.org/x/tools v0.22.0 // indirect
 	rsc.io/binaryregexp v0.2.0 // indirect
 )
diff --git a/examples/http-server/go.sum b/examples/http-server/go.sum
index f7bb758..bde8489 100644
--- a/examples/http-server/go.sum
+++ b/examples/http-server/go.sum
@@ -10,22 +10,22 @@ github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
 github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
 github.com/petar-dambovaliev/aho-corasick v0.0.0-20240411101913-e07a1f0e8eb4 h1:1Kw2vDBXmjop+LclnzCb/fFy+sgb3gYARwfmoUcQe6o=
 github.com/petar-dambovaliev/aho-corasick v0.0.0-20240411101913-e07a1f0e8eb4/go.mod h1:EHPiTAKtiFmrMldLUNswFwfZ2eJIYBHktdaUTZxYWRw=
-github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
-github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94=
+github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
 github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
 github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
 github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
 github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
 github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
-golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
-golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
-golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
-golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
-golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
-golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
-golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
+golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
+golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
+golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
+golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
+golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
 rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
diff --git a/experimental/plugins/plugintypes/auditlog.go b/experimental/plugins/plugintypes/auditlog.go
index b64911d..4742953 100644
--- a/experimental/plugins/plugintypes/auditlog.go
+++ b/experimental/plugins/plugintypes/auditlog.go
@@ -1,4 +1,4 @@
-// Copyright 2023 Juan Pablo Tosso and the OWASP Coraza contributors
+// Copyright 2024 Juan Pablo Tosso and the OWASP Coraza contributors
 // SPDX-License-Identifier: Apache-2.0
 
 package plugintypes
@@ -6,6 +6,7 @@ package plugintypes
 import (
 	"io/fs"
 
+	"github.com/corazawaf/coraza/v3/internal/collections"
 	"github.com/corazawaf/coraza/v3/types"
 )
 
@@ -31,6 +32,8 @@ type AuditLogTransaction interface {
 	Response() AuditLogTransactionResponse
 	HasResponse() bool
 	Producer() AuditLogTransactionProducer
+	HighestSeverity() string // The highest severity of the matched rules for the transaction
+	IsInterrupted() bool     // True if the transaction was interrupted
 }
 
 // AuditLogTransactionResponse contains response specific information
@@ -61,6 +64,8 @@ type AuditLogTransactionRequest interface {
 	Headers() map[string][]string
 	Body() string
 	Files() []AuditLogTransactionRequestFiles
+	Args() *collections.ConcatKeyed // A string representation of all request agruments in the format 'k=v,'
+	Length() int32                  // The total size of the request in bytes
 }
 
 // AuditLogTransactionRequestFiles contains information for the
diff --git a/go.mod b/go.mod
index 90fc8a8..2c2c94f 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/corazawaf/coraza/v3
 
-go 1.21
+go 1.22.3
 
 // Testing dependencies:
 // - go-mockdns
@@ -14,16 +14,18 @@ go 1.21
 // - aho-corasick
 // - gjson
 // - binaryregexp
+// - ocsf-schema-golang
 
 require (
 	github.com/anuraaga/go-modsecurity v0.0.0-20220824035035-b9a4099778df
 	github.com/corazawaf/libinjection-go v0.2.1
 	github.com/foxcpp/go-mockdns v1.1.0
 	github.com/magefile/mage v1.15.0
-	github.com/mccutchen/go-httpbin/v2 v2.14.0
+	github.com/mccutchen/go-httpbin/v2 v2.15.0
 	github.com/petar-dambovaliev/aho-corasick v0.0.0-20240411101913-e07a1f0e8eb4
-	github.com/tidwall/gjson v1.17.1
-	golang.org/x/net v0.28.0
+	github.com/tidwall/gjson v1.17.3
+	github.com/valllabh/ocsf-schema-golang v1.0.3
+	golang.org/x/net v0.29.0
 	golang.org/x/sync v0.8.0
 	rsc.io/binaryregexp v0.2.0
 )
@@ -32,7 +34,8 @@ require (
 	github.com/miekg/dns v1.1.57 // indirect
 	github.com/tidwall/match v1.1.1 // indirect
 	github.com/tidwall/pretty v1.2.1 // indirect
-	golang.org/x/mod v0.17.0 // indirect
-	golang.org/x/sys v0.23.0 // indirect
-	golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
+	golang.org/x/mod v0.18.0 // indirect
+	golang.org/x/sys v0.25.0 // indirect
+	golang.org/x/tools v0.22.0 // indirect
+	google.golang.org/protobuf v1.34.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 4fb4851..157ba05 100644
--- a/go.sum
+++ b/go.sum
@@ -4,21 +4,27 @@ github.com/corazawaf/libinjection-go v0.2.1 h1:vNJ7L6c4xkhRgYU6sIO0Tl54TmeCQv/yf
 github.com/corazawaf/libinjection-go v0.2.1/go.mod h1:OP4TM7xdJ2skyXqNX1AN1wN5nNZEmJNuWbNPOItn7aw=
 github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI=
 github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
 github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
 github.com/mccutchen/go-httpbin/v2 v2.14.0 h1:9N7GUf8+JunYMFd+yHPIVYApC6KYgqtF0pHIcTGYcVQ=
 github.com/mccutchen/go-httpbin/v2 v2.14.0/go.mod h1:f4DUXYlU6yH0V81O4lJIwqpmYdTXXmYwzxMnYEimFPk=
+github.com/mccutchen/go-httpbin/v2 v2.15.0 h1:3b2s8LMRR2aFd+8U+1Bx2kdgHNQ5ZQkQOiW8e52Jj9A=
+github.com/mccutchen/go-httpbin/v2 v2.15.0/go.mod h1:GBy5I7XwZ4ZLhT3hcq39I4ikwN9x4QUt6EAxNiR8Jus=
 github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
 github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
 github.com/petar-dambovaliev/aho-corasick v0.0.0-20240411101913-e07a1f0e8eb4 h1:1Kw2vDBXmjop+LclnzCb/fFy+sgb3gYARwfmoUcQe6o=
 github.com/petar-dambovaliev/aho-corasick v0.0.0-20240411101913-e07a1f0e8eb4/go.mod h1:EHPiTAKtiFmrMldLUNswFwfZ2eJIYBHktdaUTZxYWRw=
-github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
-github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94=
+github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
 github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
 github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
 github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
 github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
 github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/valllabh/ocsf-schema-golang v1.0.3 h1:eR8k/3jP/OOqB8LRCtdJ4U+vlgd/gk5y3KMXoodrsrw=
+github.com/valllabh/ocsf-schema-golang v1.0.3/go.mod h1:sZ3as9xqm1SSK5feFWIR2CuGeGRhsM7TR1MbpBctzPk=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
@@ -29,8 +35,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
-golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
+golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@@ -39,18 +45,14 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
 golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
-golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
-golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
-golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
-golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
+golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
+golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
-golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
 golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -63,10 +65,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
-golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
-golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
+golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -87,8 +87,10 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
 golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
-golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
-golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
+golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
+golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
+google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
diff --git a/go.work b/go.work
index b3d2a35..6907c6b 100644
--- a/go.work
+++ b/go.work
@@ -1,4 +1,4 @@
-go 1.21
+go 1.22.3
 
 use (
 	.
diff --git a/http/interceptor.go b/http/interceptor.go
index 06d7656..0fa9feb 100644
--- a/http/interceptor.go
+++ b/http/interceptor.go
@@ -83,7 +83,11 @@ func (i *rwInterceptor) Write(b []byte) (int, error) {
 		// if there is an interruption it must be from at least phase 4 and hence
 		// WriteHeader or Write should have been called and hence the status code
 		// has been flushed to the delegated response writer.
-		return 0, nil
+		//
+		// We return the number of bytes as according to the interface io.Writer
+		// if we don't return an error, the number of bytes written is len(p).
+		// See https://pkg.go.dev/io#Writer
+		return len(b), nil
 	}
 
 	if !i.wroteHeader {
@@ -102,7 +106,10 @@ func (i *rwInterceptor) Write(b []byte) (int, error) {
 			i.overrideWriteHeader(obtainStatusCodeFromInterruptionOrDefault(it, i.statusCode))
 			// We only flush the status code after an interruption.
 			i.flushWriteHeader()
-			return 0, nil
+			// We return the number of bytes as according to the interface io.Writer
+			// if we don't return an error, the number of bytes written is len(p).
+			// See https://pkg.go.dev/io#Writer
+			return len(b), nil
 		}
 		return n, err
 	}
diff --git a/internal/actions/setvar.go b/internal/actions/setvar.go
index 2c36352..3999217 100644
--- a/internal/actions/setvar.go
+++ b/internal/actions/setvar.go
@@ -145,16 +145,23 @@ func (a *setvarFn) evaluateTxCollection(r plugintypes.RuleMetadata, tx plugintyp
 	case len(value) == 0:
 		// if nothing to input
 		col.Set(key, []string{""})
-	case value[0] == '+', value[0] == '-': // Increment or decrement, arithmetical operations
+	// Check if this could be an arithemetic operation. If it is followed by a number, it will be treated as an arithmetic operation. Otherwise, it will be treated as a string.
+	case value[0] == '+', value[0] == '-':
 		val := 0
 		if len(value) > 1 {
 			val, err = strconv.Atoi(value[1:])
 			if err != nil {
-				tx.DebugLogger().Error().
-					Str("var_value", value).
-					Int("rule_id", r.ID()).
-					Err(err).
-					Msg("Invalid value")
+				// If the variable doesn't exist, we would need to raise an error. Otherwise, it should be the same value.
+				if strings.HasPrefix(value[1:], "tx.") {
+					tx.DebugLogger().Error().
+						Str("var_value", value).
+						Int("rule_id", r.ID()).
+						Err(err).
+						Msg(value)
+					return
+				}
+
+				col.Set(key, []string{value})
 				return
 			}
 		}
diff --git a/internal/auditlog/auditlog.go b/internal/auditlog/auditlog.go
index 9ff0e35..7a0383b 100644
--- a/internal/auditlog/auditlog.go
+++ b/internal/auditlog/auditlog.go
@@ -1,4 +1,4 @@
-// Copyright 2022 Juan Pablo Tosso and the OWASP Coraza contributors
+// Copyright 2024 Juan Pablo Tosso and the OWASP Coraza contributors
 // SPDX-License-Identifier: Apache-2.0
 
 package auditlog
@@ -7,6 +7,7 @@ import (
 	"encoding/json"
 
 	"github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes"
+	"github.com/corazawaf/coraza/v3/internal/collections"
 	"github.com/corazawaf/coraza/v3/types"
 )
 
@@ -77,13 +78,15 @@ type Transaction struct {
 	// Client IP Address string representation
 	ClientIP_ string `json:"client_ip"`
 
-	ClientPort_ int                  `json:"client_port"`
-	HostIP_     string               `json:"host_ip"`
-	HostPort_   int                  `json:"host_port"`
-	ServerID_   string               `json:"server_id"`
-	Request_    *TransactionRequest  `json:"request,omitempty"`
-	Response_   *TransactionResponse `json:"response,omitempty"`
-	Producer_   *TransactionProducer `json:"producer,omitempty"`
+	ClientPort_      int                  `json:"client_port"`
+	HostIP_          string               `json:"host_ip"`
+	HostPort_        int                  `json:"host_port"`
+	ServerID_        string               `json:"server_id"`
+	Request_         *TransactionRequest  `json:"request,omitempty"`
+	Response_        *TransactionResponse `json:"response,omitempty"`
+	Producer_        *TransactionProducer `json:"producer,omitempty"`
+	HighestSeverity_ string               `json:"highest_severity"`
+	IsInterrupted_   bool                 `json:"is_interrupted"`
 }
 
 var _ plugintypes.AuditLogTransaction = Transaction{}
@@ -140,6 +143,14 @@ func (t Transaction) Producer() plugintypes.AuditLogTransactionProducer {
 	return t.Producer_
 }
 
+func (t Transaction) HighestSeverity() string {
+	return t.HighestSeverity_
+}
+
+func (t Transaction) IsInterrupted() bool {
+	return t.IsInterrupted_
+}
+
 // TransactionResponse contains response specific
 // information
 type TransactionResponse struct {
@@ -197,26 +208,50 @@ type TransactionProducer struct {
 var _ plugintypes.AuditLogTransactionProducer = (*TransactionProducer)(nil)
 
 func (tp *TransactionProducer) Connector() string {
+	if tp == nil {
+		return ""
+	}
+
 	return tp.Connector_
 }
 
 func (tp *TransactionProducer) Version() string {
+	if tp == nil {
+		return ""
+	}
+
 	return tp.Version_
 }
 
 func (tp *TransactionProducer) Server() string {
+	if tp == nil {
+		return ""
+	}
+
 	return tp.Server_
 }
 
 func (tp *TransactionProducer) RuleEngine() string {
+	if tp == nil {
+		return ""
+	}
+
 	return tp.RuleEngine_
 }
 
 func (tp *TransactionProducer) Stopwatch() string {
+	if tp == nil {
+		return ""
+	}
+
 	return tp.Stopwatch_
 }
 
 func (tp *TransactionProducer) Rulesets() []string {
+	if tp == nil {
+		return nil
+	}
+
 	return tp.Rulesets_
 }
 
@@ -230,6 +265,8 @@ type TransactionRequest struct {
 	Headers_     map[string][]string                           `json:"headers"`
 	Body_        string                                        `json:"body"`
 	Files_       []plugintypes.AuditLogTransactionRequestFiles `json:"files"`
+	Args_        *collections.ConcatKeyed                      `json:"args"`
+	Length_      int32                                         `json:"length"`
 }
 
 var _ plugintypes.AuditLogTransactionRequest = (*TransactionRequest)(nil)
@@ -286,6 +323,21 @@ func (tr *TransactionRequest) Files() []plugintypes.AuditLogTransactionRequestFi
 	return tr.Files_
 }
 
+func (tr *TransactionRequest) Args() *collections.ConcatKeyed {
+	if tr == nil {
+		return &collections.ConcatKeyed{}
+	}
+
+	return tr.Args_
+}
+
+func (tr *TransactionRequest) Length() int32 {
+	if tr == nil {
+		return 0
+	}
+	return tr.Length_
+}
+
 // TransactionRequestFiles contains information
 // for the uploaded files using multipart forms
 type TransactionRequestFiles struct {
diff --git a/internal/auditlog/formats_ocsf.go b/internal/auditlog/formats_ocsf.go
new file mode 100644
index 0000000..ca3c695
--- /dev/null
+++ b/internal/auditlog/formats_ocsf.go
@@ -0,0 +1,257 @@
+// Copyright 2024 Juan Pablo Tosso and the OWASP Coraza contributors
+// SPDX-License-Identifier: Apache-2.0
+
+// OCSF log format
+// OCSF (Open Cybersecurity Schema Framework) (https://github.com/ocsf) is an open-source framework with the goal of providing an open standard for logging security events.
+// This log format will produce a JSON log which adheres to the OCSF schema (https://schema.ocsf.io/)
+
+package auditlog
+
+import (
+	"encoding/json"
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/valllabh/ocsf-schema-golang/ocsf/v1_2_0/events/application"
+	"github.com/valllabh/ocsf-schema-golang/ocsf/v1_2_0/events/application/enums"
+	"github.com/valllabh/ocsf-schema-golang/ocsf/v1_2_0/objects"
+	ocsf_object_enums "github.com/valllabh/ocsf-schema-golang/ocsf/v1_2_0/objects/enums"
+
+	"github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes"
+	"github.com/corazawaf/coraza/v3/types"
+)
+
+type ocsfFormatter struct{}
+
+func (f ocsfFormatter) getRequestArguments(al plugintypes.AuditLog) string {
+	argString := &strings.Builder{}
+	if al.Transaction().Request().Args() != nil {
+		args := al.Transaction().Request().Args().FindAll()
+
+		argCount := len(args)
+		for i, arg := range al.Transaction().Request().Args().FindAll() {
+			argString.WriteString(fmt.Sprintf("%s=%s", arg.Key(), arg.Value()))
+			if i < argCount {
+				argString.WriteString(",")
+			}
+		}
+	}
+	return argString.String()
+}
+
+func (f ocsfFormatter) getRequestHeaders(al plugintypes.AuditLog) []*objects.HttpHeader {
+	requestHeaders := []*objects.HttpHeader{}
+	for key, values := range al.Transaction().Request().Headers() {
+		for _, value := range values {
+			requestHeaders = append(requestHeaders, &objects.HttpHeader{
+				Name:  key,
+				Value: value,
+			})
+		}
+	}
+	return requestHeaders
+}
+
+func (f ocsfFormatter) getResponseHeaders(al plugintypes.AuditLog) []*objects.HttpHeader {
+	responseHeaders := []*objects.HttpHeader{}
+	for key, values := range al.Transaction().Response().Headers() {
+		for _, value := range values {
+			responseHeaders = append(responseHeaders, &objects.HttpHeader{
+				Name:  key,
+				Value: value,
+			})
+		}
+	}
+	return responseHeaders
+}
+
+func (f ocsfFormatter) getAffectedWebResources(al plugintypes.AuditLog) []*objects.WebResource {
+	// Create an array of web Resources affected by this activity
+	webResources := []*objects.WebResource{}
+	webResources = append(webResources, &objects.WebResource{
+		UrlString: al.Transaction().Request().URI(),
+	})
+
+	return webResources
+}
+
+// Returns an array of Enrichment objects containing the details of each message in AuditLog.Messages
+func (f ocsfFormatter) getMatchDetails(al plugintypes.AuditLog) []*objects.Enrichment {
+	matchDetails := []*objects.Enrichment{}
+
+	for _, match := range al.Messages() {
+		matchData, _ := json.Marshal(match.Data())
+		matchDetails = append(matchDetails, &objects.Enrichment{
+			Data:  string(matchData),
+			Name:  match.Data().Msg(),
+			Value: match.Data().Data(),
+		})
+	}
+
+	return matchDetails
+}
+
+// Returns an array of Observable objects
+func (f ocsfFormatter) getObservables(al plugintypes.AuditLog) []*objects.Observable {
+	observables := []*objects.Observable{}
+
+	if al.Transaction().ServerID() != "" {
+		observables = append(observables, &objects.Observable{
+			Name:   "ServerID",
+			Type:   "ServerID",
+			TypeId: ocsf_object_enums.OBSERVABLE_TYPE_ID_OBSERVABLE_TYPE_ID_OTHER,
+			Value:  al.Transaction().ServerID(),
+		})
+	}
+
+	for _, file := range al.Transaction().Request().Files() {
+		observables = append(observables, &objects.Observable{
+			Name:   file.Name(),
+			Type:   "File Name",
+			TypeId: 7,
+			Value:  file.Name(),
+		})
+		observables = append(observables, &objects.Observable{
+			Name:   file.Name(),
+			Type:   "Size",
+			TypeId: ocsf_object_enums.OBSERVABLE_TYPE_ID_OBSERVABLE_TYPE_ID_OTHER,
+			Value:  fmt.Sprint(file.Size()),
+		})
+		observables = append(observables, &objects.Observable{
+			Name:   file.Name(),
+			Type:   "Mime",
+			TypeId: ocsf_object_enums.OBSERVABLE_TYPE_ID_OBSERVABLE_TYPE_ID_OTHER,
+			Value:  file.Mime(),
+		})
+	}
+
+	return observables
+}
+
+func (f ocsfFormatter) Format(al plugintypes.AuditLog) ([]byte, error) {
+
+	// Determine the Action/ActionID based on whether the transaction was interrutped
+	ActionID := enums.WEB_RESOURCES_ACTIVITY_ACTION_ID_WEB_RESOURCES_ACTIVITY_ACTION_ID_ALLOWED
+	Action := "Allowed"
+	if al.Transaction().IsInterrupted() {
+		ActionID = enums.WEB_RESOURCES_ACTIVITY_ACTION_ID_WEB_RESOURCES_ACTIVITY_ACTION_ID_DENIED
+		Action = "Denied"
+
+	}
+
+	// Populate the required fields for the WebRecourcesActivity
+	webResourcesActivity := application.WebResourcesActivity{
+		ActivityId:   enums.WEB_RESOURCES_ACTIVITY_ACTIVITY_ID_WEB_RESOURCES_ACTIVITY_ACTIVITY_ID_READ,
+		ActivityName: "Read",
+		CategoryName: "Application Activity",
+		ClassName:    "Web Resources Activity",
+		CategoryUid:  enums.WEB_RESOURCES_ACTIVITY_CATEGORY_UID_WEB_RESOURCES_ACTIVITY_CATEGORY_UID_APPLICATION_ACTIVITY,
+		ClassUid:     enums.WEB_RESOURCES_ACTIVITY_CLASS_UID_WEB_RESOURCES_ACTIVITY_CLASS_UID_WEB_RESOURCES_ACTIVITY,
+		Time:         al.Transaction().UnixTimestamp(),
+		ActionId:     ActionID,
+		Action:       Action,
+		Metadata: &objects.Metadata{
+			CorrelationUid: "",
+			EventCode:      "",
+			//Labels:         [2]string{"", ""},
+			LogLevel: "",
+			LogName:  "",
+			//LogProvider: "OWASP Coraza Web Application Firewall",
+			LogProvider: al.Transaction().Producer().Connector(),
+			LogVersion:  al.Transaction().Producer().Version(),
+			LoggedTime:  time.Now().UnixMicro(),
+			Product: &objects.Product{
+				VendorName: "OWASP Coraza Web Application Firewall",
+			},
+			Version: "1.2.0",
+		},
+		TypeUid:     enums.WEB_RESOURCES_ACTIVITY_TYPE_UID_WEB_RESOURCES_ACTIVITY_TYPE_UID_WEB_RESOURCES_ACTIVITY_READ,
+		Enrichments: f.getMatchDetails(al),
+		HttpRequest: &objects.HttpRequest{
+			Version:     al.Transaction().Request().Protocol(),
+			Args:        f.getRequestArguments(al),
+			HttpMethod:  al.Transaction().Request().Method(),
+			Uid:         al.Transaction().ID(),
+			Url:         &objects.Url{UrlString: al.Transaction().Request().URI()},
+			HttpHeaders: f.getRequestHeaders(al),
+			Length:      al.Transaction().Request().Length(),
+		},
+		HttpResponse: &objects.HttpResponse{
+			Code:        int32(al.Transaction().Response().Status()),
+			HttpHeaders: f.getResponseHeaders(al),
+		},
+		SrcEndpoint: &objects.NetworkEndpoint{
+			Ip:   al.Transaction().ClientIP(),
+			Port: int32(al.Transaction().ClientPort()),
+		},
+		DstEndpoint: &objects.NetworkEndpoint{
+			Ip:   al.Transaction().HostIP(),
+			Port: int32(al.Transaction().HostPort()),
+		},
+		WebResources: f.getAffectedWebResources(al),
+	}
+
+	userAgent := al.Transaction().Request().Headers()["user-agent"]
+	if len(userAgent) > 0 {
+		webResourcesActivity.HttpRequest.UserAgent = userAgent[0]
+	}
+
+	// Note: 'referer' is a misspelling of 'referrer' but was incorporated into the HTTP specification with this misspelling
+	// see https://en.wikipedia.org/wiki/HTTP_referer
+	referrer := al.Transaction().Request().Headers()["referer"]
+	if len(referrer) > 0 {
+		webResourcesActivity.HttpRequest.Referrer = referrer[0]
+	}
+
+	xForwardedFor := al.Transaction().Request().Headers()["x-forwarded-for"]
+	if len(xForwardedFor) > 0 {
+		webResourcesActivity.HttpRequest.XForwardedFor = xForwardedFor
+	}
+
+	if len(al.Messages()) > 0 {
+		message := al.Messages()[0]
+		webResourcesActivity.Message = message.Message()
+	}
+
+	_, offset := time.Now().Zone()
+	webResourcesActivity.TimezoneOffset = int32(offset)
+
+	// The WebResource Activity Severity ID is not to be confused by the Transaction severity.  The Transaction severity has to do with Coraza error/debug severity,
+	// while WebResource Activity Severity is defined by OCSF to represent the severity of the security event.
+	// For now, we're setting severityID to 'Other' and setting Severity to the Highest severity of the matched rules.
+	// A future update should map/translate rule severity to OCSF severity if possible.
+	highestSeverity, _ := types.ParseRuleSeverity(al.Transaction().HighestSeverity())
+	webResourcesActivity.Severity = highestSeverity.String()
+	webResourcesActivity.SeverityId = enums.WEB_RESOURCES_ACTIVITY_SEVERITY_ID_WEB_RESOURCES_ACTIVITY_SEVERITY_ID_OTHER
+
+	webResourcesActivity.StartTime = al.Transaction().UnixTimestamp()
+	webResourcesActivity.TypeName = "Read"
+
+	webResourcesActivity.Observables = f.getObservables(al)
+
+	// Not implemented
+	// webResourcesActivity.Count = 0
+	// webResourcesActivity.Duration = 0
+	// webResourcesActivity.EndTime = 0
+	// webResourcesActivity.RawData = ""
+	// webResourcesActivity.Status = ""
+	// webResourcesActivity.StatusCode = ""
+	// webResourcesActivity.StatusDetail = ""
+	// webResourcesActivity.StatusId = enums.WEB_RESOURCES_ACTIVITY_STATUS_ID_WEB_RESOURCES_ACTIVITY_STATUS_ID_UNKNOWN
+	// webResourcesActivity.Tls = &objects.Tls{}
+	// webResourcesActivity.WebResourcesResult =
+	// webResourcesActivity.Unmapped = nil
+
+	logJson, _ := json.Marshal(&webResourcesActivity)
+
+	return logJson, nil
+}
+
+func (ocsfFormatter) MIME() string {
+	return "application/json"
+}
+
+var (
+	_ plugintypes.AuditLogFormatter = (*ocsfFormatter)(nil)
+)
diff --git a/internal/auditlog/init.go b/internal/auditlog/init.go
index 6984da4..41a16e6 100644
--- a/internal/auditlog/init.go
+++ b/internal/auditlog/init.go
@@ -22,4 +22,5 @@ func init() {
 	RegisterFormatter("json", &jsonFormatter{})
 	RegisterFormatter("jsonlegacy", &legacyJSONFormatter{})
 	RegisterFormatter("native", &nativeFormatter{})
+	RegisterFormatter("ocsf", &ocsfFormatter{})
 }
diff --git a/internal/auditlog/init_tinygo.go b/internal/auditlog/init_tinygo.go
index ebf5244..b10fd79 100644
--- a/internal/auditlog/init_tinygo.go
+++ b/internal/auditlog/init_tinygo.go
@@ -22,4 +22,5 @@ func init() {
 	RegisterFormatter("json", &jsonFormatter{})
 	RegisterFormatter("jsonlegacy", &legacyJSONFormatter{})
 	RegisterFormatter("native", &nativeFormatter{})
+	RegisterFormatter("ocsf", &ocsfFormatter{})
 }
diff --git a/internal/corazawaf/rule.go b/internal/corazawaf/rule.go
index a1cbbf2..bfff25e 100644
--- a/internal/corazawaf/rule.go
+++ b/internal/corazawaf/rule.go
@@ -399,7 +399,7 @@ func (r *Rule) transformArg(arg types.MatchData, argIdx int, cache map[transform
 		default:
 			// NOTE: See comment on transformationKey struct to understand this hacky code
 			argKey := arg.Key()
-			argKeyPtr := (*reflect.StringHeader)(unsafe.Pointer(&argKey)).Data
+			argKeyPtr := (*reflect.StringHeader)(unsafe.Pointer(&argKey)).Data // nolint:staticcheck
 			key := transformationKey{
 				argKey:            argKeyPtr,
 				argIndex:          argIdx,
diff --git a/internal/corazawaf/transaction.go b/internal/corazawaf/transaction.go
index 880c622..36159c6 100644
--- a/internal/corazawaf/transaction.go
+++ b/internal/corazawaf/transaction.go
@@ -67,6 +67,7 @@ type Transaction struct {
 	// Copies from the WAF instance that may be overwritten by the ctl action
 	AuditEngine               types.AuditEngineStatus
 	AuditLogParts             types.AuditLogParts
+	AuditLogFormat            string
 	ForceRequestBodyVariable  bool
 	RequestBodyAccess         bool
 	RequestBodyLimit          int64
@@ -1372,6 +1373,18 @@ func (tx *Transaction) AuditLog() *auditlog.Log {
 
 	clientPort, _ := strconv.Atoi(tx.variables.remotePort.Get())
 	hostPort, _ := strconv.Atoi(tx.variables.serverPort.Get())
+
+	// Convert the transaction fullRequestLength to Int32
+	requestLength, err := strconv.ParseInt(tx.variables.fullRequestLength.Get(), 10, 32)
+	if err != nil {
+		requestLength = 0
+		tx.DebugLogger().Error().
+			Str("transaction", "AuditLog").
+			Str("value", tx.variables.fullRequestLength.Get()).
+			Err(err).
+			Msg("Error converting request length to integer")
+	}
+
 	// YYYY/MM/DD HH:mm:ss
 	ts := time.Unix(0, tx.Timestamp).Format("2006/01/02 15:04:05")
 	al.Transaction_ = auditlog.Transaction{
@@ -1387,7 +1400,10 @@ func (tx *Transaction) AuditLog() *auditlog.Log {
 			Method_:   tx.variables.requestMethod.Get(),
 			URI_:      tx.variables.requestURI.Get(),
 			Protocol_: tx.variables.requestProtocol.Get(),
+			Args_:     tx.variables.args,
+			Length_:   int32(requestLength),
 		},
+		IsInterrupted_: tx.IsInterrupted(),
 	}
 
 	for _, part := range tx.AuditLogParts {
diff --git a/internal/corazawaf/waf.go b/internal/corazawaf/waf.go
index e6946a3..e4430a8 100644
--- a/internal/corazawaf/waf.go
+++ b/internal/corazawaf/waf.go
@@ -119,6 +119,9 @@ type WAF struct {
 	// Array of logging parts to be used
 	AuditLogParts types.AuditLogParts
 
+	// Audit log format
+	AuditLogFormat string
+
 	// Contains the regular expression for relevant status audit logging
 	AuditLogRelevantStatus *regexp.Regexp
 
@@ -174,6 +177,7 @@ func (w *WAF) newTransaction(opts Options) *Transaction {
 	tx.SkipAfter = ""
 	tx.AuditEngine = w.AuditEngine
 	tx.AuditLogParts = w.AuditLogParts
+	tx.AuditLogFormat = w.AuditLogFormat
 	tx.ForceRequestBodyVariable = false
 	tx.RequestBodyAccess = w.RequestBodyAccess
 	tx.RequestBodyLimit = int64(w.RequestBodyLimit)
@@ -305,8 +309,9 @@ func NewWAF() *WAF {
 			types.AuditLogPartResponseHeaders,
 			types.AuditLogPartAuditLogTrailer,
 		},
-		Logger:        logger,
-		ArgumentLimit: 1000,
+		AuditLogFormat: "Native",
+		Logger:         logger,
+		ArgumentLimit:  1000,
 	}
 
 	if environment.HasAccessToFS {
diff --git a/internal/seclang/directives.go b/internal/seclang/directives.go
index 4d2350f..b6670c2 100644
--- a/internal/seclang/directives.go
+++ b/internal/seclang/directives.go
@@ -646,9 +646,9 @@ func directiveSecAuditLogType(options *DirectiveOptions) error {
 	return nil
 }
 
-// Description: Select the output format of the AuditLogs. The format can be either
-// the native AuditLogs format or JSON.
-// Syntax: SecAuditLogFormat JSON|Native
+// Description: Select the output format of the AuditLogs. The format can be
+// the native AuditLogs format, JSON, or OCSF (Open CyberSecurity Schema Framework).
+// Syntax: SecAuditLogFormat JSON|JsonLegacy|Native|OCSF
 // Default: Native
 func directiveSecAuditLogFormat(options *DirectiveOptions) error {
 	if len(options.Opts) == 0 {
diff --git a/internal/seclang/rule_parser.go b/internal/seclang/rule_parser.go
index 77b61ab..e80368c 100644
--- a/internal/seclang/rule_parser.go
+++ b/internal/seclang/rule_parser.go
@@ -76,7 +76,7 @@ func (rp *RuleParser) ParseVariables(vars string) error {
 				if len(vars) <= i+1 || vars[i+1] != '\'' {
 					if vars[i] != '\'' {
 						// TODO fix here
-						return fmt.Errorf("unclosed quote: " + string(curKey))
+						return fmt.Errorf("unclosed quote: %q", string(curKey))
 					}
 				}
 				// we skip one additional character
diff --git a/magefile.go b/magefile.go
index 905cfb9..2bc87e1 100644
--- a/magefile.go
+++ b/magefile.go
@@ -20,7 +20,7 @@ import (
 
 var addLicenseVersion = "v1.1.1" // https://github.com/google/addlicense/releases
 var gosImportsVer = "v0.3.7"     // https://github.com/rinchsan/gosimports/releases
-var golangCILintVer = "v1.54.0"  // https://github.com/golangci/golangci-lint/releases
+var golangCILintVer = "v1.60.3"  // https://github.com/golangci/golangci-lint/releases
 var errNoGitDir = errors.New("no .git directory found")
 var errUpdateGeneratedFiles = errors.New("generated files need to be updated")
 
diff --git a/renovate.json b/renovate.json
index 599cfc5..f1b6d23 100644
--- a/renovate.json
+++ b/renovate.json
@@ -4,6 +4,6 @@
     "config:best-practices"
   ],
   "constraints": {
-    "go": "1.21"
+    "go": "1.22"
   }
 }


Test patch:
diff --git a/internal/actions/setvar_test.go b/internal/actions/setvar_test.go
index 591d5a8..48966e8 100644
--- a/internal/actions/setvar_test.go
+++ b/internal/actions/setvar_test.go
@@ -96,6 +96,18 @@ func TestSetvarEvaluate(t *testing.T) {
 			init:                     "TX.newvar=-%{tx.missingvar}",
 			expectInvalidSyntaxError: true,
 		},
+		{
+			name:                     "Non Numerical Operation - If the value starts with -",
+			init:                     "TX.newvar=----expected_value",
+			expectInvalidSyntaxError: false,
+			expectNewVarValue:        "----expected_value",
+		},
+		{
+			name:                     "Non Numerical Operation - If the value starts with +",
+			init:                     "TX.newvar=+++expected_value",
+			expectInvalidSyntaxError: false,
+			expectNewVarValue:        "+++expected_value",
+		},
 	}
 
 	for _, tt := range tests {
diff --git a/internal/auditlog/auditlog_test.go b/internal/auditlog/auditlog_test.go
index d7b1950..089640c 100644
--- a/internal/auditlog/auditlog_test.go
+++ b/internal/auditlog/auditlog_test.go
@@ -1,4 +1,4 @@
-// Copyright 2023 Juan Pablo Tosso and the OWASP Coraza contributors
+// Copyright 2024 Juan Pablo Tosso and the OWASP Coraza contributors
 // SPDX-License-Identifier: Apache-2.0
 
 //go:build !tinygo
@@ -6,20 +6,188 @@
 
 package auditlog
 
-import "testing"
+import (
+	"slices"
+	"testing"
+)
+
+func TestAuditLogUnmarshalInvalidJSON(t *testing.T) {
+	// Improper JSON data (missing a closing '}')
+	invalidSerializedLog := []byte(`{
+		"transaction": {
+			"id":
+	}`)
+
+	log := &Log{}
+
+	// Verify a error is returned for invalidly formatted JSON data
+	if err := log.UnmarshalJSON(invalidSerializedLog); err != nil {
+		if err.Error() != "invalid character '}' looking for beginning of value" {
+			t.Errorf("failed to match error message, \ngot: %s, \nexpected: %s", err, "invalid character '}' looking for beginning of value")
+		}
+
+	}
+}
+
+func TestAuditLogUnmarshalEmptyJSON(t *testing.T) {
+	serializedLog := []byte(`{
+		"transaction": {
+		}
+	}`)
+
+	log := &Log{}
+
+	// Validate proper results for nil values
+	if err := log.UnmarshalJSON(serializedLog); err != nil {
+		t.Error(err)
+	}
+
+	if want, have := "", log.Transaction().ID(); want != have {
+		t.Errorf("failed to match transaction id, got: %s, expected: %s", have, want)
+	}
+
+	// Validate Transaction Request parameters
+	if want, have := false, log.Transaction().HasRequest(); want != have {
+		t.Errorf("failed to match transaction has request, got: %t, expected: %t", have, want)
+	}
+
+	if want, have := "", log.Transaction().Request().Method(); want != have {
+		t.Errorf("failed to match transaction request method, got: %s, expected: %s", have, want)
+	}
+
+	if want, have := "", log.Transaction().Request().HTTPVersion(); want != have {
+		t.Errorf("failed to match transaction request method, got: %s, expected: %s", have, want)
+	}
+
+	// Validate Transaction Response parameters
+	if want, have := false, log.Transaction().HasResponse(); want != have {
+		t.Errorf("failed to match transaction has response, got: %t, expected: %t", have, want)
+	}
+
+	if want, have := 0, log.Transaction().Response().Status(); want != have {
+		t.Errorf("failed to match transaction has response, got: %d, expected: %d", have, want)
+	}
+
+	if want, have := "", log.Transaction().Response().Protocol(); want != have {
+		t.Errorf("failed to match transaction has response, got: %s, expected: %s", have, want)
+	}
+
+	// Validate log messages
+	if want, have := 0, len(log.Messages()); want != have {
+		t.Errorf("failed to match messages length, got: %d, expected: %d", have, want)
+	}
+
+	// Validaate Transaction Producer parameters
+	if want, have := "", log.Transaction().Producer().Connector(); want != have {
+		t.Errorf("failed to match producer connector, got: %s, expected: %s", have, want)
+	}
+
+	if want, have := "", log.Transaction().Producer().Version(); want != have {
+		t.Errorf("failed to match producer version, got: %s, expected: %s", have, want)
+	}
+
+	if want, have := "", log.Transaction().Producer().Server(); want != have {
+		t.Errorf("failed to match producer server, got: %s, expected: %s", have, want)
+	}
+
+	if want, have := "", log.Transaction().Producer().RuleEngine(); want != have {
+		t.Errorf("failed to match producer rule engine, got: %s, expected: %s", have, want)
+	}
+
+	if want, have := "", log.Transaction().Producer().Stopwatch(); want != have {
+		t.Errorf("failed to match producer stopwatch, got: %s, expected: %s", have, want)
+	}
+
+	if have := log.Transaction().Producer().Rulesets(); nil != have {
+		t.Errorf("failed to match producer ruleset, got: %s, expected: nil", have)
+	}
+
+	if want, have := false, log.Transaction_.HasResponse(); want != have {
+		t.Errorf("failed to match transaction has response, got: %t, expected: %t", have, want)
+	}
+
+	if want, have := 0, log.Transaction().Response().Status(); want != have {
+		t.Errorf("failed to match producer server, got: %d, expected: %d", have, want)
+	}
+
+	if want, have := false, log.Transaction_.HasRequest(); want != have {
+		t.Errorf("failed to match transaction has request, got: %t, expected: %t", have, want)
+	}
+}
 
 func TestAuditLogUnmarshalJSON(t *testing.T) {
+
 	serializedLog := []byte(`{
 		"transaction": {
-			"id": "abc123"
+			"id": "abc123",
+			"producer": {
+				"connector": "c",
+				"version": "d",
+				"server": "e",
+				"rule_engine": "f",
+				"stopwatch": "g",
+				"rulesets": [
+					"h",
+					"i"
+				]
+			},
+			"request": {
+				"method": "",
+				"protocol": "",
+				"uri": "",
+				"http_version": "",
+				"body": "",
+				"length": 123,
+				"uid": "",
+				"headers":{
+					"request_header_key": [
+						"request_header_value"
+					]
+				}
+					
+			},
+			"response": {
+				"status": 200,
+				"protocol": "p",
+				"body": "b",
+				"headers":{
+					"response_header_key": [
+						"response_header_value"
+					]
+				}
+			}
 		},
 		"messages": [
 			{
 				"actionset": "a",
-				"message": "b"
+				"message": "b",
+				"data": {
+					"file": "/etc/coraza-spoa/rules/REQUEST-930-APPLICATION-ATTACK-LFI.conf",
+					"line": 4468,
+					"id": 930130,
+					"rev": "1.3",
+					"msg": "Restricted File Access Attempt",
+					"data": "Matched Data: /.git/ found within REQUEST_FILENAME: /.git/config",
+					"severity": 2,
+					"ver": "OWASP_CRS/4.4.0-dev",
+					"maturity": 3,
+					"accuracy": 8,
+					"tags": [
+						"application-multi",
+						"language-multi",
+						"platform-multi",
+						"attack-lfi",
+						"paranoia-level/1",
+						"OWASP_CRS",
+						"capec/1000/255/153/126",
+						"PCI/6.5.4"
+					],
+					"raw": "SecRule REQUEST_FILENAME \"@pmFromFile restricted-files.data\" \"id: 930130,phase: 1,block,capture,t:none,t:utf8toUnicode,t:urlDecodeUni,t:normalizePathWin,msg:\"Restricted File Access Attempt\",logdata:\"Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}\",tag:\"application-multi\",tag:\"language-multi\",tag:\"platform-multi\",tag:\"attack-lfi\",tag:\"paranoia-level/1\",tag:\"OWASP_CRS\",tag:\"capec/1000/255/153/126\",tag:\"PCI/6.5.4\",ver:\"OWASP_CRS/4.4.0-dev\",severity:\"CRITICAL\",setvar:\"tx.lfi_score=+%{tx.critical_anomaly_score}\",setvar:\"tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}\"\""
+				}
 			}
 		]
 	}`)
+
 	log := &Log{}
 	if err := log.UnmarshalJSON(serializedLog); err != nil {
 		t.Error(err)
@@ -29,18 +197,41 @@ func TestAuditLogUnmarshalJSON(t *testing.T) {
 		t.Errorf("failed to match transaction id, got: %s, expected: %s", have, want)
 	}
 
-	if want, have := false, log.Transaction_.HasRequest(); want != have {
+	// Validate Transaction Request parameters
+	if want, have := true, log.Transaction().HasRequest(); want != have {
 		t.Errorf("failed to match transaction has request, got: %t, expected: %t", have, want)
 	}
 
-	if want, have := "", log.Transaction_.Request().Method(); want != have {
+	if want, have := "", log.Transaction().Request().Method(); want != have {
 		t.Errorf("failed to match transaction request method, got: %s, expected: %s", have, want)
 	}
 
-	if want, have := false, log.Transaction_.HasResponse(); want != have {
+	if want, have := "request_header_value", log.Transaction().Request().Headers()["request_header_key"]; !slices.Contains(have, want) {
+		t.Errorf("failed to match message data tags, expected tag: %s not found in array", want)
+	}
+
+	// Validate Transaction Response parameters
+	if want, have := true, log.Transaction().HasResponse(); want != have {
 		t.Errorf("failed to match transaction has response, got: %t, expected: %t", have, want)
 	}
 
+	if want, have := 200, log.Transaction().Response().Status(); want != have {
+		t.Errorf("failed to match transaction response status, got: %d, expected: %d", have, want)
+	}
+
+	if want, have := "p", log.Transaction().Response().Protocol(); want != have {
+		t.Errorf("failed to match transaction response protocol, got: %s, expected: %s", have, want)
+	}
+
+	if want, have := "b", log.Transaction().Response().Body(); want != have {
+		t.Errorf("failed to match transaction response body, got: %s, expected: %s", have, want)
+	}
+
+	if want, have := "response_header_value", log.Transaction().Response().Headers()["response_header_key"]; !slices.Contains(have, want) {
+		t.Errorf("failed to match transaction response header response_header_key, expected value: %s not found in array", want)
+	}
+
+	// Validate log messages
 	if want, have := 1, len(log.Messages()); want != have {
 		t.Errorf("failed to match messages length, got: %d, expected: %d", have, want)
 	}
@@ -52,4 +243,90 @@ func TestAuditLogUnmarshalJSON(t *testing.T) {
 	if want, have := "b", log.Messages()[0].Message(); want != have {
 		t.Errorf("failed to match message, got: %s, expected: %s", have, want)
 	}
+
+	if want, have := "/etc/coraza-spoa/rules/REQUEST-930-APPLICATION-ATTACK-LFI.conf", log.Messages()[0].Data().File(); want != have {
+		t.Errorf("failed to match message data file, got: %s, expected: %s", have, want)
+	}
+
+	if want, have := 4468, log.Messages()[0].Data().Line(); want != have {
+		t.Errorf("failed to match message data line, got: %d, expected: %d", have, want)
+	}
+
+	if want, have := 930130, log.Messages()[0].Data().ID(); want != have {
+		t.Errorf("failed to match message data id, got: %d, expected: %d", have, want)
+	}
+
+	if want, have := "1.3", log.Messages()[0].Data().Rev(); want != have {
+		t.Errorf("failed to match message data rev, got: %s, expected: %s", have, want)
+	}
+
+	if want, have := "Restricted File Access Attempt", log.Messages()[0].Data().Msg(); want != have {
+		t.Errorf("failed to match message data msg, got: %s, expected: %s", have, want)
+	}
+
+	if want, have := "Matched Data: /.git/ found within REQUEST_FILENAME: /.git/config", log.Messages()[0].Data().Data(); want != have {
+		t.Errorf("failed to match message data data, got: %s, expected: %s", have, want)
+	}
+
+	if want, have := 2, log.Messages()[0].Data().Severity().Int(); want != have {
+		t.Errorf("failed to match message data severity, got: %d, expected: %d", have, want)
+	}
+
+	if want, have := "OWASP_CRS/4.4.0-dev", log.Messages()[0].Data().Ver(); want != have {
+		t.Errorf("failed to match message data ver, got: %s, expected: %s", have, want)
+	}
+
+	if want, have := 3, log.Messages()[0].Data().Maturity(); want != have {
+		t.Errorf("failed to match message data maturity, got: %d, expected: %d", have, want)
+	}
+
+	if want, have := 8, log.Messages()[0].Data().Accuracy(); want != have {
+		t.Errorf("failed to match message data accuracy, got: %d, expected: %d", have, want)
+	}
+
+	if want, have := "application-multi", log.Messages()[0].Data().Tags(); !slices.Contains(have, want) {
+		t.Errorf("failed to match message data tags, expected tag: %s not found in array", want)
+	}
+
+	if want, have := "paranoia-level/1", log.Messages()[0].Data().Tags(); !slices.Contains(have, want) {
+		t.Errorf("failed to match message data tags, expected tag: %s not found in array", want)
+	}
+
+	if want, have := "capec/1000/255/153/126", log.Messages()[0].Data().Tags(); !slices.Contains(have, want) {
+		t.Errorf("failed to match message data tags, expected tag: %s not found in array", want)
+	}
+
+	if want, have := "PCI/6.5.4", log.Messages()[0].Data().Tags(); !slices.Contains(have, want) {
+		t.Errorf("failed to match message data tags, expected tag: %s not found in array", want)
+	}
+
+	// Validate Transaction Producer parameters
+	if want, have := "c", log.Transaction().Producer().Connector(); want != have {
+		t.Errorf("failed to match producer connector, got: %s, expected: %s", have, want)
+	}
+
+	if want, have := "d", log.Transaction().Producer().Version(); want != have {
+		t.Errorf("failed to match producer version, got: %s, expected: %s", have, want)
+	}
+
+	if want, have := "e", log.Transaction().Producer().Server(); want != have {
+		t.Errorf("failed to match producer server, got: %s, expected: %s", have, want)
+	}
+
+	if want, have := "f", log.Transaction().Producer().RuleEngine(); want != have {
+		t.Errorf("failed to match producer rule engine, got: %s, expected: %s", have, want)
+	}
+
+	if want, have := "g", log.Transaction().Producer().Stopwatch(); want != have {
+		t.Errorf("failed to match producer stopwatch, got: %s, expected: %s", have, want)
+	}
+
+	if want, have := "h", log.Transaction().Producer().Rulesets(); !slices.Contains(have, want) {
+		t.Errorf("failed to match transaction producer rulesets, expected tag: %s not found in array", want)
+	}
+
+	if want, have := "i", log.Transaction().Producer().Rulesets(); !slices.Contains(have, want) {
+		t.Errorf("failed to match transaction producer rulesets, expected tag: %s not found in array", want)
+	}
+
 }
diff --git a/internal/auditlog/formats_ocsf_test.go b/internal/auditlog/formats_ocsf_test.go
new file mode 100644
index 0000000..2f04099
--- /dev/null
+++ b/internal/auditlog/formats_ocsf_test.go
@@ -0,0 +1,411 @@
+// Copyright 2024 Juan Pablo Tosso and the OWASP Coraza contributors
+// SPDX-License-Identifier: Apache-2.0
+
+package auditlog
+
+import (
+	"encoding/json"
+	"fmt"
+	"strings"
+	"testing"
+
+	"github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes"
+	"github.com/corazawaf/coraza/v3/internal/collections"
+	"github.com/corazawaf/coraza/v3/types"
+	"github.com/corazawaf/coraza/v3/types/variables"
+	"github.com/valllabh/ocsf-schema-golang/ocsf/v1_2_0/events/application"
+	"github.com/valllabh/ocsf-schema-golang/ocsf/v1_2_0/events/application/enums"
+)
+
+func TestOCSFFormatter(t *testing.T) {
+	for _, al := range createAuditLogs() {
+		f := &ocsfFormatter{}
+		data, err := f.Format(al)
+		if err != nil {
+			t.Error(err)
+		}
+		if !strings.Contains(f.MIME(), "json") {
+			t.Errorf("failed to match MIME, expected json and got %s", f.MIME())
+		}
+
+		var wra application.WebResourcesActivity
+		if err := json.Unmarshal(data, &wra); err != nil {
+			t.Error(err)
+		}
+
+		// validate MIME type
+		if f.MIME() != "application/json" {
+			t.Errorf("failed to match ocsfFormatter MIME type, \ngot: %s\nexpected: %s", fmt.Sprint(f.MIME()), "application/json")
+		}
+
+		// validate Unix Timestamp
+		if wra.Time != al.Transaction().UnixTimestamp() {
+			t.Errorf("failed to match audit log Unix Timestamp, \ngot: %s\nexpected: %s", fmt.Sprint(wra.Time), fmt.Sprint(al.Transaction().UnixTimestamp()))
+		}
+
+		// validate transation interruption
+		if al.Transaction().IsInterrupted() {
+			if wra.Action != "Denied" {
+				t.Errorf("failed to match audit log Action, \ngot: %s\nexpected: %s", wra.Action, "Denied")
+			}
+
+			if wra.ActionId != enums.WEB_RESOURCES_ACTIVITY_ACTION_ID_WEB_RESOURCES_ACTIVITY_ACTION_ID_DENIED {
+				t.Errorf("failed to match audit log Action ID, \ngot: %s\nexpected: %s", wra.ActionId, enums.WEB_RESOURCES_ACTIVITY_ACTION_ID_WEB_RESOURCES_ACTIVITY_ACTION_ID_DENIED)
+			}
+		} else {
+			if wra.Action != "Allowed" {
+				t.Errorf("failed to match audit log Action, \ngot: %s\nexpected: %s", wra.Action, "Allowed")
+			}
+
+			if wra.ActionId != enums.WEB_RESOURCES_ACTIVITY_ACTION_ID_WEB_RESOURCES_ACTIVITY_ACTION_ID_ALLOWED {
+				t.Errorf("failed to match audit log Action ID, \ngot: %s\nexpected: %s", wra.ActionId, enums.WEB_RESOURCES_ACTIVITY_ACTION_ID_WEB_RESOURCES_ACTIVITY_ACTION_ID_ALLOWED)
+			}
+		}
+
+		// validate Server ID
+		for _, observable := range wra.Observables {
+			if observable.Name == "ServerID" {
+				if observable.Value != al.Transaction().ServerID() {
+					t.Errorf("failed to match audit log Server ID, \ngot: %s\nexpected: %s", observable.Value, al.Transaction().ServerID())
+				}
+			}
+		}
+
+		// validate transaction requests
+		if al.Transaction().HasRequest() {
+			// validate Request URI
+			if wra.HttpRequest.Url.UrlString != al.Transaction().Request().URI() {
+				t.Errorf("failed to match audit log URI, \ngot: %s\nexpected: %s", wra.HttpRequest.Url.UrlString, al.Transaction().Request().URI())
+			}
+			// validate Request Method
+			if wra.HttpRequest.HttpMethod != al.Transaction().Request().Method() {
+				t.Errorf("failed to match audit log HTTP Request Method, \ngot: %s\nexpected: %s", wra.HttpRequest.HttpMethod, al.Transaction().Request().Method())
+			}
+			// validate Request Headers
+			for _, header := range wra.HttpRequest.HttpHeaders {
+				if header.Value != al.Transaction().Request().Headers()[header.Name][0] {
+					t.Errorf("failed to match audit log Request Header, \ngot: %s\nexpected: %s", header.Value, al.Transaction().Request().Headers()[header.Name][0])
+				}
+			}
+			// validate Request Files
+			for _, file := range al.Transaction().Request().Files() {
+				for _, observable := range wra.Observables {
+					if observable.Name == file.Name() {
+						if observable.Type == "File Name" {
+							if file.Name() != observable.Value {
+								t.Errorf("failed to match audit log Request File Name, \ngot: %s\nexpected: %s", observable.Value, file.Name())
+							}
+						}
+						if observable.Type == "Mime" {
+							if file.Mime() != observable.Value {
+								t.Errorf("failed to match audit log Request File Mime, \ngot: %s\nexpected: %s", observable.Value, file.Mime())
+							}
+						}
+						if observable.Type == "Size" {
+							if fmt.Sprint(file.Size()) != observable.Value {
+								t.Errorf("failed to match audit log Request File Size, \ngot: %s\nexpected: %s", observable.Value, fmt.Sprint(file.Size()))
+							}
+						}
+					}
+				}
+			}
+
+			// validate Request Protocol
+			if wra.HttpRequest.Version != al.Transaction().Request().Protocol() {
+				t.Errorf("failed to match audit log HTTP Request Protocol, \ngot: %s\nexpected: %s", wra.HttpRequest.Version, al.Transaction().Request().Protocol())
+			}
+
+			// validate Request Arguments
+			if al.Transaction().Request().Args() != nil {
+				for _, arg := range al.Transaction().Request().Args().FindAll() {
+					if strings.Contains(wra.HttpRequest.Args, fmt.Sprintf("%s=%s", arg.Key(), arg.Value())) == false {
+						t.Errorf("failed to match audit log Request arguments, \n%s not found in: %s", fmt.Sprintf("%s=%s", arg.Key(), arg.Value()), wra.HttpRequest.Args)
+					}
+				}
+			}
+
+			// validate Request UID
+			if wra.HttpRequest.Uid != al.Transaction().ID() {
+				t.Errorf("failed to match audit log HTTP Request UID, \ngot: %s\nexpected: %s", wra.HttpRequest.Uid, al.Transaction().ID())
+			}
+
+			// validate Request Length
+			if wra.HttpRequest.Length != al.Transaction().Request().Length() {
+				t.Errorf("failed to match audit log HTTP Request Length, \ngot: %d\nexpected: %d", wra.HttpRequest.Length, al.Transaction().Request().Length())
+			}
+		}
+
+		if al.Transaction().HasResponse() {
+			// validate Response Status
+			if int(wra.HttpResponse.Code) != al.Transaction().Response().Status() {
+				t.Errorf("failed to match audit log HTTP Response Status, \ngot: %s\nexpected: %s", fmt.Sprint(wra.HttpResponse.Code), fmt.Sprint(al.Transaction().Response().Status()))
+			}
+
+			// validate Response Headers
+			for _, header := range wra.HttpResponse.HttpHeaders {
+				if header.Value != al.Transaction().Response().Headers()[header.Name][0] {
+					t.Errorf("failed to match audit log Response Header, \ngot: %s\nexpected: %s", header.Value, al.Transaction().Response().Headers()[header.Name][0])
+				}
+			}
+		}
+
+		// validate Enrichments (Rule Matches)
+		if wra.Enrichments[0].Name != al.Messages()[0].Data().Msg() {
+			t.Errorf("failed to match audit log data, \ngot: %s\nexpected: %s", wra.Enrichments[0].Name, al.Messages()[0].Data().Msg())
+		}
+
+		// validate Schema
+		// ocsf-schema-golang appears to have a bug and is not validating against the OCSF 1.2 Schema.
+		// It would be nice to include this validation as part of the test suite, but for now it must be disabled until this bug is fixed.
+		// if ocsfvalidate_1_2.Validate("web_resources_activity", data) != nil {
+		// 	t.Errorf("failed to validate audit log schema, \ngot: %s\nexpected: %s", ocsfvalidate_1_2.Validate("web_resources_activity", data), "")
+		// }
+	}
+}
+
+func createAuditLogs() []*Log {
+
+	transactionLogs := []*Log{}
+
+	// Test case for "normal" / "typical" transaction
+	getArgs := collections.NewMap(variables.ArgsGet)
+	postArgs := collections.NewMap(variables.ArgsPost)
+	pathArgs := collections.NewMap(variables.ArgsPath)
+	args := collections.NewConcatKeyed(variables.Args, getArgs, postArgs, pathArgs)
+	getArgs.Add("qkey", "qvalue")
+	postArgs.Add("pkey", "pvalue")
+	transactionLogs = append(transactionLogs, &Log{
+		Parts_: []types.AuditLogPart{
+			types.AuditLogPartRequestHeaders,
+			types.AuditLogPartRequestBody,
+			types.AuditLogPartIntermediaryResponseBody,
+			types.AuditLogPartResponseHeaders,
+			types.AuditLogPartAuditLogTrailer,
+			types.AuditLogPartRulesMatched,
+		},
+		Transaction_: Transaction{
+			Timestamp_:     "02/Jan/2006:15:04:20 -0700",
+			UnixTimestamp_: 1136239460,
+			ID_:            "123",
+			IsInterrupted_: true,
+			Request_: &TransactionRequest{
+				URI_:    "/test.php?qkey=qvalue",
+				Method_: "GET",
+				Headers_: map[string][]string{
+					"host": {
+						"test.coraza.null",
+					},
+				},
+				Body_:     "pkey=pvalue",
+				Protocol_: "HTTP/1.1",
+				Args_:     args,
+				Length_:   112345,
+				Files_: []plugintypes.AuditLogTransactionRequestFiles{
+					&TransactionRequestFiles{
+						Name_: "dummyfile.txt",
+						Mime_: "text/plain",
+						Size_: 12345,
+					},
+				},
+			},
+			Response_: &TransactionResponse{
+				Status_: 200,
+				Headers_: map[string][]string{
+					"connection": {
+						"close",
+					},
+				},
+				Body_: "some response body",
+			},
+			Producer_: &TransactionProducer{
+				Connector_: "some connector",
+				Version_:   "1.2.3",
+			},
+		},
+		Messages_: []plugintypes.AuditLogMessage{
+			&Message{
+				Message_: "some message",
+				Data_: &MessageData{
+					Msg_: "some message",
+					Raw_: "SecAction \"id:100\"",
+				},
+			},
+		},
+	})
+
+	// Test case for abnormal transaction (all empty values, no arguments)
+	getArgs = collections.NewMap(variables.ArgsGet)
+	postArgs = collections.NewMap(variables.ArgsPost)
+	pathArgs = collections.NewMap(variables.ArgsPath)
+	args = collections.NewConcatKeyed(variables.Args, getArgs, postArgs, pathArgs)
+	transactionLogs = append(transactionLogs, &Log{
+		Parts_: []types.AuditLogPart{
+			types.AuditLogPartRequestHeaders,
+			types.AuditLogPartRequestBody,
+			types.AuditLogPartIntermediaryResponseBody,
+			types.AuditLogPartResponseHeaders,
+			types.AuditLogPartAuditLogTrailer,
+			types.AuditLogPartRulesMatched,
+		},
+		Transaction_: Transaction{
+			Timestamp_:     "",
+			UnixTimestamp_: 0,
+			ID_:            "",
+			IsInterrupted_: true,
+			ServerID_:      "someServer",
+			Request_: &TransactionRequest{
+				URI_:    "",
+				Method_: "",
+				Headers_: map[string][]string{
+					"host": {
+						"",
+					},
+					"accept": {
+						"",
+					},
+					"accept-encoding": {
+						"",
+					},
+					"accept-language": {
+						"",
+					},
+					"cache-control": {
+						"",
+					},
+					"connection": {
+						"",
+					},
+					"upgrade-insecure-requests": {
+						"",
+					},
+					"user-agent": {
+						"",
+					},
+					"x-forwarded-for": {
+						"",
+					},
+					"referer": {
+						"",
+					},
+				},
+				Body_:     "",
+				Protocol_: "",
+				Args_:     args,
+				Length_:   0,
+			},
+			Response_: &TransactionResponse{
+				Status_: 200,
+				Headers_: map[string][]string{
+					"connection": {
+						"",
+					},
+					"content-type": {
+						"",
+					},
+					"referrer-policy": {
+						"",
+					},
+					"strict-transport-security": {
+						"",
+					},
+					"transfer-encoding": {
+						"",
+					},
+					"referer": {
+						"",
+					},
+				},
+				Body_: "",
+			},
+			Producer_: &TransactionProducer{
+				Connector_: "",
+				Version_:   "",
+			},
+		},
+		Messages_: []plugintypes.AuditLogMessage{
+			&Message{
+				Message_: "some message",
+				Data_: &MessageData{
+					Msg_: "some message",
+					Raw_: "SecAction \"id:100\"",
+				},
+			},
+		},
+	})
+
+	// Test case for abnormal transaction (all empty values, no arguments, no reponse)
+	getArgs = collections.NewMap(variables.ArgsGet)
+	postArgs = collections.NewMap(variables.ArgsPost)
+	pathArgs = collections.NewMap(variables.ArgsPath)
+	args = collections.NewConcatKeyed(variables.Args, getArgs, postArgs, pathArgs)
+	transactionLogs = append(transactionLogs, &Log{
+		Parts_: []types.AuditLogPart{
+			types.AuditLogPartRequestHeaders,
+			types.AuditLogPartRequestBody,
+			types.AuditLogPartIntermediaryResponseBody,
+			types.AuditLogPartResponseHeaders,
+			types.AuditLogPartAuditLogTrailer,
+			types.AuditLogPartRulesMatched,
+		},
+		Transaction_: Transaction{
+			Timestamp_:     "",
+			UnixTimestamp_: 0,
+			ID_:            "",
+			IsInterrupted_: true,
+			Request_: &TransactionRequest{
+				URI_:    "",
+				Method_: "",
+				Headers_: map[string][]string{
+					"host": {
+						"",
+					},
+					"accept": {
+						"",
+					},
+					"accept-encoding": {
+						"",
+					},
+					"accept-language": {
+						"",
+					},
+					"cache-control": {
+						"",
+					},
+					"connection": {
+						"",
+					},
+					"upgrade-insecure-requests": {
+						"",
+					},
+					"user-agent": {
+						"",
+					},
+					"x-forwarded-for": {
+						"",
+					},
+					"referer": {
+						"",
+					},
+				},
+				Body_:     "",
+				Protocol_: "",
+				Args_:     args,
+			},
+			Producer_: &TransactionProducer{
+				Connector_: "",
+				Version_:   "",
+			},
+		},
+		Messages_: []plugintypes.AuditLogMessage{
+			&Message{
+				Message_: "some message",
+				Data_: &MessageData{
+					Msg_: "some message",
+					Raw_: "SecAction \"id:100\"",
+				},
+			},
+		},
+	})
+
+	return transactionLogs
+}
diff --git a/internal/auditlog/https_writer_test.go b/internal/auditlog/https_writer_test.go
index 21c478d..f0713e0 100644
--- a/internal/auditlog/https_writer_test.go
+++ b/internal/auditlog/https_writer_test.go
@@ -1,4 +1,4 @@
-// Copyright 2023 Juan Pablo Tosso and the OWASP Coraza contributors
+// Copyright 2024 Juan Pablo Tosso and the OWASP Coraza contributors
 // SPDX-License-Identifier: Apache-2.0
 
 //go:build !tinygo
@@ -19,7 +19,6 @@ import (
 )
 
 var sampleHttpsAuditLog = &Log{
-
 	Transaction_: Transaction{
 		ID_: "test123",
 	},
@@ -33,7 +32,7 @@ var sampleHttpsAuditLog = &Log{
 	},
 }
 
-func TestHTTPSAuditLog(t *testing.T) {
+func TestHTTPAuditLog(t *testing.T) {
 	writer := &httpsWriter{}
 	formatter := &nativeFormatter{}
 	pts, err := types.ParseAuditLogParts("ABCDEZ")
@@ -74,7 +73,7 @@ func TestHTTPSAuditLog(t *testing.T) {
 	}
 }
 
-func TestJSONAuditHTTPS(t *testing.T) {
+func TestJSONAuditHTTP(t *testing.T) {
 	writer := &httpsWriter{}
 	formatter := &jsonFormatter{}
 	// we create a test http server
@@ -98,3 +97,28 @@ func TestJSONAuditHTTPS(t *testing.T) {
 		t.Fatal(err)
 	}
 }
+
+func TestOCSFAuditHTTP(t *testing.T) {
+	writer := &httpsWriter{}
+	formatter := &ocsfFormatter{}
+	// we create a test http server
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.WriteHeader(http.StatusOK)
+		if r.ContentLength == 0 {
+			t.Fatal("ContentLength is 0")
+		}
+		if ct := r.Header.Get("Content-Type"); !strings.HasPrefix(ct, "application/json") {
+			t.Fatalf("Content-Type is not application/json, got %s", ct)
+		}
+	}))
+	defer server.Close()
+	if err := writer.Init(plugintypes.AuditLogConfig{
+		Target:    server.URL,
+		Formatter: formatter,
+	}); err != nil {
+		t.Fatal(err)
+	}
+	if err := writer.Write(sampleHttpsAuditLog); err != nil {
+		t.Fatal(err)
+	}
+}
diff --git a/internal/operators/ip_match_from_dataset_test.go b/internal/operators/ip_match_from_dataset_test.go
index 2129ace..678a3d5 100644
--- a/internal/operators/ip_match_from_dataset_test.go
+++ b/internal/operators/ip_match_from_dataset_test.go
@@ -27,13 +27,13 @@ func TestIpMatchFromDataset(t *testing.T) {
 	}
 	for _, ok := range addrok {
 		if !ipm.Evaluate(nil, ok) {
-			t.Errorf("Invalid result for single CIDR IpMatchFromDataset " + ok)
+			t.Errorf("Invalid result for single CIDR IpMatchFromDataset %q", ok)
 		}
 	}
 
 	for _, fail := range addrfail {
 		if ipm.Evaluate(nil, fail) {
-			t.Errorf("Invalid result for single CIDR IpMatchFromDataset" + fail)
+			t.Errorf("Invalid result for single CIDR IpMatchFromDataset %q", fail)
 		}
 	}
 }
diff --git a/internal/seclang/parser_test.go b/internal/seclang/parser_test.go
index c5000cc..b03ce1f 100644
--- a/internal/seclang/parser_test.go
+++ b/internal/seclang/parser_test.go
@@ -169,6 +169,10 @@ func TestHardcodedIncludeDirectiveDDOS2(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
+	err = tmpFile2.Close()
+	if err != nil {
+		t.Fatal(err)
+	}
 
 	w := bufio.NewWriter(tmpFile)
 	for i := 0; i < maxIncludeRecursion+1; i++ {
diff --git a/testing/coreruleset/.ftw-overrides.yml b/testing/coreruleset/.ftw-overrides.yml
new file mode 100644
index 0000000..0d32f24
--- /dev/null
+++ b/testing/coreruleset/.ftw-overrides.yml
@@ -0,0 +1,52 @@
+version: "v0.0.0"
+meta:
+  engine: "coraza"
+  platform: "go"
+  annotations:
+    - purpose: "Overrides for CRS test suite running against Coraza deployed as a Go middleware"
+test_overrides:
+  # - rule_id: 920100
+  #   test_ids: [5]
+  #   reason: "Invalid uri, Coraza not reached - 301 returned"
+  #   output:
+  #     status: 301
+  - rule_id: 920100
+    test_ids: [8]
+    reason: |
+      On Apache is not allowed to put a colon in the path. Go/http allows it.
+      Note that the colon is a legal character in a regular path segment, according to the RFC.
+    output:
+      status: 200
+      log:
+        no_expect_ids: [920100]
+  - rule_id: 920270
+    test_ids: [4]
+    reason: "Rule works, Apache test expects status 400"
+    output:
+      log:
+        expect_ids: [920270]
+  - rule_id: 920274
+    test_ids: [1]
+    reason: "Host validation. Apache expects status 400. Coraza correctly triggers the rule 920274"
+    output:
+      log:
+        expect_ids: [920274]
+  - rule_id: 920290
+    test_ids: [1]
+    reason: "TODO"
+    output:
+      log:
+        expect_ids: [920280] # TODO: understand why 920280 (Missing Host Header) is triggered and not 920290 (Empty Host header). See what go-ftw sends.
+  - rule_id: 920290
+    test_ids: [4]
+    reason: "TODO"
+    output:
+      log:
+        expect_ids: [920280] # TODO: understand why 920280 (Missing Host Header) is triggered and not 920290 (Empty Host header). See what go-ftw sends.
+  - rule_id: 920430
+    test_ids: [8]
+    reason: "Go/http does not allow HTTP/3.0 - 505 HTTP Version Not Supported"
+    output:
+      status: 505
+      log:
+        no_expect_ids: [920430]
diff --git a/testing/coreruleset/.ftw.yml b/testing/coreruleset/.ftw.yml
index 0cc9300..23077a4 100644
--- a/testing/coreruleset/.ftw.yml
+++ b/testing/coreruleset/.ftw.yml
@@ -1,13 +1,19 @@
+# Tests should not just be ignored via .ftw.yml, but new expectations for each test should be set.
+# Avoid as much as possible adding new entries here, in favor of .ftw-overrides.yml
 ---
 testoverride:
   ignore:
     920100-4: 'Invalid uri, Coraza not reached - 404 page not found'
     920100-5: 'Invalid uri, Coraza not reached - 404 page not found'
-    920100-8: 'Go/http allows a colon in the path. Test expects status 400 or 403 (Apache behaviour)'
-    920270-4: 'Rule works, log contains 920270. Test expects status 400 (Apache behaviour)'
-    920272-5: 'Rule works, log contains 920272. Test expects status 400 (Apache behaviour)'
-    920290-1: 'Rule works, log contains 920290. Test expects status 400 (Apache behaviour)'
-    920290-4: 'Go/http returns 400 Bad Request: missing required Host header'
-    920430-8: 'Go/http does not allow HTTP/3.0 - 505 HTTP Version Not Supported'
-    932200-13: 'wip'
     930110-7: 'CRS issue: https://github.com/coreruleset/coreruleset/issues/3736'
+
+    # TODO: investigate
+    932200-13: 'Failing only in multiphase evalution'
+    932300-10: 'Failing only in multiphase evalution'
+    933120-2: 'Failing only in multiphase evalution'
+    920280-3: ''
+    920430-3: ''
+    920430-5: ''
+    920430-9: ''
+    920610-2: 'fragments, Coraza might just happly accept them. Run and check it.'
+    920620-1: 'Rule checks if multiple Content-Type headers are kepts. Go/http might keep them and trigger the rule. Run and check it.'
diff --git a/testing/coreruleset/coreruleset_test.go b/testing/coreruleset/coreruleset_test.go
index f926bda..6fe6ead 100644
--- a/testing/coreruleset/coreruleset_test.go
+++ b/testing/coreruleset/coreruleset_test.go
@@ -9,7 +9,6 @@ package coreruleset
 
 import (
 	"bufio"
-	b64 "encoding/base64"
 	"fmt"
 	"io"
 	"io/fs"
@@ -21,6 +20,7 @@ import (
 	"strconv"
 	"strings"
 	"testing"
+	"time"
 
 	"github.com/bmatcuk/doublestar/v4"
 	"github.com/coreruleset/go-ftw/config"
@@ -34,6 +34,7 @@ import (
 	"github.com/corazawaf/coraza/v3"
 	txhttp "github.com/corazawaf/coraza/v3/http"
 	"github.com/corazawaf/coraza/v3/types"
+	albedo "github.com/coreruleset/albedo/server"
 )
 
 func BenchmarkCRSCompilation(b *testing.B) {
@@ -220,43 +221,13 @@ SecRule REQUEST_HEADERS:X-CRS-Test "@rx ^.*$" \
 		t.Fatal(err)
 	}
 
+	// CRS regression tests are expected to be run with https://github.com/coreruleset/albedo as backend server
 	s := httptest.NewServer(txhttp.WrapHandler(waf, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		defer r.Body.Close()
+		// TODO: Investigate why we need to enforce text/plain to have response body tests working.
+		// Check the Content-Type set by albed and SecResponseBodyMimeType
 		w.Header().Set("Content-Type", "text/plain")
-		switch {
-		case r.URL.Path == "/anything", r.URL.Path == "/post":
-			body, err := io.ReadAll(r.Body)
-			// Emulated httpbin behaviour: /anything and /post endpoints act as an echo server, writing back the request body
-			if r.Header.Get("Content-Type") == "application/x-www-form-urlencoded" {
-				// Tests 954120-1 and 954120-2 are the only two calling /anything with a POST and payload is urlencoded
-				if err != nil {
-					t.Fatalf("handler can not read request body: %v", err)
-				}
-				urldecodedBody, err := url.QueryUnescape(string(body))
-				if err != nil {
-					t.Logf("[warning] handler can not unescape urlencoded request body: %v", err)
-					// If the body can't be unescaped, we will keep going with the received body
-					urldecodedBody = string(body)
-				}
-				fmt.Fprint(w, urldecodedBody)
-			} else {
-				_, err = w.Write(body)
-				if err != nil {
-					t.Fatalf("handler can not write request body: %v", err)
-				}
-			}
-
-		case strings.HasPrefix(r.URL.Path, "/base64/"):
-			// Emulated httpbin behaviour: /base64 endpoint write the decoded base64 into the response body
-			b64Decoded, err := b64.StdEncoding.DecodeString(strings.TrimPrefix(r.URL.Path, "/base64/"))
-			if err != nil {
-				t.Fatalf("handler can not decode base64: %v", err)
-			}
-			fmt.Fprint(w, string(b64Decoded))
-		default:
-			// Common path "/status/200" defaults here
-			fmt.Fprint(w, "Hello!")
-		}
+		albedo.Handler().ServeHTTP(w, r)
 	})))
 	defer s.Close()
 
@@ -266,7 +237,7 @@ SecRule REQUEST_HEADERS:X-CRS-Test "@rx ^.*$" \
 		if err != nil {
 			return err
 		}
-		ftwt, err := test.GetTestFromYaml(yaml)
+		ftwt, err := test.GetTestFromYaml(yaml, path)
 		if err != nil {
 			return err
 		}
@@ -293,15 +264,21 @@ SecRule REQUEST_HEADERS:X-CRS-Test "@rx ^.*$" \
 	cfg.TestOverride.Overrides.DestAddr = &host
 	cfg.TestOverride.Overrides.Port = &port
 
+	cfg.LoadPlatformOverrides(".ftw-overrides.yml")
 	res, err := runner.Run(cfg, tests, runner.RunnerConfig{
-		ShowTime: false,
+		ShowTime:    false,
+		ReadTimeout: 3 * time.Second, // Defaults to 1s but looks to be not enough in the CI
 	}, output.NewOutput("quiet", os.Stdout))
 	if err != nil {
 		t.Fatal(err)
 	}
-
-	if len(res.Stats.Failed) > 0 {
-		t.Errorf("failed tests: %v", res.Stats.Failed)
+	totalIgnored := len(res.Stats.Ignored)
+	if totalIgnored > 0 {
+		t.Logf("[info] %d ignored tests: %v", totalIgnored, res.Stats.Ignored)
+	}
+	totalFailed := len(res.Stats.Failed)
+	if totalFailed > 0 {
+		t.Errorf("[fatal] %d failed tests: %v", totalFailed, res.Stats.Failed)
 	}
 }
 
diff --git a/testing/coreruleset/go.mod b/testing/coreruleset/go.mod
index a6c0007..570d22c 100644
--- a/testing/coreruleset/go.mod
+++ b/testing/coreruleset/go.mod
@@ -1,12 +1,13 @@
 module github.com/corazawaf/coraza/v3/testing/coreruleset
 
-go 1.21
+go 1.22.3
 
 require (
 	github.com/bmatcuk/doublestar/v4 v4.6.1
-	github.com/corazawaf/coraza-coreruleset/v4 v4.3.0
+	github.com/corazawaf/coraza-coreruleset/v4 v4.5.0
 	github.com/corazawaf/coraza/v3 v3.0.0-00010101000000-000000000000
-	github.com/coreruleset/go-ftw v0.6.4
+	github.com/coreruleset/albedo v0.0.16-0.20240924185852-4b95a321ebfd
+	github.com/coreruleset/go-ftw v1.0.4-0.20240923043156-8474a93d514a
 	github.com/rs/zerolog v1.33.0
 )
 
@@ -15,8 +16,8 @@ require (
 	github.com/Masterminds/semver v1.5.0 // indirect
 	github.com/Masterminds/sprig v2.22.0+incompatible // indirect
 	github.com/corazawaf/libinjection-go v0.2.1 // indirect
-	github.com/coreruleset/ftw-tests-schema v1.1.0 // indirect
-	github.com/fatih/color v1.16.0 // indirect
+	github.com/coreruleset/ftw-tests-schema/v2 v2.1.0 // indirect
+	github.com/fatih/color v1.17.0 // indirect
 	github.com/fsnotify/fsnotify v1.7.0 // indirect
 	github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
 	github.com/goccy/go-yaml v1.11.3 // indirect
@@ -28,9 +29,10 @@ require (
 	github.com/knadh/koanf/maps v0.1.1 // indirect
 	github.com/knadh/koanf/parsers/yaml v0.1.0 // indirect
 	github.com/knadh/koanf/providers/env v0.1.0 // indirect
-	github.com/knadh/koanf/providers/file v0.1.0 // indirect
+	github.com/knadh/koanf/providers/file v1.1.0 // indirect
 	github.com/knadh/koanf/providers/rawbytes v0.1.0 // indirect
 	github.com/knadh/koanf/v2 v2.1.1 // indirect
+	github.com/kr/text v0.2.0 // indirect
 	github.com/kyokomi/emoji/v2 v2.2.13 // indirect
 	github.com/magefile/mage v1.15.0 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
@@ -38,15 +40,19 @@ require (
 	github.com/mitchellh/copystructure v1.2.0 // indirect
 	github.com/mitchellh/reflectwalk v1.0.2 // indirect
 	github.com/petar-dambovaliev/aho-corasick v0.0.0-20240411101913-e07a1f0e8eb4 // indirect
-	github.com/tidwall/gjson v1.17.1 // indirect
+	github.com/rogpeppe/go-internal v1.13.1 // indirect
+	github.com/tidwall/gjson v1.17.3 // indirect
 	github.com/tidwall/match v1.1.1 // indirect
 	github.com/tidwall/pretty v1.2.1 // indirect
+	github.com/valllabh/ocsf-schema-golang v1.0.3 // indirect
 	github.com/yargevad/filepathx v1.0.0 // indirect
-	golang.org/x/crypto v0.25.0 // indirect
-	golang.org/x/net v0.27.0 // indirect
-	golang.org/x/sync v0.7.0 // indirect
-	golang.org/x/sys v0.22.0 // indirect
+	golang.org/x/crypto v0.27.0 // indirect
+	golang.org/x/net v0.29.0 // indirect
+	golang.org/x/sync v0.8.0 // indirect
+	golang.org/x/sys v0.25.0 // indirect
+	golang.org/x/time v0.6.0 // indirect
 	golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
+	google.golang.org/protobuf v1.34.1 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 	rsc.io/binaryregexp v0.2.0 // indirect
 )
diff --git a/testing/coreruleset/go.sum b/testing/coreruleset/go.sum
index babfd47..e11a16f 100644
--- a/testing/coreruleset/go.sum
+++ b/testing/coreruleset/go.sum
@@ -6,19 +6,24 @@ github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZC
 github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
 github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
 github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
-github.com/corazawaf/coraza-coreruleset/v4 v4.3.0 h1:izzVRUxfvVf1OXhRQXpFm1jj3g/cIlLu9SiNzXOW7XU=
-github.com/corazawaf/coraza-coreruleset/v4 v4.3.0/go.mod h1:RQMGurig+irQq7v21yq7rM/9SAEf1bT6hCSplJ0ByKY=
+github.com/corazawaf/coraza-coreruleset/v4 v4.5.0 h1:4BDr9/yWKSJ7Ch3h7SvSqJBASju73+EqIIF0WxjsFgQ=
+github.com/corazawaf/coraza-coreruleset/v4 v4.5.0/go.mod h1:1FQt1p+JSQ6tYrafMqZrEEdDmhq6aVuIJdnk+bM9hMY=
 github.com/corazawaf/libinjection-go v0.2.1 h1:vNJ7L6c4xkhRgYU6sIO0Tl54TmeCQv/yfxBma30Dy/Y=
 github.com/corazawaf/libinjection-go v0.2.1/go.mod h1:OP4TM7xdJ2skyXqNX1AN1wN5nNZEmJNuWbNPOItn7aw=
 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
-github.com/coreruleset/ftw-tests-schema v1.1.0 h1:3+NYrdLE3HVmOc3nGrisRBBvY9lGjePUrV+YkT5Ay3s=
-github.com/coreruleset/ftw-tests-schema v1.1.0/go.mod h1:gRd9wBxjUI85HypWRDxJzbk1JqHC4KTxl0l/Y2p9QK4=
-github.com/coreruleset/go-ftw v0.6.4 h1:EdDNld38Jv4lxqHS+csGOJuHu1/8rpp4TlrFyoijTPk=
-github.com/coreruleset/go-ftw v0.6.4/go.mod h1:IayMjfOmmNNBcqTcZU92e6UZTy79/eFdmJEmRu8tLs4=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
-github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
+github.com/coreruleset/albedo v0.0.16-0.20240924185852-4b95a321ebfd h1:4KjOGOv4I81ZOuW/TY29oe0yZCNeNGuPt8HXvmjukPI=
+github.com/coreruleset/albedo v0.0.16-0.20240924185852-4b95a321ebfd/go.mod h1:6mYBASfvvRM2ckXgYO7N5nyKAj8OqLnT4+YLbM0/XWE=
+github.com/coreruleset/ftw-tests-schema/v2 v2.1.0 h1:2ilKzKRG5UzzxBcrJLXFtPalStdQ9jzzaYFuFk0OEk0=
+github.com/coreruleset/ftw-tests-schema/v2 v2.1.0/go.mod h1:ZHVFX5ses4+5IxUP0ufCNg/VqRWxziH6ZuUca092Hxo=
+github.com/coreruleset/go-ftw v1.0.4-0.20240809050408-f8169f0325ac h1:I++204ogJDnOyYQrMs6IdfTYRBIMr4A9Dtix+XdtZEc=
+github.com/coreruleset/go-ftw v1.0.4-0.20240809050408-f8169f0325ac/go.mod h1:gI2N2EYdTIZnXQbsdzBRxbj/zSaYEyrhLJUCOJ3VK6I=
+github.com/coreruleset/go-ftw v1.0.4-0.20240923043156-8474a93d514a h1:ucsngMh75QkedILO3N9JRiVWPbkcelhH055adS51hNs=
+github.com/coreruleset/go-ftw v1.0.4-0.20240923043156-8474a93d514a/go.mod h1:/HKpRGHn0rOEb+VdkfjLqltb3Jr5DHDeKPAiC/04lR8=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
+github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
 github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI=
 github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
@@ -52,12 +57,16 @@ github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP
 github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY=
 github.com/knadh/koanf/providers/env v0.1.0 h1:LqKteXqfOWyx5Ab9VfGHmjY9BvRXi+clwyZozgVRiKg=
 github.com/knadh/koanf/providers/env v0.1.0/go.mod h1:RE8K9GbACJkeEnkl8L/Qcj8p4ZyPXZIQ191HJi44ZaQ=
-github.com/knadh/koanf/providers/file v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c=
-github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA=
+github.com/knadh/koanf/providers/file v1.1.0 h1:MTjA+gRrVl1zqgetEAIaXHqYje0XSosxSiMD4/7kz0o=
+github.com/knadh/koanf/providers/file v1.1.0/go.mod h1:/faSBcv2mxPVjFrXck95qeoyoZ5myJ6uxN8OOVNJJCI=
 github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI51k5pe8RYKQ0qME=
 github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c=
 github.com/knadh/koanf/v2 v2.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM=
 github.com/knadh/koanf/v2 v2.1.1/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/kyokomi/emoji/v2 v2.2.13 h1:GhTfQa67venUUvmleTNFnb+bi7S3aocF7ZCXU9fSO7U=
 github.com/kyokomi/emoji/v2 v2.2.13/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
 github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
@@ -79,41 +88,50 @@ github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx
 github.com/petar-dambovaliev/aho-corasick v0.0.0-20240411101913-e07a1f0e8eb4 h1:1Kw2vDBXmjop+LclnzCb/fFy+sgb3gYARwfmoUcQe6o=
 github.com/petar-dambovaliev/aho-corasick v0.0.0-20240411101913-e07a1f0e8eb4/go.mod h1:EHPiTAKtiFmrMldLUNswFwfZ2eJIYBHktdaUTZxYWRw=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
+github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
 github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
 github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
-github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94=
+github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
 github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
 github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
 github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
 github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
 github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/valllabh/ocsf-schema-golang v1.0.3 h1:eR8k/3jP/OOqB8LRCtdJ4U+vlgd/gk5y3KMXoodrsrw=
+github.com/valllabh/ocsf-schema-golang v1.0.3/go.mod h1:sZ3as9xqm1SSK5feFWIR2CuGeGRhsM7TR1MbpBctzPk=
 github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc=
 github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA=
-golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
-golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
-golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
-golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
-golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
-golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
-golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
+golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
+golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
+golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
+golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
-golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
-golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
+golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
+golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
+golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
+golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
+google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=