Browse Source

Merge branch 'master' into appsec-properly-populate-event

Sebastien Blot 1 year ago
parent
commit
f6038feabe

+ 1 - 1
.github/workflows/bats-hub.yml

@@ -33,7 +33,7 @@ jobs:
     - name: "Set up Go"
       uses: actions/setup-go@v5
       with:
-        go-version: "1.21.8"
+        go-version: "1.21.9"
 
     - name: "Install bats dependencies"
       env:

+ 1 - 1
.github/workflows/bats-mysql.yml

@@ -36,7 +36,7 @@ jobs:
     - name: "Set up Go"
       uses: actions/setup-go@v5
       with:
-        go-version: "1.21.8"
+        go-version: "1.21.9"
 
     - name: "Install bats dependencies"
       env:

+ 1 - 1
.github/workflows/bats-postgres.yml

@@ -45,7 +45,7 @@ jobs:
     - name: "Set up Go"
       uses: actions/setup-go@v5
       with:
-        go-version: "1.21.8"
+        go-version: "1.21.9"
 
     - name: "Install bats dependencies"
       env:

+ 1 - 1
.github/workflows/bats-sqlite-coverage.yml

@@ -28,7 +28,7 @@ jobs:
     - name: "Set up Go"
       uses: actions/setup-go@v5
       with:
-        go-version: "1.21.8"
+        go-version: "1.21.9"
 
     - name: "Install bats dependencies"
       env:

+ 1 - 1
.github/workflows/ci-windows-build-msi.yml

@@ -35,7 +35,7 @@ jobs:
     - name: "Set up Go"
       uses: actions/setup-go@v5
       with:
-        go-version: "1.21.8"
+        go-version: "1.21.9"
 
     - name: Build
       run: make windows_installer BUILD_RE2_WASM=1

+ 1 - 1
.github/workflows/codeql-analysis.yml

@@ -52,7 +52,7 @@ jobs:
     - name: "Set up Go"
       uses: actions/setup-go@v5
       with:
-        go-version: "1.21.8"
+        go-version: "1.21.9"
         cache-dependency-path: "**/go.sum"
 
     # Initializes the CodeQL tools for scanning.

+ 2 - 2
.github/workflows/go-tests-windows.yml

@@ -34,7 +34,7 @@ jobs:
     - name: "Set up Go"
       uses: actions/setup-go@v5
       with:
-        go-version: "1.21.8"
+        go-version: "1.21.9"
 
     - name: Build
       run: |
@@ -56,7 +56,7 @@ jobs:
     - name: golangci-lint
       uses: golangci/golangci-lint-action@v4
       with:
-        version: v1.56
+        version: v1.57
         args: --issues-exit-code=1 --timeout 10m
         only-new-issues: false
         # the cache is already managed above, enabling it here

+ 2 - 2
.github/workflows/go-tests.yml

@@ -126,7 +126,7 @@ jobs:
     - name: "Set up Go"
       uses: actions/setup-go@v5
       with:
-        go-version: "1.21.8"
+        go-version: "1.21.9"
 
     - name: Create localstack streams
       run: |
@@ -157,7 +157,7 @@ jobs:
     - name: golangci-lint
       uses: golangci/golangci-lint-action@v4
       with:
-        version: v1.56
+        version: v1.57
         args: --issues-exit-code=1 --timeout 10m
         only-new-issues: false
         # the cache is already managed above, enabling it here

+ 1 - 1
.github/workflows/publish-tarball-release.yml

@@ -25,7 +25,7 @@ jobs:
       - name: "Set up Go"
         uses: actions/setup-go@v5
         with:
-          go-version: "1.21.8"
+          go-version: "1.21.9"
 
       - name: Build the binaries
         run: |

+ 13 - 11
.golangci.yml

@@ -1,12 +1,5 @@
 # https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml
 
-run:
-  skip-dirs:
-    - pkg/time/rate
-  skip-files:
-    - pkg/yamlpatch/merge.go
-    - pkg/yamlpatch/merge_test.go
-
 linters-settings:
   cyclop:
     # lower this after refactoring
@@ -19,6 +12,10 @@ linters-settings:
      - prefix(github.com/crowdsecurity)
      - prefix(github.com/crowdsecurity/crowdsec)
 
+  gomoddirectives:
+    replace-allow-list:
+      - golang.org/x/time/rate
+
   gocognit:
     # lower this after refactoring
     min-complexity: 145
@@ -40,7 +37,6 @@ linters-settings:
     statements: 122
 
   govet:
-    check-shadowing: true
     enable:
      - atomicalign
      - deepequalerrors
@@ -295,15 +291,21 @@ issues:
   # “Look, that’s why there’s rules, understand? So that you think before you
   # break ‘em.” ― Terry Pratchett
 
+  exclude-dirs:
+    - pkg/time/rate
+
+  exclude-files:
+    - pkg/yamlpatch/merge.go
+    - pkg/yamlpatch/merge_test.go
+
+  exclude-generated-strict: true
+
   max-issues-per-linter: 0
   max-same-issues: 0
   exclude-rules:
 
     # Won't fix:
 
-    - path: go.mod
-      text: "replacement are not allowed: golang.org/x/time/rate"
-
     # `err` is often shadowed, we may continue to do it
     - linters:
         - govet

+ 4 - 10
Dockerfile

@@ -1,5 +1,5 @@
 # vim: set ft=dockerfile:
-FROM golang:1.21.8-alpine3.18 AS build
+FROM golang:1.21.9-alpine3.18 AS build
 
 ARG BUILD_VERSION
 
@@ -25,6 +25,7 @@ RUN make clean release DOCKER_BUILD=1 BUILD_STATIC=1 && \
     ./wizard.sh --docker-mode && \
     cd - >/dev/null && \
     cscli hub update && \
+    ./docker/preload-hub-items && \
     cscli collections install crowdsecurity/linux && \
     cscli parsers install crowdsecurity/whitelists
 
@@ -43,11 +44,12 @@ COPY --from=build /go/bin/yq /usr/local/bin/crowdsec /usr/local/bin/cscli /usr/l
 COPY --from=build /etc/crowdsec /staging/etc/crowdsec
 COPY --from=build /go/src/crowdsec/docker/docker_start.sh /
 COPY --from=build /go/src/crowdsec/docker/config.yaml /staging/etc/crowdsec/config.yaml
+COPY --from=build /var/lib/crowdsec /staging/var/lib/crowdsec
 RUN yq -n '.url="http://0.0.0.0:8080"' | install -m 0600 /dev/stdin /staging/etc/crowdsec/local_api_credentials.yaml
 
 ENTRYPOINT /bin/bash /docker_start.sh
 
-FROM slim as plugins
+FROM slim as full
 
 # Due to the wizard using cp -n, we have to copy the config files directly from the source as -n does not exist in busybox cp
 # The files are here for reference, as users will need to mount a new version to be actually able to use notifications
@@ -60,11 +62,3 @@ COPY --from=build \
     /staging/etc/crowdsec/notifications/
 
 COPY --from=build /usr/local/lib/crowdsec/plugins /usr/local/lib/crowdsec/plugins
-
-FROM slim as geoip
-
-COPY --from=build /var/lib/crowdsec /staging/var/lib/crowdsec
-
-FROM plugins as full
-
-COPY --from=build /var/lib/crowdsec /staging/var/lib/crowdsec

+ 2 - 1
Dockerfile.debian

@@ -1,5 +1,5 @@
 # vim: set ft=dockerfile:
-FROM golang:1.21.8-bookworm AS build
+FROM golang:1.21.9-bookworm AS build
 
 ARG BUILD_VERSION
 
@@ -30,6 +30,7 @@ RUN make clean release DOCKER_BUILD=1 BUILD_STATIC=1 && \
     ./wizard.sh --docker-mode && \
     cd - >/dev/null && \
     cscli hub update && \
+    ./docker/preload-hub-items && \
     cscli collections install crowdsecurity/linux && \
     cscli parsers install crowdsecurity/whitelists
 

+ 1 - 1
azure-pipelines.yml

@@ -21,7 +21,7 @@ stages:
           - task: GoTool@0
             displayName: "Install Go"
             inputs:
-                version: '1.21.8'
+                version: '1.21.9'
 
           - pwsh: |
               choco install -y make

+ 9 - 2
docker/README.md

