Repository: corazawaf/coraza
Pull request #684 (2023-03-02 16:26:03)

Fix(980170): Correlation rules, SecAction messages, flow rules with DetectionOnly
Debugging `980170` failing tests, I had to dig a bit in a rabbit hole:

1. `SecAction` empty match logic was not carrying the `Msg` field, therefore no Messages from `SecAction` were printed. [Correlations rules](https://github.com/coreruleset/coreruleset/blob/v4.0/dev/rules/RESPONSE-980-CORRELATION.conf) rely on them. Fixed proposed in the PR, maybe to be better addressed later on via https://github.com/corazawaf/coraza/issues/676.

2. `ActionTypeFlow` actions were evaluated just like `ActionTypeDisruptive`. It leads to actions like `skipAfter` not working in `DetectionOnly` mode. I think this is something not intended, also because without this fix `980170-3` started to fail (the report was always printed, not only when the total detection score was greater than zero). Looking at some issues (https://github.com/SpiderLabs/ModSecurity/issues/313, https://github.com/coreruleset/coreruleset/issues/100) I still enforced their execution only for the parent rule (so a chain has to be fully matched before triggering a type flow action).

3. Just like phases 3 and phases 4, also for phase 5 I had to explicitly set `SecDefaultAction "phase:5,log,auditlog,pass"` (Related to https://github.com/corazawaf/coraza/issues/494). Rule `980170` does not have explicit `log`, therefore Coraza does not log its message without setting the default action also for this phase.

4. CRS test configuration (rule 900005) was relying on an [outdated](https://github.com/coreruleset/coreruleset/pull/2417) variable to set the paranoia level. We never found it because `skipAfter` action was not working in Detection mode (see point 1), so all the rules were evaluated (just like the tests requires setting the paranoia level to `4`).

This Fix partially address also https://github.com/corazawaf/coraza/discussions/662, Fixing the correlation rules, A summary about the transaction can be found in the error logs, providing details like the total of inbound anomaly score. E.g.:
```
[client "127.0.0.1"] Coraza: Warning. Anomaly Scores: (Inbound Scores: blocking=28, detection=28, per_pl=15-5-0-8, threshold=5) - (Outbound Scores: blocking=0, detection=0, per_pl=0-0-0-0, threshold=4) - (SQLI=3, XSS=20, RFI=0, LFI=0, RCE [file "crs/rules/RESPONSE-980-CORRELATION.conf"] [line "0"] [id "980170"] [rev ""] [msg "Anomaly Scores: (Inbound Scores: blocking=28, detection=28, per_pl=15-5-0-8, threshold=5) - (Outbound Scores: blocking=0, detection=0, per_pl=0-0-0-0, threshold=4) - (SQLI=3, XSS=20, RFI=0, LFI=0, RCE"] [data ""] [severity "emergency"] [ver "OWASP_CRS/4.0.0-rc1"] [maturity "0"] [accuracy "0"] [tag "reporting"] [hostname ""] [uri "/?a=<script></script>"] [unique_id "FdIkuoChhsUtRUHspaI"]
```

codecov[bot]: # [Codecov](https://codecov.io/gh/corazawaf/coraza/pull/684?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf) Report
Patch coverage: **`100.00`**% and project coverage change: **`+0.10`** :tada:
> Comparison is base [(`fb4a3eb`)](https://codecov.io/gh/corazawaf/coraza/commit/fb4a3eb185a6d751f86834d4a4e5801224fdeeb5?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf) 80.50% compared to head [(`a740d4d`)](https://codecov.io/gh/corazawaf/coraza/pull/684?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf) 80.60%.

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


```diff
@@            Coverage Diff             @@
##           v3/dev     #684      +/-   ##
==========================================
+ Coverage   80.50%   80.60%   +0.10%     
==========================================
  Files         145      145              
  Lines        7771     7782      +11     
==========================================
+ Hits         6256     6273      +17     
+ Misses       1284     1280       -4     
+ Partials      231      229       -2     
```

| Flag | Coverage Δ | |
|---|---|---|
| default | `76.50% <100.00%> (-0.01%)` | :arrow_down: |
| examples | `27.03% <23.07%> (-0.02%)` | :arrow_down: |
| ftw | `51.65% <61.53%> (+0.23%)` | :arrow_up: |
| ftw-multiphase | `51.75% <61.53%> (+0.23%)` | :arrow_up: |
| tinygo | `75.56% <100.00%> (+0.06%)` | :arrow_up: |

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

| [Impacted Files](https://codecov.io/gh/corazawaf/coraza/pull/684?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf) | Coverage Δ | |
|---|---|---|
| [internal/corazawaf/transaction.go](https://codecov.io/gh/corazawaf/coraza/pull/684?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf#diff-aW50ZXJuYWwvY29yYXphd2FmL3RyYW5zYWN0aW9uLmdv) | `78.59% <ø> (ø)` | |
| [internal/corazawaf/rule.go](https://codecov.io/gh/corazawaf/coraza/pull/684?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf#diff-aW50ZXJuYWwvY29yYXphd2FmL3J1bGUuZ28=) | `77.23% <100.00%> (+0.57%)` | :arrow_up: |
| [internal/corazarules/rule\_match.go](https://codecov.io/gh/corazawaf/coraza/pull/684?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf#diff-aW50ZXJuYWwvY29yYXphcnVsZXMvcnVsZV9tYXRjaC5nbw==) | `53.39% <0.00%> (+5.82%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/corazawaf/coraza/pull/684?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf).

M4tteoP: Adding the logic that enforces that only one disruptive action I added `appendRuleAction` to avoid code redundancy.
Performance and allocations seem not to be impacted:
Before:
```
BenchmarkParseActions-10    	 1140902	      1012 ns/op	    1072 B/op	      20 allocs/op
```
After:
```
BenchmarkParseActions-10    	 1157869	      1019 ns/op	    1072 B/op	      20 allocs/op
```

Patch:
diff --git a/internal/corazawaf/rule.go b/internal/corazawaf/rule.go
index 3596991..ffff362 100644
--- a/internal/corazawaf/rule.go
+++ b/internal/corazawaf/rule.go
@@ -192,6 +192,12 @@ func (r *Rule) doEvaluate(phase types.RulePhase, tx *Transaction, cache map[tran
 	if r.operator == nil {
 		tx.DebugLogger().Debug().Int("rule_id", rid).Msg("Forcing rule to match")
 		md := &corazarules.MatchData{}
+		if r.Msg != nil {
+			md.Message_ = r.Msg.Expand(tx)
+		}
+		if r.LogData != nil {
+			md.Data_ = r.LogData.Expand(tx)
+		}
 		matchedValues = append(matchedValues, md)
 		r.matchVariable(tx, md)
 	} else {
@@ -273,7 +279,7 @@ func (r *Rule) doEvaluate(phase types.RulePhase, tx *Transaction, cache map[tran
 		return matchedValues
 	}
 
-	// disruptive actions are only evaluated by parent rules
+	// disruptive actions and rules affecting the rule flow are only evaluated by parent rules
 	if r.ParentID_ == 0 {
 		// we only run the chains for the parent rule
 		for nr := r.Chain; nr != nil; {
@@ -285,16 +291,17 @@ func (r *Rule) doEvaluate(phase types.RulePhase, tx *Transaction, cache map[tran
 			matchedValues = append(matchedValues, matchedChainValues...)
 			nr = nr.Chain
 		}
-		// we need to add disruptive actions in the end, otherwise they would be triggered without their chains.
-		if tx.RuleEngine != types.RuleEngineDetectionOnly {
-			tx.DebugLogger().Debug().Int("rule_id", rid).Msg("Disrupting transaction by rule")
-			for _, a := range r.actions {
-				if a.Function.Type() == rules.ActionTypeDisruptive || a.Function.Type() == rules.ActionTypeFlow {
-					tx.DebugLogger().Debug().Int("rule_id", rid).Str("action", a.Name).Msg("Evaluating action for rule")
-					a.Function.Evaluate(r, tx)
-				}
-			}
 
+		for _, a := range r.actions {
+			if a.Function.Type() == rules.ActionTypeFlow {
+				// Flow actions are evaluated also if the rule engine is set to DetectionOnly
+				tx.DebugLogger().Debug().Int("rule_id", rid).Str("action", a.Name).Msg("Evaluating flow action for rule")
+				a.Function.Evaluate(r, tx)
+			} else if a.Function.Type() == rules.ActionTypeDisruptive && tx.RuleEngine == types.RuleEngineOn {
+				// The parser enforces that the disruptive action is just one per rule (if more than one, only the last one is kept)
+				tx.DebugLogger().Debug().Int("rule_id", rid).Str("action", a.Name).Msg("Executing disruptive action for rule")
+				a.Function.Evaluate(r, tx)
+			}
 		}
 		if r.ID_ != 0 {
 			// we avoid matching chains and secmarkers
diff --git a/internal/corazawaf/transaction.go b/internal/corazawaf/transaction.go
index 95b4caa..3037cfe 100644
--- a/internal/corazawaf/transaction.go
+++ b/internal/corazawaf/transaction.go
@@ -658,10 +658,9 @@ func (tx *Transaction) AddResponseArgument(key string, value string) {
 // This method should be called at very beginning of a request process, it is
 // expected to be executed prior to the virtual host resolution, when the
 // connection arrives on the server.
-// note: There is no direct connection between this function and any phase of
-//
-//	the SecLanguages phases. It is something that may occur between the
-//	SecLanguage phase 1 and 2.
+// note: There is no direct connection between this function and any phase of the
+// SecLanguages phases. It is something that may occur between the SecLanguage
+// phase 1 and 2.
 //
 // note: This function won't add GET arguments, they must be added with AddArgument
 func (tx *Transaction) ProcessURI(uri string, method string, httpVersion string) {
diff --git a/internal/seclang/rule_parser.go b/internal/seclang/rule_parser.go
index e387514..ac0e84f 100644
--- a/internal/seclang/rule_parser.go
+++ b/internal/seclang/rule_parser.go
@@ -401,6 +401,8 @@ func getLastRuleExpectingChain(w *corazawaf.WAF) *corazawaf.Rule {
 	return nil
 }
 
+const unset = -1
+
 // parseActions will assign the function name, arguments and
 // function (pkg.actions) for each action split by comma (,)
 // Action arguments are allowed to wrap values between colons(”)
@@ -413,6 +415,8 @@ func parseActions(actions string) ([]ruleAction, error) {
 
 	quoted := false
 	var res []ruleAction
+	var err error
+	disruptiveActionIndex := unset
 actionLoop:
 	for i, c := range actions {
 		switch {
@@ -420,16 +424,10 @@ actionLoop:
 			// skip whitespaces in key
 			continue actionLoop
 		case !quoted && c == ',':
-			f, err := actionsmod.Get(ckey.String())
+			res, disruptiveActionIndex, err = appendRuleAction(res, &ckey, &cval, disruptiveActionIndex)
 			if err != nil {
 				return nil, err
 			}
-			res = append(res, ruleAction{
-				Key:   ckey.String(),
-				Value: cval.String(),
-				F:     f,
-				Atype: f.Type(),
-			})
 			ckey.Reset()
 			cval.Reset()
 			iskey = true
@@ -452,21 +450,45 @@ actionLoop:
 			ckey.WriteRune(c)
 		}
 		if i+1 == len(actions) {
-			f, err := actionsmod.Get(ckey.String())
+			// last action, returned disruptiveActionIndex is not needed
+			res, _, err = appendRuleAction(res, &ckey, &cval, disruptiveActionIndex)
 			if err != nil {
 				return nil, err
 			}
-			res = append(res, ruleAction{
-				Key:   ckey.String(),
-				Value: cval.String(),
-				F:     f,
-				Atype: f.Type(),
-			})
 		}
 	}
 	return res, nil
 }
 
+func appendRuleAction(res []ruleAction, ckey *strings.Builder, cval *strings.Builder, disruptiveActionIndex int) ([]ruleAction, int, error) {
+	f, err := actionsmod.Get(ckey.String())
+	if err != nil {
+		return res, unset, err
+	}
+	if f.Type() == rules.ActionTypeDisruptive && disruptiveActionIndex != unset {
+		// There can only be one disruptive action per rule (if there are multiple disruptive
+		// actions present, or inherited, only the last one will take effect).
+		// Therefore, if we encounter another disruptive action, we replace the previous one.
+		res[disruptiveActionIndex] = ruleAction{
+			Key:   ckey.String(),
+			Value: cval.String(),
+			F:     f,
+			Atype: f.Type(),
+		}
+	} else {
+		if f.Type() == rules.ActionTypeDisruptive {
+			disruptiveActionIndex = len(res)
+		}
+		res = append(res, ruleAction{
+			Key:   ckey.String(),
+			Value: cval.String(),
+			F:     f,
+			Atype: f.Type(),
+		})
+	}
+	return res, disruptiveActionIndex, nil
+}
+
 /*
 So here is my research:
 SecDefaultAction must contain a phase and a disruptive action
diff --git a/types/transaction.go b/types/transaction.go
index 4b2a723..dfbb3c6 100644
--- a/types/transaction.go
+++ b/types/transaction.go
@@ -26,10 +26,9 @@ type Transaction interface {
 	// This method should be called at very beginning of a request process, it is
 	// expected to be executed prior to the virtual host resolution, when the
 	// connection arrives on the server.
-	// note: There is no direct connection between this function and any phase of
-	//
-	//	the SecLanguages phases. It is something that may occur between the
-	//	SecLanguage phase 1 and 2.
+	// note: There is no direct connection between this function and any phase of the
+	// SecLanguages phases. It is something that may occur between the SecLanguage
+	// phase 1 and 2.
 	//
 	// note: This function won't add GET arguments, they must be added with AddArgument
 	ProcessURI(uri string, method string, httpVersion string)


Test patch:
diff --git a/internal/corazawaf/rule_test.go b/internal/corazawaf/rule_test.go
index 1cabed4..2a77fab 100644
--- a/internal/corazawaf/rule_test.go
+++ b/internal/corazawaf/rule_test.go
@@ -6,9 +6,33 @@ package corazawaf
 import (
 	"testing"
 
+	"github.com/corazawaf/coraza/v3/macro"
+	"github.com/corazawaf/coraza/v3/types"
 	"github.com/corazawaf/coraza/v3/types/variables"
 )
 
+func TestSecActionMessagePropagationInMatchData(t *testing.T) {
+	r := NewRule()
+	r.Msg, _ = macro.NewMacro("Message")
+	r.LogData, _ = macro.NewMacro("Data Message")
+	r.ID_ = 1
+	// SecAction uses nil operator
+	r.operator = nil
+	tx := NewWAF().NewTransaction()
+	transformationCache := tx.transformationCache
+	matchdata := r.doEvaluate(types.PhaseLogging, tx, transformationCache)
+	if len(matchdata) != 1 {
+		t.Errorf("expected 1 matchdata from a SecActions rule, got %d", len(matchdata))
+	}
+
+	if want, have := r.Msg.String(), matchdata[0].Message(); want != have {
+		t.Fatalf("unexpected Message: want %q, have %q", want, have)
+	}
+	if want, have := r.LogData.String(), matchdata[0].Data(); want != have {
+		t.Fatalf("unexpected LogData: want %q, have %q", want, have)
+	}
+}
+
 func TestRuleNegativeVariables(t *testing.T) {
 	rule := NewRule()
 	if err := rule.AddVariable(variables.Args, "", false); err != nil {
diff --git a/internal/seclang/rule_parser_test.go b/internal/seclang/rule_parser_test.go
index 39eef63..fcba3ee 100644
--- a/internal/seclang/rule_parser_test.go
+++ b/internal/seclang/rule_parser_test.go
@@ -146,3 +146,9 @@ func TestDefaultActionsForPhase2(t *testing.T) {
 		t.Error("phase 1 rules shouldn't have log set by default actions")
 	}
 }
+func BenchmarkParseActions(b *testing.B) {
+	actionsToBeParsed := "id:980170,phase:5,pass,t:none,noauditlog,msg:'Anomaly Scores:Inbound Scores - Outbound Scores',tag:test"
+	for i := 0; i < b.N; i++ {
+		_, _ = parseActions(actionsToBeParsed)
+	}
+}
diff --git a/internal/seclang/rules_test.go b/internal/seclang/rules_test.go
index 2f2648d..898c7e5 100644
--- a/internal/seclang/rules_test.go
+++ b/internal/seclang/rules_test.go
@@ -31,7 +31,7 @@ func TestRuleMatch(t *testing.T) {
 		t.Errorf("failed to match rules with %d", len(tx.MatchedRules()))
 	}
 	if tx.Interruption() == nil {
-		t.Error("failed to interrupt transaction")
+		t.Fatal("failed to interrupt transaction")
 	}
 
 	if tx.Interruption().RuleID != 1 {
@@ -98,6 +98,31 @@ func TestSecMarkers(t *testing.T) {
 	}
 }
 
+// There can only be one disruptive action per rule (if there are multiple disruptive
+// actions present, or inherited, only the last one will take effect).
+// The parser enforces it, keeping only one disruptive action per rule.
+func TestOnlyLastDisruptiveActionEnforced(t *testing.T) {
+	waf := corazawaf.NewWAF()
+	parser := NewParser(waf)
+	// Both deny and allow are disruptive actions, so only allow should be enforced
+	err := parser.FromString(`
+		SecRuleEngine On
+		SecDefaultAction "phase:1,deny,status:403,log"
+		SecRule REQUEST_URI "@unconditionalMatch" "id:1, phase:1,deny,allow,log,auditlog"
+	`)
+	if err != nil {
+		t.Error(err.Error())
+	}
+	tx := waf.NewTransaction()
+	tx.ProcessRequestHeaders()
+	if len(tx.MatchedRules()) != 1 {
+		t.Errorf("failed to match rules with %d", len(tx.MatchedRules()))
+	}
+	if tx.Interruption() != nil {
+		t.Fatal("unexpected interruption, deny action has been enforced instead of allow")
+	}
+}
+
 func TestSecAuditLogs(t *testing.T) {
 	waf := corazawaf.NewWAF()
 	parser := NewParser(waf)
diff --git a/testing/coreruleset/.ftw.yml b/testing/coreruleset/.ftw.yml
index ffd26b4..350bc05 100644
--- a/testing/coreruleset/.ftw.yml
+++ b/testing/coreruleset/.ftw.yml
@@ -24,5 +24,3 @@ testoverride:
     941310-3: ''
     942522-7: ''
     944200-1: ''
-    980170-1: ''
-    980170-2: ''
diff --git a/testing/coreruleset/coreruleset_test.go b/testing/coreruleset/coreruleset_test.go
index 88c9225..c63439f 100644
--- a/testing/coreruleset/coreruleset_test.go
+++ b/testing/coreruleset/coreruleset_test.go
@@ -158,14 +158,16 @@ func TestFTW(t *testing.T) {
 SecResponseBodyMimeType text/plain
 SecDefaultAction "phase:3,log,auditlog,pass"
 SecDefaultAction "phase:4,log,auditlog,pass"
+SecDefaultAction "phase:5,log,auditlog,pass"
 
+# Rule 900005 from https://github.com/coreruleset/coreruleset/blob/v4.0/dev/tests/regression/README.md#requirements
 SecAction "id:900005,\
   phase:1,\
   nolog,\
   pass,\
   ctl:ruleEngine=DetectionOnly,\
   ctl:ruleRemoveById=910000,\
-  setvar:tx.paranoia_level=4,\
+  setvar:tx.blocking_paranoia_level=4,\
   setvar:tx.crs_validate_utf8_encoding=1,\
   setvar:tx.arg_name_length=100,\
   setvar:tx.arg_length=400,\
@@ -307,13 +309,14 @@ SecResponseBodyMimeType text/plain
 SecDefaultAction "phase:3,log,auditlog,pass"
 SecDefaultAction "phase:4,log,auditlog,pass"
 
+# Rule 900005 from https://github.com/coreruleset/coreruleset/blob/v4.0/dev/tests/regression/README.md#requirements
 SecAction "id:900005,\
   phase:1,\
   nolog,\
   pass,\
   ctl:ruleEngine=DetectionOnly,\
   ctl:ruleRemoveById=910000,\
-  setvar:tx.paranoia_level=4,\
+  setvar:tx.blocking_paranoia_level=4,\
   setvar:tx.crs_validate_utf8_encoding=1,\
   setvar:tx.arg_name_length=100,\
   setvar:tx.arg_length=400,\
diff --git a/testing/engine/flow_actions.go b/testing/engine/flow_actions.go
new file mode 100644
index 0000000..be79f35
--- /dev/null
+++ b/testing/engine/flow_actions.go
@@ -0,0 +1,46 @@
+// Copyright 2022 Juan Pablo Tosso and the OWASP Coraza contributors
+// SPDX-License-Identifier: Apache-2.0
+
+package engine
+
+import (
+	"github.com/corazawaf/coraza/v3/testing/profile"
+)
+
+var _ = profile.RegisterProfile(profile.Profile{
+	Meta: profile.Meta{
+		Author:      "M4tteoP",
+		Description: "Test if flow actions are working with both the Engine On and DetectionOnly mode, only by parent rules",
+		Enabled:     true,
+		Name:        "flow_actions.yaml",
+	},
+	Tests: []profile.Test{
+		{
+			Title: "flaw_actions",
+			Stages: []profile.Stage{
+				{
+					Stage: profile.SubStage{
+						Input: profile.StageInput{
+							URI: "/",
+						},
+						Output: profile.ExpectedOutput{
+							TriggeredRules:    []int{1, 4},
+							NonTriggeredRules: []int{2, 3},
+						},
+					},
+				},
+			},
+		},
+	},
+	Rules: `
+	SecRuleEngine DetectionOnly
+	SecRule REQUEST_URI "@unconditionalMatch" "id:1,phase:1,pass,nolog,skipAfter:END-REPORTING"
+	SecAction "id:2,phase:1,deny,status:403,log,msg:'Should not be triggered, being skipped by skipAfter of rule 1'"
+	SecMarker "END-REPORTING"
+
+	SecRule REQUEST_URI "@unconditionalMatch" "id:3,phase:2,pass,nolog,skipAfter:END,chain"
+	SecRule REQUEST_URI "@rx /UrlThatWillNotMatch" "id:3,phase:2,pass,log"
+	SecAction "id:4,phase:2,deny,status:403,log,msg:'Should match because rule 3 should not be triggered, not being the whole chain matched'"
+	SecMarker "END"
+`,
+})
diff --git a/testing/engine/macroexpansion.go b/testing/engine/macroexpansion.go
index 1129f2f..33ef3fc 100644
--- a/testing/engine/macroexpansion.go
+++ b/testing/engine/macroexpansion.go
@@ -30,14 +30,14 @@ var _ = profile.RegisterProfile(profile.Profile{
 		},
 	},
 	Rules: `
-SecAction "id:1,pass,setvar:'tx.inbound_anomaly_score_threshold=5',setvar:'tx.paranoia_level=1'"
+SecAction "id:1,pass,setvar:'tx.inbound_anomaly_score_threshold=5',setvar:'tx.blocking_paranoia_level=1'"
 
 SecRule TX:inbound_anomaly_score_threshold "@eq 5" "id:100,log,pass"
 
 SecRule TX:inbound_anomaly_score_threshold "@eq %{tx.inbound_anomaly_ScorE_threshold}" "id:150,log,pass"
 
-SecRule TX:paranoia_level "@eq 1" "id:200,log,pass"
+SecRule TX:blocking_paranoia_level "@eq 1" "id:200,log,pass"
 
-SecRule TX:executing_paranoia_level "@lt %{tx.paranoia_level}" "id:901500,phase:1,log"
+SecRule TX:executing_paranoia_level "@lt %{tx.blocking_paranoia_level}" "id:901500,phase:1,log"
 `,
 })