@@ -134,7 +134,6 @@ labels:
   type: apache2
 ```
 
-
 ## Recommended configuration
 
 ### Volumes
@@ -146,6 +145,14 @@ to avoid losing credentials and decision data in case of container destruction a
 * Acquisition: `/etc/crowdsec/acquis.d` and/or `/etc/crowdsec.acquis.yaml` (yes, they can be nested in `/etc/crowdsec`)
 * Database when using SQLite (default): `/var/lib/crowdsec/data`
 
+### Hub updates
+
+To ensure you have the latest version of the collections, scenarios, parsers, etc., you can set the variable `DO_HUB_UPGRADE` to true.
+This will perform an update/upgrade of the hub every time the container is started.
+
+Be aware that if your container is misbehaving and caught in a restart loop, the CrowdSec hub may ban your IP for some time and your containers
+will run with the version of the hub that is cached in the container's image. If you enable `DO_HUB_UPGRADE`, do it when your infrastructure is running
+correctly and make sure you have some monitoring in place.
 
 ## Start a Crowdsec instance
 
@@ -316,7 +323,7 @@ config.yaml) each time the container is run.
 | `BOUNCERS_ALLOWED_OU`   | bouncer-ou | OU values allowed for bouncers, separated by comma |
 |                         | | |
 | __Hub management__      | | |
-| `NO_HUB_UPGRADE`        | false | Skip hub update / upgrade when the container starts |
+| `DO_HUB_UPGRADE`        | false | Force hub update / upgrade when the container starts. If for some reason the container restarts too often, it may lead to a temporary ban from hub updates. |
 | `COLLECTIONS`           | | Collections to install, separated by space: `-e COLLECTIONS="crowdsecurity/linux crowdsecurity/apache2"` |
 | `PARSERS`               | | Parsers to install, separated by space |
 | `SCENARIOS`             | | Scenarios to install, separated by space |

+ 2 - 3
docker/docker_start.sh

@@ -304,9 +304,8 @@ conf_set_if "$PLUGIN_DIR" '.config_paths.plugin_dir = strenv(PLUGIN_DIR)'
 
 ## Install hub items
 
-cscli hub update || true
-
-if isfalse "$NO_HUB_UPGRADE"; then
+if istrue "$DO_HUB_UPGRADE"; then
+    cscli hub update || true
     cscli hub upgrade || true
 fi
 

+ 22 - 0
docker/preload-hub-items

@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+set -eu
+
+# pre-download everything but don't install anything
+
+echo "Pre-downloading Hub content..."
+
+types=$(cscli hub types -o raw)
+
+for itemtype in $types; do
+    ALL_ITEMS=$(cscli "$itemtype" list -a -o json | itemtype="$itemtype" yq '.[env(itemtype)][] | .name')
+    if [[ -n "${ALL_ITEMS}" ]]; then
+        #shellcheck disable=SC2086
+        cscli "$itemtype" install \
+            $ALL_ITEMS \
+            --download-only \
+            --error
+    fi
+done
+
+echo " done."

+ 1 - 1
docker/test/default.env

@@ -6,7 +6,7 @@ CROWDSEC_TEST_VERSION="dev"
 # All of the following flavors will be tested when using the "flavor" fixture
 CROWDSEC_TEST_FLAVORS="full"
 # CROWDSEC_TEST_FLAVORS="full,slim,debian"
-# CROWDSEC_TEST_FLAVORS="full,slim,debian,geoip,plugins-debian-slim,debian-geoip,debian-plugins"
+# CROWDSEC_TEST_FLAVORS="full,slim,debian,debian-slim"
 
 # network to use
 CROWDSEC_TEST_NETWORK="net-test"

+ 1 - 1
docker/test/tests/test_flavors.py

@@ -42,7 +42,7 @@ def test_flavor_content(crowdsec, flavor):
         x = cs.cont.exec_run(
             'ls -1 /usr/local/lib/crowdsec/plugins/')
         stdout = x.output.decode()
-        if 'slim' in flavor or 'geoip' in flavor:
+        if 'slim' in flavor:
             # the exact return code and full message depend
             # on the 'ls' implementation (busybox vs coreutils)
             assert x.exit_code != 0

+ 11 - 11
go.mod

@@ -24,6 +24,7 @@ require (
 	github.com/buger/jsonparser v1.1.1
 	github.com/c-robinson/iplib v1.0.3
 	github.com/cespare/xxhash/v2 v2.2.0
+	github.com/corazawaf/libinjection-go v0.1.2
 	github.com/crowdsecurity/coraza/v3 v3.0.0-20240108124027-a62b8d8e5607
 	github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26
 	github.com/crowdsecurity/go-cs-lib v0.0.6
@@ -31,7 +32,7 @@ require (
 	github.com/crowdsecurity/machineid v1.0.2
 	github.com/davecgh/go-spew v1.1.1
 	github.com/dghubble/sling v1.3.0
-	github.com/docker/docker v24.0.7+incompatible
+	github.com/docker/docker v24.0.9+incompatible
 	github.com/docker/go-connections v0.4.0
 	github.com/fatih/color v1.15.0
 	github.com/fsnotify/fsnotify v1.6.0
@@ -55,7 +56,7 @@ require (
 	github.com/hashicorp/go-version v1.2.1
 	github.com/hexops/gotextdiff v1.0.3
 	github.com/ivanpirog/coloredcobra v1.0.1
-	github.com/jackc/pgx/v4 v4.14.1
+	github.com/jackc/pgx/v4 v4.18.2
 	github.com/jarcoal/httpmock v1.1.0
 	github.com/jszwec/csvutil v1.5.1
 	github.com/lithammer/dedent v1.1.0
@@ -81,9 +82,9 @@ require (
 	github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26
 	github.com/wasilibs/go-re2 v1.3.0
 	github.com/xhit/go-simple-mail/v2 v2.16.0
-	golang.org/x/crypto v0.17.0
+	golang.org/x/crypto v0.22.0
 	golang.org/x/mod v0.11.0
-	golang.org/x/sys v0.15.0
+	golang.org/x/sys v0.19.0
 	golang.org/x/text v0.14.0
 	google.golang.org/grpc v1.56.3
 	google.golang.org/protobuf v1.33.0
@@ -104,7 +105,6 @@ require (
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/bytedance/sonic v1.9.1 // indirect
 	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
-	github.com/corazawaf/libinjection-go v0.1.2 // indirect
 	github.com/coreos/go-systemd/v22 v22.5.0 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
 	github.com/creack/pty v1.1.18 // indirect
@@ -137,12 +137,12 @@ require (
 	github.com/imdario/mergo v0.3.12 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/jackc/chunkreader/v2 v2.0.1 // indirect
-	github.com/jackc/pgconn v1.10.1 // indirect
+	github.com/jackc/pgconn v1.14.3 // indirect
 	github.com/jackc/pgio v1.0.0 // indirect
 	github.com/jackc/pgpassfile v1.0.0 // indirect
-	github.com/jackc/pgproto3/v2 v2.2.0 // indirect
-	github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
-	github.com/jackc/pgtype v1.9.1 // indirect
+	github.com/jackc/pgproto3/v2 v2.3.3 // indirect
+	github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
+	github.com/jackc/pgtype v1.14.0 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
@@ -198,9 +198,9 @@ require (
 	github.com/zclconf/go-cty v1.8.0 // indirect
 	go.mongodb.org/mongo-driver v1.9.4 // indirect
 	golang.org/x/arch v0.3.0 // indirect
-	golang.org/x/net v0.19.0 // indirect
+	golang.org/x/net v0.24.0 // indirect
 	golang.org/x/sync v0.6.0 // indirect
-	golang.org/x/term v0.15.0 // indirect
+	golang.org/x/term v0.19.0 // indirect
 	golang.org/x/time v0.3.0 // indirect
 	golang.org/x/tools v0.8.1-0.20230428195545-5283a0178901 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect

+ 20 - 20
go.sum

@@ -116,8 +116,8 @@ github.com/dghubble/sling v1.3.0 h1:pZHjCJq4zJvc6qVQ5wN1jo5oNZlNE0+8T/h0XeXBUKU=
 github.com/dghubble/sling v1.3.0/go.mod h1:XXShWaBWKzNLhu2OxikSNFrlsvowtz4kyRuXUG7oQKY=
 github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
 github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
-github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
-github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0=
+github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
 github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
 github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
@@ -368,8 +368,8 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU
 github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
 github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
 github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
-github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8=
-github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
+github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
 github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
 github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
 github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
@@ -385,26 +385,26 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW
 github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
 github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
-github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns=
-github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
-github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
+github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
+github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
+github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
+github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
 github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
 github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
 github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
 github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
-github.com/jackc/pgtype v1.9.1 h1:MJc2s0MFS8C3ok1wQTdQxWuXQcB6+HwAm5x1CzW7mf0=
-github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
+github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
 github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
 github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
 github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
 github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
-github.com/jackc/pgx/v4 v4.14.1 h1:71oo1KAGI6mXhLiTMn6iDFcp3e7+zon/capWjl2OEFU=
-github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
+github.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU=
+github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
 github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jarcoal/httpmock v1.1.0 h1:F47ChZj1Y2zFsCXxNkBPwNNKnAyOATcdQibk0qEdVCE=
 github.com/jarcoal/httpmock v1.1.0/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
 github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
@@ -757,8 +757,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
-golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
-golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
+golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
@@ -791,8 +791,8 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
-golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
-golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
+golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
+golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -841,8 +841,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 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=
@@ -850,8 +850,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
-golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
-golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
+golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
+golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

+ 714 - 0
pkg/acquisition/modules/appsec/appsec_hooks_test.go

@@ -0,0 +1,714 @@
+package appsecacquisition
+
+import (
+	"net/http"
+	"net/url"
+	"testing"
+
+	"github.com/crowdsecurity/crowdsec/pkg/appsec"
+	"github.com/crowdsecurity/crowdsec/pkg/appsec/appsec_rule"
+	"github.com/crowdsecurity/crowdsec/pkg/types"
+	"github.com/davecgh/go-spew/spew"
+	log "github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/require"
+)
+
+func TestAppsecOnMatchHooks(t *testing.T) {
+	tests := []appsecRuleTest{
+		{
+			name:             "no rule : check return code",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Len(t, responses, 1)
+				require.Equal(t, 403, responses[0].BouncerHTTPResponseCode)
+				require.Equal(t, 403, responses[0].UserHTTPResponseCode)
+				require.Equal(t, appsec.BanRemediation, responses[0].Action)
+
+			},
+		},
+		{
+			name:             "on_match: change return code",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			on_match: []appsec.Hook{
+				{Filter: "IsInBand == true", Apply: []string{"SetReturnCode(413)"}},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Len(t, responses, 1)
+				require.Equal(t, 403, responses[0].BouncerHTTPResponseCode)
+				require.Equal(t, 413, responses[0].UserHTTPResponseCode)
+				require.Equal(t, appsec.BanRemediation, responses[0].Action)
+			},
+		},
+		{
+			name:             "on_match: change action to a non standard one (log)",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			on_match: []appsec.Hook{
+				{Filter: "IsInBand == true", Apply: []string{"SetRemediation('log')"}},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Len(t, responses, 1)
+				require.Equal(t, "log", responses[0].Action)
+				require.Equal(t, 403, responses[0].BouncerHTTPResponseCode)
+				require.Equal(t, 403, responses[0].UserHTTPResponseCode)
+			},
+		},
+		{
+			name:             "on_match: change action to another standard one (allow)",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			on_match: []appsec.Hook{
+				{Filter: "IsInBand == true", Apply: []string{"SetRemediation('allow')"}},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Len(t, responses, 1)
+				require.Equal(t, appsec.AllowRemediation, responses[0].Action)
+			},
+		},
+		{
+			name:             "on_match: change action to another standard one (ban)",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			on_match: []appsec.Hook{
+				{Filter: "IsInBand == true", Apply: []string{"SetRemediation('ban')"}},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, responses, 1)
+				//note: SetAction normalizes deny, ban and block to ban
+				require.Equal(t, appsec.BanRemediation, responses[0].Action)
+			},
+		},
+		{
+			name:             "on_match: change action to another standard one (captcha)",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			on_match: []appsec.Hook{
+				{Filter: "IsInBand == true", Apply: []string{"SetRemediation('captcha')"}},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, responses, 1)
+				//note: SetAction normalizes deny, ban and block to ban
+				require.Equal(t, appsec.CaptchaRemediation, responses[0].Action)
+			},
+		},
+		{
+			name:             "on_match: change action to a non standard one",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			on_match: []appsec.Hook{
+				{Filter: "IsInBand == true", Apply: []string{"SetRemediation('foobar')"}},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Len(t, responses, 1)
+				require.Equal(t, "foobar", responses[0].Action)
+			},
+		},
+		{
+			name:             "on_match: cancel alert",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule42",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			on_match: []appsec.Hook{
+				{Filter: "IsInBand == true && LogInfo('XX -> %s', evt.Appsec.MatchedRules.GetName())", Apply: []string{"CancelAlert()"}},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 1)
+				require.Equal(t, types.LOG, events[0].Type)
+				require.Len(t, responses, 1)
+				require.Equal(t, appsec.BanRemediation, responses[0].Action)
+			},
+		},
+		{
+			name:             "on_match: cancel event",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule42",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			on_match: []appsec.Hook{
+				{Filter: "IsInBand == true", Apply: []string{"CancelEvent()"}},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 1)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Len(t, responses, 1)
+				require.Equal(t, appsec.BanRemediation, responses[0].Action)
+			},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			loadAppSecEngine(test, t)
+		})
+	}
+}
+
+func TestAppsecPreEvalHooks(t *testing.T) {
+
+	tests := []appsecRuleTest{
+		{
+			name:             "Basic on_load hook to disable inband rule",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			pre_eval: []appsec.Hook{
+				{Filter: "1 == 1", Apply: []string{"RemoveInBandRuleByName('rule1')"}},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Empty(t, events)
+				require.Len(t, responses, 1)
+				require.False(t, responses[0].InBandInterrupt)
+				require.False(t, responses[0].OutOfBandInterrupt)
+			},
+		},
+		{
+			name:             "Basic on_load fails to disable rule",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			pre_eval: []appsec.Hook{
+				{Filter: "1 ==2", Apply: []string{"RemoveInBandRuleByName('rule1')"}},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+
+				require.Equal(t, types.LOG, events[1].Type)
+				require.True(t, events[1].Appsec.HasInBandMatches)
+				require.Len(t, events[1].Appsec.MatchedRules, 1)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+
+				require.Len(t, responses, 1)
+				require.True(t, responses[0].InBandInterrupt)
+
+			},
+		},
+		{
+			name:             "on_load : disable inband by tag",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rulez",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			pre_eval: []appsec.Hook{
+				{Apply: []string{"RemoveInBandRuleByTag('crowdsec-rulez')"}},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Empty(t, events)
+				require.Len(t, responses, 1)
+				require.False(t, responses[0].InBandInterrupt)
+				require.False(t, responses[0].OutOfBandInterrupt)
+			},
+		},
+		{
+			name:             "on_load : disable inband by ID",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rulez",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			pre_eval: []appsec.Hook{
+				{Apply: []string{"RemoveInBandRuleByID(1516470898)"}}, //rule ID is generated at runtime. If you change rule, it will break the test (:
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Empty(t, events)
+				require.Len(t, responses, 1)
+				require.False(t, responses[0].InBandInterrupt)
+				require.False(t, responses[0].OutOfBandInterrupt)
+			},
+		},
+		{
+			name:             "on_load : disable inband by name",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rulez",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			pre_eval: []appsec.Hook{
+				{Apply: []string{"RemoveInBandRuleByName('rulez')"}},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Empty(t, events)
+				require.Len(t, responses, 1)
+				require.False(t, responses[0].InBandInterrupt)
+				require.False(t, responses[0].OutOfBandInterrupt)
+			},
+		},
+		{
+			name:             "on_load : outofband default behavior",
+			expected_load_ok: true,
+			outofband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rulez",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 1)
+				require.Equal(t, types.LOG, events[0].Type)
+				require.True(t, events[0].Appsec.HasOutBandMatches)
+				require.False(t, events[0].Appsec.HasInBandMatches)
+				require.Len(t, events[0].Appsec.MatchedRules, 1)
+				require.Equal(t, "rulez", events[0].Appsec.MatchedRules[0]["msg"])
+				//maybe surprising, but response won't mention OOB event, as it's sent as soon as the inband phase is over.
+				require.Len(t, responses, 1)
+				require.False(t, responses[0].InBandInterrupt)
+				require.False(t, responses[0].OutOfBandInterrupt)
+			},
+		},
+		{
+			name:             "on_load : set remediation by tag",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rulez",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			pre_eval: []appsec.Hook{
+				{Apply: []string{"SetRemediationByTag('crowdsec-rulez', 'foobar')"}}, //rule ID is generated at runtime. If you change rule, it will break the test (:
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Len(t, responses, 1)
+				require.Equal(t, "foobar", responses[0].Action)
+			},
+		},
+		{
+			name:             "on_load : set remediation by name",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rulez",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			pre_eval: []appsec.Hook{
+				{Apply: []string{"SetRemediationByName('rulez', 'foobar')"}}, //rule ID is generated at runtime. If you change rule, it will break the test (:
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Len(t, responses, 1)
+				require.Equal(t, "foobar", responses[0].Action)
+			},
+		},
+		{
+			name:             "on_load : set remediation by ID",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rulez",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			pre_eval: []appsec.Hook{
+				{Apply: []string{"SetRemediationByID(1516470898, 'foobar')"}}, //rule ID is generated at runtime. If you change rule, it will break the test (:
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Len(t, responses, 1)
+				require.Equal(t, "foobar", responses[0].Action)
+				require.Equal(t, "foobar", appsecResponse.Action)
+				require.Equal(t, http.StatusForbidden, appsecResponse.HTTPStatus)
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			loadAppSecEngine(test, t)
+		})
+	}
+}
+
+func TestAppsecRemediationConfigHooks(t *testing.T) {
+
+	tests := []appsecRuleTest{
+		{
+			name:             "Basic matching rule",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Equal(t, appsec.BanRemediation, responses[0].Action)
+				require.Equal(t, http.StatusForbidden, statusCode)
+				require.Equal(t, appsec.BanRemediation, appsecResponse.Action)
+				require.Equal(t, http.StatusForbidden, appsecResponse.HTTPStatus)
+			},
+		},
+		{
+			name:             "SetRemediation",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			on_match: []appsec.Hook{{Apply: []string{"SetRemediation('captcha')"}}}, //rule ID is generated at runtime. If you change rule, it will break the test (:
+
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Equal(t, appsec.CaptchaRemediation, responses[0].Action)
+				require.Equal(t, http.StatusForbidden, statusCode)
+				require.Equal(t, appsec.CaptchaRemediation, appsecResponse.Action)
+				require.Equal(t, http.StatusForbidden, appsecResponse.HTTPStatus)
+			},
+		},
+		{
+			name:             "SetRemediation",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			on_match: []appsec.Hook{{Apply: []string{"SetReturnCode(418)"}}}, //rule ID is generated at runtime. If you change rule, it will break the test (:
+
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Equal(t, appsec.BanRemediation, responses[0].Action)
+				require.Equal(t, http.StatusForbidden, statusCode)
+				require.Equal(t, appsec.BanRemediation, appsecResponse.Action)
+				require.Equal(t, http.StatusTeapot, appsecResponse.HTTPStatus)
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			loadAppSecEngine(test, t)
+		})
+	}
+}
+func TestOnMatchRemediationHooks(t *testing.T) {
+	tests := []appsecRuleTest{
+		{
+			name:             "set remediation to allow with on_match hook",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule42",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			on_match: []appsec.Hook{
+				{Filter: "IsInBand == true", Apply: []string{"SetRemediation('allow')"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
+				require.Equal(t, http.StatusOK, appsecResponse.HTTPStatus)
+			},
+		},
+		{
+			name:             "set remediation to captcha + custom user code with on_match hook",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule42",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			DefaultRemediation: appsec.AllowRemediation,
+			on_match: []appsec.Hook{
+				{Filter: "IsInBand == true", Apply: []string{"SetRemediation('captcha')", "SetReturnCode(418)"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				spew.Dump(responses)
+				spew.Dump(appsecResponse)
+
+				log.Errorf("http status : %d", statusCode)
+				require.Equal(t, appsec.CaptchaRemediation, appsecResponse.Action)
+				require.Equal(t, http.StatusTeapot, appsecResponse.HTTPStatus)
+				require.Equal(t, http.StatusForbidden, statusCode)
+			},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			loadAppSecEngine(test, t)
+		})
+	}
+}

+ 74 - 0
pkg/acquisition/modules/appsec/appsec_lnx_test.go

@@ -0,0 +1,74 @@
+//go:build !windows
+// +build !windows
+
+package appsecacquisition
+
+import (
+	"testing"
+
+	"github.com/crowdsecurity/crowdsec/pkg/appsec"
+	"github.com/crowdsecurity/crowdsec/pkg/appsec/appsec_rule"
+	"github.com/crowdsecurity/crowdsec/pkg/types"
+	log "github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/require"
+)
+
+func TestAppsecRuleTransformsOthers(t *testing.T) {
+
+	log.SetLevel(log.TraceLevel)
+	tests := []appsecRuleTest{
+		{
+			name:             "normalizepath",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "equals", Value: "b/c"},
+					Transform: []string{"normalizepath"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/?foo=a/../b/c",
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+		{
+			name:             "normalizepath #2",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "equals", Value: "b/c/"},
+					Transform: []string{"normalizepath"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/?foo=a/../b/c/////././././",
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			loadAppSecEngine(test, t)
+		})
+	}
+}

+ 320 - 0
pkg/acquisition/modules/appsec/appsec_remediation_test.go

@@ -0,0 +1,320 @@
+package appsecacquisition
+
+import (
+	"net/http"
+	"net/url"
+	"testing"
+
+	"github.com/crowdsecurity/crowdsec/pkg/appsec"
+	"github.com/crowdsecurity/crowdsec/pkg/appsec/appsec_rule"
+	"github.com/crowdsecurity/crowdsec/pkg/types"
+	"github.com/stretchr/testify/require"
+)
+
+func TestAppsecDefaultPassRemediation(t *testing.T) {
+
+	tests := []appsecRuleTest{
+		{
+			name:             "Basic non-matching rule",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/",
+				Args:       url.Values{"foo": []string{"tutu"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Equal(t, appsec.AllowRemediation, responses[0].Action)
+				require.Equal(t, http.StatusOK, statusCode)
+				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
+				require.Equal(t, http.StatusOK, appsecResponse.HTTPStatus)
+			},
+		},
+		{
+			name:             "DefaultPassAction: pass",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/",
+				Args:       url.Values{"foo": []string{"tutu"}},
+			},
+			DefaultPassAction: "allow",
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Equal(t, appsec.AllowRemediation, responses[0].Action)
+				require.Equal(t, http.StatusOK, statusCode)
+				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
+				require.Equal(t, http.StatusOK, appsecResponse.HTTPStatus)
+			},
+		},
+		{
+			name:             "DefaultPassAction: captcha",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/",
+				Args:       url.Values{"foo": []string{"tutu"}},
+			},
+			DefaultPassAction: "captcha",
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Equal(t, appsec.CaptchaRemediation, responses[0].Action)
+				require.Equal(t, http.StatusOK, statusCode) //@tko: body is captcha, but as it's 200, captcha won't be showed to user
+				require.Equal(t, appsec.CaptchaRemediation, appsecResponse.Action)
+				require.Equal(t, http.StatusOK, appsecResponse.HTTPStatus)
+			},
+		},
+		{
+			name:             "DefaultPassHTTPCode: 200",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/",
+				Args:       url.Values{"foo": []string{"tutu"}},
+			},
+			UserPassedHTTPCode: 200,
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Equal(t, appsec.AllowRemediation, responses[0].Action)
+				require.Equal(t, http.StatusOK, statusCode)
+				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
+				require.Equal(t, http.StatusOK, appsecResponse.HTTPStatus)
+			},
+		},
+		{
+			name:             "DefaultPassHTTPCode: 200",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/",
+				Args:       url.Values{"foo": []string{"tutu"}},
+			},
+			UserPassedHTTPCode: 418,
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Equal(t, appsec.AllowRemediation, responses[0].Action)
+				require.Equal(t, http.StatusOK, statusCode)
+				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
+				require.Equal(t, http.StatusTeapot, appsecResponse.HTTPStatus)
+			},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			loadAppSecEngine(test, t)
+		})
+	}
+}
+
+func TestAppsecDefaultRemediation(t *testing.T) {
+
+	tests := []appsecRuleTest{
+		{
+			name:             "Basic matching rule",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Equal(t, appsec.BanRemediation, responses[0].Action)
+				require.Equal(t, http.StatusForbidden, statusCode)
+				require.Equal(t, appsec.BanRemediation, appsecResponse.Action)
+				require.Equal(t, http.StatusForbidden, appsecResponse.HTTPStatus)
+			},
+		},
+		{
+			name:             "default remediation to ban (default)",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule42",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			DefaultRemediation: "ban",
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Equal(t, appsec.BanRemediation, responses[0].Action)
+				require.Equal(t, http.StatusForbidden, statusCode)
+				require.Equal(t, appsec.BanRemediation, appsecResponse.Action)
+				require.Equal(t, http.StatusForbidden, appsecResponse.HTTPStatus)
+			},
+		},
+		{
+			name:             "default remediation to allow",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule42",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			DefaultRemediation: "allow",
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Equal(t, appsec.AllowRemediation, responses[0].Action)
+				require.Equal(t, http.StatusOK, statusCode)
+				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
+				require.Equal(t, http.StatusOK, appsecResponse.HTTPStatus)
+			},
+		},
+		{
+			name:             "default remediation to captcha",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule42",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			DefaultRemediation: "captcha",
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Equal(t, appsec.CaptchaRemediation, responses[0].Action)
+				require.Equal(t, http.StatusForbidden, statusCode)
+				require.Equal(t, appsec.CaptchaRemediation, appsecResponse.Action)
+				require.Equal(t, http.StatusForbidden, appsecResponse.HTTPStatus)
+			},
+		},
+		{
+			name:             "custom user HTTP code",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule42",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			UserBlockedHTTPCode: 418,
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Equal(t, appsec.BanRemediation, responses[0].Action)
+				require.Equal(t, http.StatusForbidden, statusCode)
+				require.Equal(t, appsec.BanRemediation, appsecResponse.Action)
+				require.Equal(t, http.StatusTeapot, appsecResponse.HTTPStatus)
+			},
+		},
+		{
+			name:             "custom remediation + HTTP code",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule42",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			UserBlockedHTTPCode: 418,
+			DefaultRemediation:  "foobar",
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Equal(t, "foobar", responses[0].Action)
+				require.Equal(t, http.StatusForbidden, statusCode)
+				require.Equal(t, "foobar", appsecResponse.Action)
+				require.Equal(t, http.StatusTeapot, appsecResponse.HTTPStatus)
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			loadAppSecEngine(test, t)
+		})
+	}
+}

+ 733 - 0
pkg/acquisition/modules/appsec/appsec_rules_test.go

@@ -0,0 +1,733 @@
+package appsecacquisition
+
+import (
+	"net/http"
+	"net/url"
+	"testing"
+
+	"github.com/crowdsecurity/crowdsec/pkg/appsec"
+	"github.com/crowdsecurity/crowdsec/pkg/appsec/appsec_rule"
+	"github.com/crowdsecurity/crowdsec/pkg/types"
+	log "github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/require"
+)
+
+func TestAppsecRuleMatches(t *testing.T) {
+
+	tests := []appsecRuleTest{
+		{
+			name:             "Basic matching rule",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+
+				require.Equal(t, types.LOG, events[1].Type)
+				require.True(t, events[1].Appsec.HasInBandMatches)
+				require.Len(t, events[1].Appsec.MatchedRules, 1)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+
+				require.Len(t, responses, 1)
+				require.True(t, responses[0].InBandInterrupt)
+			},
+		},
+		{
+			name:             "Basic non-matching rule",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"tutu"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Empty(t, events)
+				require.Len(t, responses, 1)
+				require.False(t, responses[0].InBandInterrupt)
+				require.False(t, responses[0].OutOfBandInterrupt)
+			},
+		},
+		{
+			name:             "default remediation to allow",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule42",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			DefaultRemediation: "allow",
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Equal(t, appsec.AllowRemediation, responses[0].Action)
+				require.Equal(t, http.StatusOK, statusCode)
+				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
+				require.Equal(t, http.StatusOK, appsecResponse.HTTPStatus)
+			},
+		},
+		{
+			name:             "default remediation to captcha",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule42",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			DefaultRemediation: "captcha",
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Equal(t, appsec.CaptchaRemediation, responses[0].Action)
+				require.Equal(t, http.StatusForbidden, statusCode)
+				require.Equal(t, appsec.CaptchaRemediation, appsecResponse.Action)
+				require.Equal(t, http.StatusForbidden, appsecResponse.HTTPStatus)
+			},
+		},
+		{
+			name:             "no default remediation / custom user HTTP code",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule42",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"toto"}},
+			},
+			UserBlockedHTTPCode: 418,
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Equal(t, appsec.BanRemediation, responses[0].Action)
+				require.Equal(t, http.StatusForbidden, statusCode)
+				require.Equal(t, appsec.BanRemediation, appsecResponse.Action)
+				require.Equal(t, http.StatusTeapot, appsecResponse.HTTPStatus)
+			},
+		},
+		{
+			name:             "no match but try to set remediation to captcha with on_match hook",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule42",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			on_match: []appsec.Hook{
+				{Filter: "IsInBand == true", Apply: []string{"SetRemediation('captcha')"}},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"bla"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Empty(t, events)
+				require.Equal(t, http.StatusOK, statusCode)
+				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
+			},
+		},
+		{
+			name:             "no match but try to set user HTTP code with on_match hook",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule42",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			on_match: []appsec.Hook{
+				{Filter: "IsInBand == true", Apply: []string{"SetReturnCode(418)"}},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"bla"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Empty(t, events)
+				require.Equal(t, http.StatusOK, statusCode)
+				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
+			},
+		},
+		{
+			name:             "no match but try to set  remediation with pre_eval hook",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule42",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			pre_eval: []appsec.Hook{
+				{Filter: "IsInBand == true", Apply: []string{"SetRemediationByName('rule42', 'captcha')"}},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/urllll",
+				Args:       url.Values{"foo": []string{"bla"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Empty(t, events)
+				require.Equal(t, http.StatusOK, statusCode)
+				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			loadAppSecEngine(test, t)
+		})
+	}
+}
+
+func TestAppsecRuleTransforms(t *testing.T) {
+
+	log.SetLevel(log.TraceLevel)
+	tests := []appsecRuleTest{
+		{
+			name:             "Basic matching rule",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:  "rule1",
+					Zones: []string{"URI"},
+					Match: appsec_rule.Match{Type: "equals", Value: "/toto"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/toto",
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+		{
+			name:             "lowercase",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"URI"},
+					Match:     appsec_rule.Match{Type: "equals", Value: "/toto"},
+					Transform: []string{"lowercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/TOTO",
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+		{
+			name:             "uppercase",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"URI"},
+					Match:     appsec_rule.Match{Type: "equals", Value: "/TOTO"},
+					Transform: []string{"uppercase"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/toto",
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+		{
+			name:             "b64decode",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "equals", Value: "toto"},
+					Transform: []string{"b64decode"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/?foo=dG90bw",
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+		{
+			name:             "b64decode with extra padding",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "equals", Value: "toto"},
+					Transform: []string{"b64decode"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/?foo=dG90bw===",
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+		{
+			name:             "length",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "gte", Value: "3"},
+					Transform: []string{"length"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/?foo=toto",
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+		{
+			name:             "urldecode",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "equals", Value: "BB/A"},
+					Transform: []string{"urldecode"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/?foo=%42%42%2F%41",
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+		{
+			name:             "trim",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:      "rule1",
+					Zones:     []string{"ARGS"},
+					Variables: []string{"foo"},
+					Match:     appsec_rule.Match{Type: "equals", Value: "BB/A"},
+					Transform: []string{"urldecode", "trim"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/?foo=%20%20%42%42%2F%41%20%20",
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			loadAppSecEngine(test, t)
+		})
+	}
+}
+
+func TestAppsecRuleZones(t *testing.T) {
+
+	log.SetLevel(log.TraceLevel)
+	tests := []appsecRuleTest{
+		{
+			name:             "rule: ARGS",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:  "rule1",
+					Zones: []string{"ARGS"},
+					Match: appsec_rule.Match{Type: "equals", Value: "toto"},
+				},
+				{
+					Name:  "rule2",
+					Zones: []string{"ARGS"},
+					Match: appsec_rule.Match{Type: "equals", Value: "foobar"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/foobar?something=toto&foobar=smth",
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+		{
+			name:             "rule: ARGS_NAMES",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:  "rule1",
+					Zones: []string{"ARGS_NAMES"},
+					Match: appsec_rule.Match{Type: "equals", Value: "toto"},
+				},
+				{
+					Name:  "rule2",
+					Zones: []string{"ARGS_NAMES"},
+					Match: appsec_rule.Match{Type: "equals", Value: "foobar"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/foobar?something=toto&foobar=smth",
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule2", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+		{
+			name:             "rule: BODY_ARGS",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:  "rule1",
+					Zones: []string{"BODY_ARGS"},
+					Match: appsec_rule.Match{Type: "equals", Value: "toto"},
+				},
+				{
+					Name:  "rule2",
+					Zones: []string{"BODY_ARGS"},
+					Match: appsec_rule.Match{Type: "equals", Value: "foobar"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/",
+				Body:       []byte("smth=toto&foobar=other"),
+				Headers:    http.Header{"Content-Type": []string{"application/x-www-form-urlencoded"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+		{
+			name:             "rule: BODY_ARGS_NAMES",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:  "rule1",
+					Zones: []string{"BODY_ARGS_NAMES"},
+					Match: appsec_rule.Match{Type: "equals", Value: "toto"},
+				},
+				{
+					Name:  "rule2",
+					Zones: []string{"BODY_ARGS_NAMES"},
+					Match: appsec_rule.Match{Type: "equals", Value: "foobar"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/",
+				Body:       []byte("smth=toto&foobar=other"),
+				Headers:    http.Header{"Content-Type": []string{"application/x-www-form-urlencoded"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule2", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+		{
+			name:             "rule: HEADERS",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:  "rule1",
+					Zones: []string{"HEADERS"},
+					Match: appsec_rule.Match{Type: "equals", Value: "toto"},
+				},
+				{
+					Name:  "rule2",
+					Zones: []string{"HEADERS"},
+					Match: appsec_rule.Match{Type: "equals", Value: "foobar"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/",
+				Headers:    http.Header{"foobar": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+		{
+			name:             "rule: HEADERS_NAMES",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:  "rule1",
+					Zones: []string{"HEADERS_NAMES"},
+					Match: appsec_rule.Match{Type: "equals", Value: "toto"},
+				},
+				{
+					Name:  "rule2",
+					Zones: []string{"HEADERS_NAMES"},
+					Match: appsec_rule.Match{Type: "equals", Value: "foobar"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/",
+				Headers:    http.Header{"foobar": []string{"toto"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule2", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+		{
+			name:             "rule: METHOD",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:  "rule1",
+					Zones: []string{"METHOD"},
+					Match: appsec_rule.Match{Type: "equals", Value: "GET"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/",
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+		{
+			name:             "rule: PROTOCOL",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:  "rule1",
+					Zones: []string{"PROTOCOL"},
+					Match: appsec_rule.Match{Type: "contains", Value: "3.1"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/",
+				Proto:      "HTTP/3.1",
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+		{
+			name:             "rule: URI",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:  "rule1",
+					Zones: []string{"URI"},
+					Match: appsec_rule.Match{Type: "equals", Value: "/foobar"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/foobar",
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+		{
+			name:             "rule: URI_FULL",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:  "rule1",
+					Zones: []string{"URI_FULL"},
+					Match: appsec_rule.Match{Type: "equals", Value: "/foobar?a=b"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/foobar?a=b",
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+		{
+			name:             "rule: RAW_BODY",
+			expected_load_ok: true,
+			inband_rules: []appsec_rule.CustomRule{
+				{
+					Name:  "rule1",
+					Zones: []string{"RAW_BODY"},
+					Match: appsec_rule.Match{Type: "equals", Value: "foobar=42421"},
+				},
+			},
+			input_request: appsec.ParsedRequest{
+				RemoteAddr: "1.2.3.4",
+				Method:     "GET",
+				URI:        "/",
+				Body:       []byte("foobar=42421"),
+				Headers:    http.Header{"Content-Type": []string{"application/x-www-form-urlencoded"}},
+			},
+			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+				require.Len(t, events, 2)
+				require.Equal(t, types.APPSEC, events[0].Type)
+				require.Equal(t, types.LOG, events[1].Type)
+				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+			},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			loadAppSecEngine(test, t)
+		})
+	}
+}

+ 0 - 1254
pkg/acquisition/modules/appsec/appsec_test.go

@@ -1,8 +1,6 @@
 package appsecacquisition
 
 import (
-	"net/http"
-	"net/url"
 	"testing"
 	"time"
 
@@ -12,15 +10,8 @@ import (
 	"github.com/davecgh/go-spew/spew"
 	"github.com/google/uuid"
 	log "github.com/sirupsen/logrus"
-	"github.com/stretchr/testify/require"
 )
 
-/*
-Missing tests (wip):
- - GenerateResponse
- - evt.Appsec and it's subobjects and methods
-*/
-
 type appsecRuleTest struct {
 	name                   string
 	expected_load_ok       bool
@@ -39,1251 +30,6 @@ type appsecRuleTest struct {
 	output_asserts         func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int)
 }
 
-func TestAppsecOnMatchHooks(t *testing.T) {
-	tests := []appsecRuleTest{
-		{
-			name:             "no rule : check return code",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Len(t, events, 2)
-				require.Equal(t, types.APPSEC, events[0].Type)
-				require.Equal(t, types.LOG, events[1].Type)
-				require.Len(t, responses, 1)
-				require.Equal(t, 403, responses[0].BouncerHTTPResponseCode)
-				require.Equal(t, 403, responses[0].UserHTTPResponseCode)
-				require.Equal(t, appsec.BanRemediation, responses[0].Action)
-
-			},
-		},
-		{
-			name:             "on_match: change return code",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			on_match: []appsec.Hook{
-				{Filter: "IsInBand == true", Apply: []string{"SetReturnCode(413)"}},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Len(t, events, 2)
-				require.Equal(t, types.APPSEC, events[0].Type)
-				require.Equal(t, types.LOG, events[1].Type)
-				require.Len(t, responses, 1)
-				require.Equal(t, 403, responses[0].BouncerHTTPResponseCode)
-				require.Equal(t, 413, responses[0].UserHTTPResponseCode)
-				require.Equal(t, appsec.BanRemediation, responses[0].Action)
-			},
-		},
-		{
-			name:             "on_match: change action to a non standard one (log)",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			on_match: []appsec.Hook{
-				{Filter: "IsInBand == true", Apply: []string{"SetRemediation('log')"}},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Len(t, events, 2)
-				require.Equal(t, types.APPSEC, events[0].Type)
-				require.Equal(t, types.LOG, events[1].Type)
-				require.Len(t, responses, 1)
-				require.Equal(t, "log", responses[0].Action)
-			},
-		},
-		{
-			name:             "on_match: change action to another standard one (allow)",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			on_match: []appsec.Hook{
-				{Filter: "IsInBand == true", Apply: []string{"SetRemediation('allow')"}},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Len(t, events, 2)
-				require.Equal(t, types.APPSEC, events[0].Type)
-				require.Equal(t, types.LOG, events[1].Type)
-				require.Len(t, responses, 1)
-				require.Equal(t, appsec.AllowRemediation, responses[0].Action)
-			},
-		},
-		{
-			name:             "on_match: change action to another standard one (ban)",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			on_match: []appsec.Hook{
-				{Filter: "IsInBand == true", Apply: []string{"SetRemediation('ban')"}},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Len(t, responses, 1)
-				//note: SetAction normalizes deny, ban and block to ban
-				require.Equal(t, appsec.BanRemediation, responses[0].Action)
-			},
-		},
-		{
-			name:             "on_match: change action to another standard one (captcha)",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			on_match: []appsec.Hook{
-				{Filter: "IsInBand == true", Apply: []string{"SetRemediation('captcha')"}},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Len(t, responses, 1)
-				//note: SetAction normalizes deny, ban and block to ban
-				require.Equal(t, appsec.CaptchaRemediation, responses[0].Action)
-			},
-		},
-		{
-			name:             "on_match: change action to a non standard one",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			on_match: []appsec.Hook{
-				{Filter: "IsInBand == true", Apply: []string{"SetRemediation('foobar')"}},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Len(t, events, 2)
-				require.Equal(t, types.APPSEC, events[0].Type)
-				require.Equal(t, types.LOG, events[1].Type)
-				require.Len(t, responses, 1)
-				require.Equal(t, "foobar", responses[0].Action)
-			},
-		},
-		{
-			name:             "on_match: cancel alert",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule42",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			on_match: []appsec.Hook{
-				{Filter: "IsInBand == true && LogInfo('XX -> %s', evt.Appsec.MatchedRules.GetName())", Apply: []string{"CancelAlert()"}},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Len(t, events, 1)
-				require.Equal(t, types.LOG, events[0].Type)
-				require.Len(t, responses, 1)
-				require.Equal(t, appsec.BanRemediation, responses[0].Action)
-			},
-		},
-		{
-			name:             "on_match: cancel event",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule42",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			on_match: []appsec.Hook{
-				{Filter: "IsInBand == true", Apply: []string{"CancelEvent()"}},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Len(t, events, 1)
-				require.Equal(t, types.APPSEC, events[0].Type)
-				require.Len(t, responses, 1)
-				require.Equal(t, appsec.BanRemediation, responses[0].Action)
-			},
-		},
-	}
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			loadAppSecEngine(test, t)
-		})
-	}
-}
-
-func TestAppsecPreEvalHooks(t *testing.T) {
-	/*
-	 [x] basic working hook
-	 [x] basic failing hook
-	 [ ] test the "OnSuccess" feature
-	 [ ] test multiple competing hooks
-	 [ ] test the variety of helpers
-	*/
-	tests := []appsecRuleTest{
-		{
-			name:             "Basic on_load hook to disable inband rule",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			pre_eval: []appsec.Hook{
-				{Filter: "1 == 1", Apply: []string{"RemoveInBandRuleByName('rule1')"}},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Empty(t, events)
-				require.Len(t, responses, 1)
-				require.False(t, responses[0].InBandInterrupt)
-				require.False(t, responses[0].OutOfBandInterrupt)
-			},
-		},
-		{
-			name:             "Basic on_load fails to disable rule",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			pre_eval: []appsec.Hook{
-				{Filter: "1 ==2", Apply: []string{"RemoveInBandRuleByName('rule1')"}},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Len(t, events, 2)
-				require.Equal(t, types.APPSEC, events[0].Type)
-
-				require.Equal(t, types.LOG, events[1].Type)
-				require.True(t, events[1].Appsec.HasInBandMatches)
-				require.Len(t, events[1].Appsec.MatchedRules, 1)
-				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
-
-				require.Len(t, responses, 1)
-				require.True(t, responses[0].InBandInterrupt)
-
-			},
-		},
-		{
-			name:             "on_load : disable inband by tag",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rulez",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			pre_eval: []appsec.Hook{
-				{Apply: []string{"RemoveInBandRuleByTag('crowdsec-rulez')"}},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Empty(t, events)
-				require.Len(t, responses, 1)
-				require.False(t, responses[0].InBandInterrupt)
-				require.False(t, responses[0].OutOfBandInterrupt)
-			},
-		},
-		{
-			name:             "on_load : disable inband by ID",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rulez",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			pre_eval: []appsec.Hook{
-				{Apply: []string{"RemoveInBandRuleByID(1516470898)"}}, //rule ID is generated at runtime. If you change rule, it will break the test (:
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Empty(t, events)
-				require.Len(t, responses, 1)
-				require.False(t, responses[0].InBandInterrupt)
-				require.False(t, responses[0].OutOfBandInterrupt)
-			},
-		},
-		{
-			name:             "on_load : disable inband by name",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rulez",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			pre_eval: []appsec.Hook{
-				{Apply: []string{"RemoveInBandRuleByName('rulez')"}},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Empty(t, events)
-				require.Len(t, responses, 1)
-				require.False(t, responses[0].InBandInterrupt)
-				require.False(t, responses[0].OutOfBandInterrupt)
-			},
-		},
-		{
-			name:             "on_load : outofband default behavior",
-			expected_load_ok: true,
-			outofband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rulez",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Len(t, events, 1)
-				require.Equal(t, types.LOG, events[0].Type)
-				require.True(t, events[0].Appsec.HasOutBandMatches)
-				require.False(t, events[0].Appsec.HasInBandMatches)
-				require.Len(t, events[0].Appsec.MatchedRules, 1)
-				require.Equal(t, "rulez", events[0].Appsec.MatchedRules[0]["msg"])
-				//maybe surprising, but response won't mention OOB event, as it's sent as soon as the inband phase is over.
-				require.Len(t, responses, 1)
-				require.False(t, responses[0].InBandInterrupt)
-				require.False(t, responses[0].OutOfBandInterrupt)
-			},
-		},
-		{
-			name:             "on_load : set remediation by tag",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rulez",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			pre_eval: []appsec.Hook{
-				{Apply: []string{"SetRemediationByTag('crowdsec-rulez', 'foobar')"}}, //rule ID is generated at runtime. If you change rule, it will break the test (:
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Len(t, events, 2)
-				require.Len(t, responses, 1)
-				require.Equal(t, "foobar", responses[0].Action)
-			},
-		},
-		{
-			name:             "on_load : set remediation by name",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rulez",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			pre_eval: []appsec.Hook{
-				{Apply: []string{"SetRemediationByName('rulez', 'foobar')"}}, //rule ID is generated at runtime. If you change rule, it will break the test (:
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Len(t, events, 2)
-				require.Len(t, responses, 1)
-				require.Equal(t, "foobar", responses[0].Action)
-			},
-		},
-		{
-			name:             "on_load : set remediation by ID",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rulez",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			pre_eval: []appsec.Hook{
-				{Apply: []string{"SetRemediationByID(1516470898, 'foobar')"}}, //rule ID is generated at runtime. If you change rule, it will break the test (:
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Len(t, events, 2)
-				require.Len(t, responses, 1)
-				require.Equal(t, "foobar", responses[0].Action)
-				require.Equal(t, "foobar", appsecResponse.Action)
-				require.Equal(t, http.StatusForbidden, appsecResponse.HTTPStatus)
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			loadAppSecEngine(test, t)
-		})
-	}
-}
-
-func TestAppsecRemediationConfigHooks(t *testing.T) {
-
-	tests := []appsecRuleTest{
-		{
-			name:             "Basic matching rule",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Equal(t, appsec.BanRemediation, responses[0].Action)
-				require.Equal(t, http.StatusForbidden, statusCode)
-				require.Equal(t, appsec.BanRemediation, appsecResponse.Action)
-				require.Equal(t, http.StatusForbidden, appsecResponse.HTTPStatus)
-			},
-		},
-		{
-			name:             "SetRemediation",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			on_match: []appsec.Hook{{Apply: []string{"SetRemediation('captcha')"}}}, //rule ID is generated at runtime. If you change rule, it will break the test (:
-
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Equal(t, appsec.CaptchaRemediation, responses[0].Action)
-				require.Equal(t, http.StatusForbidden, statusCode)
-				require.Equal(t, appsec.CaptchaRemediation, appsecResponse.Action)
-				require.Equal(t, http.StatusForbidden, appsecResponse.HTTPStatus)
-			},
-		},
-		{
-			name:             "SetRemediation",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			on_match: []appsec.Hook{{Apply: []string{"SetReturnCode(418)"}}}, //rule ID is generated at runtime. If you change rule, it will break the test (:
-
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Equal(t, appsec.BanRemediation, responses[0].Action)
-				require.Equal(t, http.StatusForbidden, statusCode)
-				require.Equal(t, appsec.BanRemediation, appsecResponse.Action)
-				require.Equal(t, http.StatusTeapot, appsecResponse.HTTPStatus)
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			loadAppSecEngine(test, t)
-		})
-	}
-}
-func TestOnMatchRemediationHooks(t *testing.T) {
-	tests := []appsecRuleTest{
-		{
-			name:             "set remediation to allow with on_match hook",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule42",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			on_match: []appsec.Hook{
-				{Filter: "IsInBand == true", Apply: []string{"SetRemediation('allow')"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
-				require.Equal(t, http.StatusOK, appsecResponse.HTTPStatus)
-			},
-		},
-		{
-			name:             "set remediation to captcha + custom user code with on_match hook",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule42",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			DefaultRemediation: appsec.AllowRemediation,
-			on_match: []appsec.Hook{
-				{Filter: "IsInBand == true", Apply: []string{"SetRemediation('captcha')", "SetReturnCode(418)"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				spew.Dump(responses)
-				spew.Dump(appsecResponse)
-
-				log.Errorf("http status : %d", statusCode)
-				require.Equal(t, appsec.CaptchaRemediation, appsecResponse.Action)
-				require.Equal(t, http.StatusTeapot, appsecResponse.HTTPStatus)
-				require.Equal(t, http.StatusForbidden, statusCode)
-			},
-		},
-	}
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			loadAppSecEngine(test, t)
-		})
-	}
-}
-
-func TestAppsecDefaultPassRemediation(t *testing.T) {
-
-	tests := []appsecRuleTest{
-		{
-			name:             "Basic non-matching rule",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/",
-				Args:       url.Values{"foo": []string{"tutu"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Equal(t, appsec.AllowRemediation, responses[0].Action)
-				require.Equal(t, http.StatusOK, statusCode)
-				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
-				require.Equal(t, http.StatusOK, appsecResponse.HTTPStatus)
-			},
-		},
-		{
-			name:             "DefaultPassAction: pass",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/",
-				Args:       url.Values{"foo": []string{"tutu"}},
-			},
-			DefaultPassAction: "allow",
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Equal(t, appsec.AllowRemediation, responses[0].Action)
-				require.Equal(t, http.StatusOK, statusCode)
-				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
-				require.Equal(t, http.StatusOK, appsecResponse.HTTPStatus)
-			},
-		},
-		{
-			name:             "DefaultPassAction: captcha",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/",
-				Args:       url.Values{"foo": []string{"tutu"}},
-			},
-			DefaultPassAction: "captcha",
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Equal(t, appsec.CaptchaRemediation, responses[0].Action)
-				require.Equal(t, http.StatusOK, statusCode) //@tko: body is captcha, but as it's 200, captcha won't be showed to user
-				require.Equal(t, appsec.CaptchaRemediation, appsecResponse.Action)
-				require.Equal(t, http.StatusOK, appsecResponse.HTTPStatus)
-			},
-		},
-		{
-			name:             "DefaultPassHTTPCode: 200",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/",
-				Args:       url.Values{"foo": []string{"tutu"}},
-			},
-			UserPassedHTTPCode: 200,
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Equal(t, appsec.AllowRemediation, responses[0].Action)
-				require.Equal(t, http.StatusOK, statusCode)
-				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
-				require.Equal(t, http.StatusOK, appsecResponse.HTTPStatus)
-			},
-		},
-		{
-			name:             "DefaultPassHTTPCode: 200",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/",
-				Args:       url.Values{"foo": []string{"tutu"}},
-			},
-			UserPassedHTTPCode: 418,
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Equal(t, appsec.AllowRemediation, responses[0].Action)
-				require.Equal(t, http.StatusOK, statusCode)
-				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
-				require.Equal(t, http.StatusTeapot, appsecResponse.HTTPStatus)
-			},
-		},
-	}
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			loadAppSecEngine(test, t)
-		})
-	}
-}
-
-func TestAppsecDefaultRemediation(t *testing.T) {
-
-	tests := []appsecRuleTest{
-		{
-			name:             "Basic matching rule",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Equal(t, appsec.BanRemediation, responses[0].Action)
-				require.Equal(t, http.StatusForbidden, statusCode)
-				require.Equal(t, appsec.BanRemediation, appsecResponse.Action)
-				require.Equal(t, http.StatusForbidden, appsecResponse.HTTPStatus)
-			},
-		},
-		{
-			name:             "default remediation to ban (default)",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule42",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			DefaultRemediation: "ban",
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Equal(t, appsec.BanRemediation, responses[0].Action)
-				require.Equal(t, http.StatusForbidden, statusCode)
-				require.Equal(t, appsec.BanRemediation, appsecResponse.Action)
-				require.Equal(t, http.StatusForbidden, appsecResponse.HTTPStatus)
-			},
-		},
-		{
-			name:             "default remediation to allow",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule42",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			DefaultRemediation: "allow",
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Equal(t, appsec.AllowRemediation, responses[0].Action)
-				require.Equal(t, http.StatusOK, statusCode)
-				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
-				require.Equal(t, http.StatusOK, appsecResponse.HTTPStatus)
-			},
-		},
-		{
-			name:             "default remediation to captcha",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule42",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			DefaultRemediation: "captcha",
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Equal(t, appsec.CaptchaRemediation, responses[0].Action)
-				require.Equal(t, http.StatusForbidden, statusCode)
-				require.Equal(t, appsec.CaptchaRemediation, appsecResponse.Action)
-				require.Equal(t, http.StatusForbidden, appsecResponse.HTTPStatus)
-			},
-		},
-		{
-			name:             "custom user HTTP code",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule42",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			UserBlockedHTTPCode: 418,
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Equal(t, appsec.BanRemediation, responses[0].Action)
-				require.Equal(t, http.StatusForbidden, statusCode)
-				require.Equal(t, appsec.BanRemediation, appsecResponse.Action)
-				require.Equal(t, http.StatusTeapot, appsecResponse.HTTPStatus)
-			},
-		},
-		{
-			name:             "custom remediation + HTTP code",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule42",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			UserBlockedHTTPCode: 418,
-			DefaultRemediation:  "foobar",
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Equal(t, "foobar", responses[0].Action)
-				require.Equal(t, http.StatusForbidden, statusCode)
-				require.Equal(t, "foobar", appsecResponse.Action)
-				require.Equal(t, http.StatusTeapot, appsecResponse.HTTPStatus)
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			loadAppSecEngine(test, t)
-		})
-	}
-}
-
-func TestAppsecRuleMatches(t *testing.T) {
-
-	/*
-		[x] basic matching rule
-		[x] basic non-matching rule
-		[ ] test the transformation
-		[ ] ?
-	*/
-	tests := []appsecRuleTest{
-		{
-			name:             "Basic matching rule",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Len(t, events, 2)
-				require.Equal(t, types.APPSEC, events[0].Type)
-
-				require.Equal(t, types.LOG, events[1].Type)
-				require.True(t, events[1].Appsec.HasInBandMatches)
-				require.Len(t, events[1].Appsec.MatchedRules, 1)
-				require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
-
-				require.Len(t, responses, 1)
-				require.True(t, responses[0].InBandInterrupt)
-			},
-		},
-		{
-			name:             "Basic non-matching rule",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule1",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"tutu"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Empty(t, events)
-				require.Len(t, responses, 1)
-				require.False(t, responses[0].InBandInterrupt)
-				require.False(t, responses[0].OutOfBandInterrupt)
-			},
-		},
-		{
-			name:             "default remediation to allow",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule42",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			DefaultRemediation: "allow",
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Equal(t, appsec.AllowRemediation, responses[0].Action)
-				require.Equal(t, http.StatusOK, statusCode)
-				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
-				require.Equal(t, http.StatusOK, appsecResponse.HTTPStatus)
-			},
-		},
-		{
-			name:             "default remediation to captcha",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule42",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			DefaultRemediation: "captcha",
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Equal(t, appsec.CaptchaRemediation, responses[0].Action)
-				require.Equal(t, http.StatusForbidden, statusCode)
-				require.Equal(t, appsec.CaptchaRemediation, appsecResponse.Action)
-				require.Equal(t, http.StatusForbidden, appsecResponse.HTTPStatus)
-			},
-		},
-		{
-			name:             "no default remediation / custom user HTTP code",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule42",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"toto"}},
-			},
-			UserBlockedHTTPCode: 418,
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Equal(t, appsec.BanRemediation, responses[0].Action)
-				require.Equal(t, http.StatusForbidden, statusCode)
-				require.Equal(t, appsec.BanRemediation, appsecResponse.Action)
-				require.Equal(t, http.StatusTeapot, appsecResponse.HTTPStatus)
-			},
-		},
-		{
-			name:             "no match but try to set remediation to captcha with on_match hook",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule42",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			on_match: []appsec.Hook{
-				{Filter: "IsInBand == true", Apply: []string{"SetRemediation('captcha')"}},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"bla"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Empty(t, events)
-				require.Equal(t, http.StatusOK, statusCode)
-				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
-			},
-		},
-		{
-			name:             "no match but try to set user HTTP code with on_match hook",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule42",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			on_match: []appsec.Hook{
-				{Filter: "IsInBand == true", Apply: []string{"SetReturnCode(418)"}},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"bla"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Empty(t, events)
-				require.Equal(t, http.StatusOK, statusCode)
-				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
-			},
-		},
-		{
-			name:             "no match but try to set  remediation with pre_eval hook",
-			expected_load_ok: true,
-			inband_rules: []appsec_rule.CustomRule{
-				{
-					Name:      "rule42",
-					Zones:     []string{"ARGS"},
-					Variables: []string{"foo"},
-					Match:     appsec_rule.Match{Type: "regex", Value: "^toto"},
-					Transform: []string{"lowercase"},
-				},
-			},
-			pre_eval: []appsec.Hook{
-				{Filter: "IsInBand == true", Apply: []string{"SetRemediationByName('rule42', 'captcha')"}},
-			},
-			input_request: appsec.ParsedRequest{
-				RemoteAddr: "1.2.3.4",
-				Method:     "GET",
-				URI:        "/urllll",
-				Args:       url.Values{"foo": []string{"bla"}},
-			},
-			output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
-				require.Empty(t, events)
-				require.Equal(t, http.StatusOK, statusCode)
-				require.Equal(t, appsec.AllowRemediation, appsecResponse.Action)
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			loadAppSecEngine(test, t)
-		})
-	}
-}
-
 func loadAppSecEngine(test appsecRuleTest, t *testing.T) {
 	if testing.Verbose() {
 		log.SetLevel(log.TraceLevel)

+ 46 - 0
pkg/acquisition/modules/appsec/appsec_win_test.go

@@ -0,0 +1,46 @@
+//go:build windows
+// +build windows
+
+package appsecacquisition
+
+import (
+	"testing"
+
+	log "github.com/sirupsen/logrus"
+)
+
+func TestAppsecRuleTransformsWindows(t *testing.T) {
+
+	log.SetLevel(log.TraceLevel)
+	tests := []appsecRuleTest{
+		// {
+		// 	name:             "normalizepath",
+		// 	expected_load_ok: true,
+		// 	inband_rules: []appsec_rule.CustomRule{
+		// 		{
+		// 			Name:      "rule1",
+		// 			Zones:     []string{"ARGS"},
+		// 			Variables: []string{"foo"},
+		// 			Match:     appsec_rule.Match{Type: "equals", Value: "b/c"},
+		// 			Transform: []string{"normalizepath"},
+		// 		},
+		// 	},
+		// 	input_request: appsec.ParsedRequest{
+		// 		RemoteAddr: "1.2.3.4",
+		// 		Method:     "GET",
+		// 		URI:        "/?foo=a/../b/c",
+		// 	},
+		// 	output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) {
+		// 		require.Len(t, events, 2)
+		// 		require.Equal(t, types.APPSEC, events[0].Type)
+		// 		require.Equal(t, types.LOG, events[1].Type)
+		// 		require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"])
+		// 	},
+		// },
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			loadAppSecEngine(test, t)
+		})
+	}
+}

+ 10 - 3
pkg/appsec/appsec_rule/modsecurity.go

@@ -19,7 +19,8 @@ var zonesMap map[string]string = map[string]string{
 	"HEADERS":         "REQUEST_HEADERS",
 	"METHOD":          "REQUEST_METHOD",
 	"PROTOCOL":        "REQUEST_PROTOCOL",
-	"URI":             "REQUEST_URI",
+	"URI":             "REQUEST_FILENAME",
+	"URI_FULL":        "REQUEST_URI",
 	"RAW_BODY":        "REQUEST_BODY",
 	"FILENAMES":       "FILES",
 }
@@ -28,8 +29,14 @@ var transformMap map[string]string = map[string]string{
 	"lowercase": "t:lowercase",
 	"uppercase": "t:uppercase",
 	"b64decode": "t:base64Decode",
-	"hexdecode": "t:hexDecode",
-	"length":    "t:length",
+	//"hexdecode":          "t:hexDecode", -> not supported by coraza
+	"length":             "t:length",
+	"urldecode":          "t:urlDecode",
+	"trim":               "t:trim",
+	"normalize_path":     "t:normalizePath",
+	"normalizepath":      "t:normalizePath",
+	"htmlentitydecode":   "t:htmlEntityDecode",
+	"html_entity_decode": "t:htmlEntityDecode",
 }
 
 var matchMap map[string]string = map[string]string{

+ 3 - 3
pkg/appsec/request.go

@@ -365,11 +365,11 @@ func NewParsedRequestFromRequest(r *http.Request, logger *logrus.Entry) (ParsedR
 		UUID:                 uuid.New().String(),
 		ClientHost:           clientHost,
 		ClientIP:             clientIP,
-		URI:                  parsedURL.Path,
+		URI:                  clientURI,
 		Method:               clientMethod,
-		Host:                 r.Host,
+		Host:                 clientHost,
 		Headers:              r.Header,
-		URL:                  r.URL,
+		URL:                  parsedURL,
 		Proto:                r.Proto,
 		Body:                 body,
 		Args:                 ParseQuery(parsedURL.RawQuery),

+ 6 - 0
pkg/csconfig/config_paths.go

@@ -13,6 +13,7 @@ type ConfigurationPaths struct {
 	HubDir             string `yaml:"hub_dir,omitempty"`
 	PluginDir          string `yaml:"plugin_dir,omitempty"`
 	NotificationDir    string `yaml:"notification_dir,omitempty"`
+	PatternDir         string `yaml:"pattern_dir,omitempty"`
 }
 
 func (c *Config) loadConfigurationPaths() error {
@@ -33,6 +34,10 @@ func (c *Config) loadConfigurationPaths() error {
 		c.ConfigPaths.HubIndexFile = filepath.Clean(c.ConfigPaths.HubDir + "/.index.json")
 	}
 
+	if c.ConfigPaths.PatternDir == "" {
+		c.ConfigPaths.PatternDir = filepath.Join(c.ConfigPaths.ConfigDir, "patterns/")
+	}
+
 	var configPathsCleanup = []*string{
 		&c.ConfigPaths.HubDir,
 		&c.ConfigPaths.HubIndexFile,
@@ -41,6 +46,7 @@ func (c *Config) loadConfigurationPaths() error {
 		&c.ConfigPaths.SimulationFilePath,
 		&c.ConfigPaths.PluginDir,
 		&c.ConfigPaths.NotificationDir,
+		&c.ConfigPaths.PatternDir,
 	}
 	for _, k := range configPathsCleanup {
 		if *k == "" {

+ 14 - 0
pkg/cwhub/cwhub.go

@@ -7,10 +7,24 @@ import (
 	"sort"
 	"strings"
 	"time"
+
+	"github.com/crowdsecurity/go-cs-lib/version"
 )
 
+// hubTransport wraps a Transport to set a custom User-Agent.
+type hubTransport struct {
+	http.RoundTripper
+}
+
+func (t *hubTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+	req.Header.Set("User-Agent", "crowdsec/"+version.String())
+	return t.RoundTripper.RoundTrip(req)
+}
+
+// hubClient is the HTTP client used to communicate with the CrowdSec Hub.
 var hubClient = &http.Client{
 	Timeout: 120 * time.Second,
+	Transport: &hubTransport{http.DefaultTransport},
 }
 
 // safePath returns a joined path and ensures that it does not escape the base directory.

+ 14 - 0
pkg/cwhub/dataset.go

@@ -4,9 +4,11 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"io/fs"
 	"net/http"
 	"os"
 	"path/filepath"
+	"runtime"
 	"time"
 
 	"github.com/sirupsen/logrus"
@@ -65,6 +67,18 @@ func downloadFile(url string, destPath string) error {
 	// TODO: use a better way to communicate this
 	fmt.Printf("updated %s\n", filepath.Base(destPath))
 
+	if runtime.GOOS == "windows" {
+		// On Windows, rename will fail if the destination file already exists
+		// so we remove it first.
+		err = os.Remove(destPath)
+		switch {
+		case errors.Is(err, fs.ErrNotExist):
+			break
+		case err != nil:
+			return err
+		}
+	}
+
 	if err = os.Rename(tmpFileName, destPath); err != nil {
 		return err
 	}

+ 34 - 28
pkg/cwhub/dataset_test.go

@@ -1,50 +1,56 @@
 package cwhub
 
 import (
+	"io"
+	"net/http"
+	"net/http/httptest"
 	"os"
+	"path/filepath"
 	"testing"
 
-	"github.com/jarcoal/httpmock"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+
+	"github.com/crowdsecurity/go-cs-lib/cstest"
 )
 
 func TestDownloadFile(t *testing.T) {
-	examplePath := "./example.txt"
-	defer os.Remove(examplePath)
-
-	httpmock.Activate()
-	defer httpmock.DeactivateAndReset()
-
-	// OK
-	httpmock.RegisterResponder(
-		"GET",
-		"https://example.com/xx",
-		httpmock.NewStringResponder(200, "example content oneoneone"),
-	)
-
-	httpmock.RegisterResponder(
-		"GET",
-		"https://example.com/x",
-		httpmock.NewStringResponder(404, "not found"),
-	)
-
-	err := downloadFile("https://example.com/xx", examplePath)
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		switch r.URL.Path {
+		case "/xx":
+			w.WriteHeader(http.StatusOK)
+			_, _ = io.WriteString(w, "example content oneoneone")
+		default:
+			w.WriteHeader(http.StatusNotFound)
+			_, _ = io.WriteString(w, "not found")
+		}
+	}))
+	defer ts.Close()
+
+	dest := filepath.Join(t.TempDir(), "example.txt")
+	defer os.Remove(dest)
+
+	err := downloadFile(ts.URL+"/xx", dest)
 	require.NoError(t, err)
 
-	content, err := os.ReadFile(examplePath)
+	content, err := os.ReadFile(dest)
 	assert.Equal(t, "example content oneoneone", string(content))
 	require.NoError(t, err)
 
 	// bad uri
-	err = downloadFile("https://zz.com", examplePath)
-	require.Error(t, err)
+	err = downloadFile("https://zz.com", dest)
+	cstest.RequireErrorContains(t, err, "lookup zz.com")
+	cstest.RequireErrorContains(t, err, "no such host")
 
 	// 404
-	err = downloadFile("https://example.com/x", examplePath)
-	require.Error(t, err)
+	err = downloadFile(ts.URL+"/x", dest)
+	cstest.RequireErrorContains(t, err, "bad http code 404")
 
 	// bad target
-	err = downloadFile("https://example.com/xx", "")
-	require.Error(t, err)
+	err = downloadFile(ts.URL+"/xx", "")
+	cstest.RequireErrorContains(t, err, cstest.PathNotFoundMessage)
+
+	// destination directory does not exist
+	err = downloadFile(ts.URL+"/xx", filepath.Join(t.TempDir(), "missing/example.txt"))
+	cstest.RequireErrorContains(t, err, cstest.PathNotFoundMessage)
 }

+ 8 - 8
pkg/models/localapi_swagger.yaml

@@ -26,10 +26,10 @@ produces:
 paths:
   /decisions/stream:
     get:
-      description: Returns a list of new/expired decisions. Intended for bouncers that need to "stream" decisions
+      description: Returns a list of new/expired decisions. Intended for remediation component that need to "stream" decisions
       summary: getDecisionsStream
       tags:
-        - bouncers
+        - Remediation component
       operationId: getDecisionsStream
       deprecated: false
       produces:
@@ -39,7 +39,7 @@ paths:
           in: query
           required: false
           type: boolean
-          description: 'If true, means that the bouncers is starting and a full list must be provided'
+          description: 'If true, means that the remediation component is starting and a full list must be provided'
         - name: scopes
           in: query
           required: false
@@ -73,10 +73,10 @@ paths:
       security:
       - APIKeyAuthorizer: []
     head:
-      description: Returns a list of new/expired decisions. Intended for bouncers that need to "stream" decisions
+      description: Returns a list of new/expired decisions. Intended for remediation component that need to "stream" decisions
       summary: GetDecisionsStream
       tags:
-        - bouncers
+        - Remediation component
       operationId: headDecisionsStream
       deprecated: false
       produces:
@@ -100,7 +100,7 @@ paths:
       description: Returns information about existing decisions
       summary: getDecisions
       tags:
-        - bouncers
+        - Remediation component
       operationId: getDecisions
       deprecated: false
       produces:
@@ -164,7 +164,7 @@ paths:
       description: Returns information about existing decisions
       summary: GetDecisions
       tags:
-        - bouncers
+        - Remediation component
       operationId: headDecisions
       deprecated: false
       produces:
@@ -1008,7 +1008,7 @@ definitions:
     title: "error response"
     description: "error response return by the API"
 tags:
-  - name: bouncers
+  - name: Remediation component
     description: 'Operations about decisions : bans, captcha, rate-limit etc.'
   - name: watchers
     description: 'Operations about watchers : cscli & crowdsec'

+ 1 - 1
pkg/parser/unix_parser.go

@@ -98,7 +98,7 @@ func NewParsers(hub *cwhub.Hub) *Parsers {
 func LoadParsers(cConfig *csconfig.Config, parsers *Parsers) (*Parsers, error) {
 	var err error
 
-	patternsDir := filepath.Join(cConfig.ConfigPaths.ConfigDir, "patterns/")
+	patternsDir := cConfig.ConfigPaths.PatternDir
 	log.Infof("Loading grok library %s", patternsDir)
 	/* load base regexps for two grok parsers */
 	parsers.Ctx, err = Init(map[string]interface{}{"patterns": patternsDir,

+ 2 - 17
test/bin/preload-hub-items

@@ -9,20 +9,12 @@ THIS_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
 
 # pre-download everything but don't install anything
 
-echo -n "Purging existing hub..."
+echo "Pre-downloading Hub content..."
 
 types=$("$CSCLI" hub types -o raw)
 
 for itemtype in $types; do
-    "$CSCLI" "${itemtype}" delete --all --error --purge --force
-done
-
-echo " done."
-
-echo -n "Pre-downloading Hub content..."
-
-for itemtype in $types; do
-    ALL_ITEMS=$("$CSCLI" "$itemtype" list -a -o json | jq --arg itemtype "$itemtype" -r '.[$itemtype][].name')
+    ALL_ITEMS=$("$CSCLI" "$itemtype" list -a -o json | itemtype="$itemtype" yq '.[env(itemtype)][] | .name')
     if [[ -n "${ALL_ITEMS}" ]]; then
         #shellcheck disable=SC2086
         "$CSCLI" "$itemtype" install \
@@ -32,11 +24,4 @@ for itemtype in $types; do
     fi
 done
 
-# XXX: download-only works only for collections, not for parsers, scenarios, postoverflows.
-# so we have to delete the links manually, and leave the downloaded files in place
-
-for itemtype in $types; do
-    "$CSCLI" "$itemtype" delete --all --error
-done
-
 echo " done."