Repository: corazawaf/coraza
Pull request #572 (2023-01-13 12:34:08)

Populates serverName variable implementing SetServerName
Currently `tx.variables.serverName` is never populated, therefore `SERVER_NAME` is always empty and any related rule will not match. See https://github.com/corazawaf/coraza-caddy/issues/35.

I proposed two possible fixes in https://github.com/corazawaf/coraza/issues/515#issuecomment-1346547802. This PR implements the first one mimicking [ModSec behaviour](https://github.com/SpiderLabs/ModSecurity/blob/12e6e325d1e6367f61dbaecc314919b99eed67fa/src/transaction.cc#L702-L705). During `AddRequestHeaders` the host header is read and serverName is populated with its value (stripping off the optional port).

I might have liked delegating it to the connectors adding a field to `ProcessConnection`, but it is stated that ProcessConnection `is expected to be executed prior to the virtual host resolution`. This PR would avoid breaking changes and sticks with this statement.

jptosso: Maybe we should provide an additional helper, most proxy apis, like standard http and caddy, won't provide the host header, among others. Connectors will have to programmatically access the request object and retrieve the host.
codecov-commenter: # [Codecov](https://codecov.io/gh/corazawaf/coraza/pull/572?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf) Report
Base: **78.79**% // Head: **78.67**% // Decreases project coverage by **`-0.12%`** :warning:
> Coverage data is based on head [(`4dd347b`)](https://codecov.io/gh/corazawaf/coraza/pull/572?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf) compared to base [(`af29c78`)](https://codecov.io/gh/corazawaf/coraza/commit/af29c78c47c5a740e45f4132c0e308170b32c9f7?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf).
> Patch coverage: 100.00% of modified lines in pull request are covered.

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


```diff
@@            Coverage Diff             @@
##           v3/dev     #572      +/-   ##
==========================================
- Coverage   78.79%   78.67%   -0.13%     
==========================================
  Files         145      145              
  Lines        7009     7028      +19     
==========================================
+ Hits         5523     5529       +6     
- Misses       1255     1264       +9     
- Partials      231      235       +4     
```

| Flag | Coverage Δ | |
|---|---|---|
| default | `74.08% <100.00%> (-0.12%)` | :arrow_down: |
| examples | `27.09% <38.46%> (-0.11%)` | :arrow_down: |
| ftw | `56.34% <100.00%> (-0.19%)` | :arrow_down: |
| tinygo | `72.12% <100.00%> (-0.12%)` | :arrow_down: |

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/572?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/572?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf#diff-aW50ZXJuYWwvY29yYXphd2FmL3RyYW5zYWN0aW9uLmdv) | `79.00% <100.00%> (-1.11%)` | :arrow_down: |
| [waf.go](https://codecov.io/gh/corazawaf/coraza/pull/572?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf#diff-d2FmLmdv) | `54.54% <0.00%> (-1.20%)` | :arrow_down: |
| [actions/ctl.go](https://codecov.io/gh/corazawaf/coraza/pull/572?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf#diff-YWN0aW9ucy9jdGwuZ28=) | `71.72% <0.00%> (-0.44%)` | :arrow_down: |
| [internal/seclang/parser.go](https://codecov.io/gh/corazawaf/coraza/pull/572?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf#diff-aW50ZXJuYWwvc2VjbGFuZy9wYXJzZXIuZ28=) | `92.37% <0.00%> (-0.26%)` | :arrow_down: |
| [internal/seclang/rule\_parser.go](https://codecov.io/gh/corazawaf/coraza/pull/572?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf#diff-aW50ZXJuYWwvc2VjbGFuZy9ydWxlX3BhcnNlci5nbw==) | `83.61% <0.00%> (-0.14%)` | :arrow_down: |
| [internal/seclang/directives.go](https://codecov.io/gh/corazawaf/coraza/pull/572?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf#diff-aW50ZXJuYWwvc2VjbGFuZy9kaXJlY3RpdmVzLmdv) | `67.88% <0.00%> (-0.10%)` | :arrow_down: |
| [actions/setenv.go](https://codecov.io/gh/corazawaf/coraza/pull/572?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf#diff-YWN0aW9ucy9zZXRlbnYuZ28=) | `60.86% <0.00%> (ø)` | |
| [macro/macro.go](https://codecov.io/gh/corazawaf/coraza/pull/572?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf#diff-bWFjcm8vbWFjcm8uZ28=) | `86.36% <0.00%> (+2.32%)` | :arrow_up: |
| [config.go](https://codecov.io/gh/corazawaf/coraza/pull/572?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corazawaf#diff-Y29uZmlnLmdv) | `58.00% <0.00%> (+7.00%)` | :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/572?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: Following the decision taken during the monthly meeting, this PR with the latest commit implements `ProcessServerName`. The retrieval of the server name is delegated to the connectors, which will rely on this new transaction method to process it. 
jcchavezs: How about also adding the reference implementation in http middleware?
M4tteoP: Any better ideas for go-http apart from retrieving it from `Request.Host`?
jptosso: > Any better ideas for go-http apart from retrieving it from `Request.Host`?

Just for consideration: https://github.com/golang/go/issues/28168 

Patch:
diff --git a/http/middleware.go b/http/middleware.go
index 16345ae..7b53051 100644
--- a/http/middleware.go
+++ b/http/middleware.go
@@ -44,9 +44,13 @@ func processRequest(tx types.Transaction, req *http.Request) (*types.Interruptio
 			tx.AddRequestHeader(k, v)
 		}
 	}
-	// Host will always be removed from req.Headers(), so we manually add it
+
+	// Host will always be removed from req.Headers() and promoted to the
+	// Request.Host field, so we manually add it
 	if req.Host != "" {
 		tx.AddRequestHeader("Host", req.Host)
+		// This connector relies on the host header (now host field) to populate ServerName
+		tx.SetServerName(req.Host)
 	}
 
 	in = tx.ProcessRequestHeaders()
diff --git a/internal/corazawaf/rule.go b/internal/corazawaf/rule.go
index 31df73f..db9b8ef 100644
--- a/internal/corazawaf/rule.go
+++ b/internal/corazawaf/rule.go
@@ -143,9 +143,6 @@ type Rule struct {
 	// If true, the transformations will be multi matched
 	MultiMatch bool
 
-	// Used for error logging
-	Disruptive bool
-
 	HasChain bool
 
 	// inferredPhases is the inferred phases the rule is relevant for
@@ -164,8 +161,8 @@ func (r *Rule) Status() int {
 // Evaluate will evaluate the current rule for the indicated transaction
 // If the operator matches, actions will be evaluated, and it will return
 // the matched variables, keys and values (MatchData)
-func (r *Rule) Evaluate(phase types.RulePhase, tx rules.TransactionState, cache map[transformationKey]*transformationValue) []types.MatchData {
-	return r.doEvaluate(phase, tx.(*Transaction), cache)
+func (r *Rule) Evaluate(phase types.RulePhase, tx rules.TransactionState, cache map[transformationKey]*transformationValue) {
+	r.doEvaluate(phase, tx.(*Transaction), cache)
 }
 
 func (r *Rule) doEvaluate(phase types.RulePhase, tx *Transaction, cache map[transformationKey]*transformationValue) []types.MatchData {
@@ -279,7 +276,7 @@ func (r *Rule) doEvaluate(phase types.RulePhase, tx *Transaction, cache map[tran
 		// we only run the chains for the parent rule
 		for nr := r.Chain; nr != nil; {
 			tx.WAF.Logger.Debug("[%s] [%d] Evaluating rule chain for %d", tx.id, rid, r.ID_)
-			matchedChainValues := nr.Evaluate(phase, tx, cache)
+			matchedChainValues := nr.doEvaluate(phase, tx, cache)
 			if len(matchedChainValues) == 0 {
 				return matchedChainValues
 			}
diff --git a/internal/corazawaf/transaction.go b/internal/corazawaf/transaction.go
index b0b5ddd..8443ea5 100644
--- a/internal/corazawaf/transaction.go
+++ b/internal/corazawaf/transaction.go
@@ -340,14 +340,15 @@ func (tx *Transaction) AddRequestHeader(key string, value string) {
 	tx.variables.requestHeadersNames.AddUniqueCS(keyl, key, keyl)
 	tx.variables.requestHeaders.AddCS(keyl, key, value)
 
-	if keyl == "content-type" {
+	switch keyl {
+	case "content-type":
 		val := strings.ToLower(value)
 		if val == "application/x-www-form-urlencoded" {
 			tx.variables.reqbodyProcessor.Set("URLENCODED")
 		} else if strings.HasPrefix(val, "multipart/form-data") {
 			tx.variables.reqbodyProcessor.Set("MULTIPART")
 		}
-	} else if keyl == "cookie" {
+	case "cookie":
 		// Cookies use the same syntax as GET params but with semicolon (;) separator
 		values := urlutil.ParseQuery(value, ';')
 		for k, vr := range values {
@@ -631,7 +632,6 @@ func (tx *Transaction) RemoveRuleByID(id int) {
 // ProcessConnection 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.
-// Important: Remember to check for a possible intervention.
 func (tx *Transaction) ProcessConnection(client string, cPort int, server string, sPort int) {
 	p := strconv.Itoa(cPort)
 	p2 := strconv.Itoa(sPort)
@@ -742,6 +742,17 @@ func (tx *Transaction) ProcessURI(uri string, method string, httpVersion string)
 	tx.variables.queryString.Set(query)
 }
 
+// SetServerName allows to set server name details.
+//
+// The API consumer is in charge of retrieving the value (e.g. from the host header).
+// It is expected to be executed before calling ProcessRequestHeaders.
+func (tx *Transaction) SetServerName(serverName string) {
+	if tx.LastPhase >= types.PhaseRequestHeaders {
+		tx.WAF.Logger.Warn("SetServerName has been called after ProcessRequestHeaders")
+	}
+	tx.variables.serverName.Set(serverName)
+}
+
 // ProcessRequestHeaders Performs the analysis on the request readers.
 //
 // This method perform the analysis on the request headers, notice however
@@ -771,7 +782,7 @@ func (tx *Transaction) ProcessRequestHeaders() *types.Interruption {
 func setAndReturnBodyLimitInterruption(tx *Transaction) (*types.Interruption, int, error) {
 	tx.DebugLogger().Warn("Disrupting transaction with body size above the configured limit (Action Reject)")
 	tx.interruption = &types.Interruption{
-		Status: 403,
+		Status: 413,
 		Action: "deny",
 	}
 	return tx.interruption, 0, nil
diff --git a/internal/seclang/directives.go b/internal/seclang/directives.go
index ad8fb18..39122f8 100644
--- a/internal/seclang/directives.go
+++ b/internal/seclang/directives.go
@@ -54,8 +54,8 @@ func directiveSecComponentSignature(options *DirectiveOptions) error {
 	return nil
 }
 
-// Description: Adds a fixed rule marker that can be used as a target in a skipAfter action.
-// A SecMarker directive essentially creates a rule that does nothing and whose only purpose
+// Description: Adds a fixed rule marker that can be used as a target in a `skipAfter` action.
+// A `SecMarker` directive essentially creates a rule that does nothing and whose only purpose
 // is to carry the given ID.
 // Syntax: SecMarker [ID|TEXT]
 // ---
@@ -63,27 +63,28 @@ func directiveSecComponentSignature(options *DirectiveOptions) error {
 // allow you to choose the best way to implement a skip-over. Here is an example used from the
 // Core Rule Set:
 //
-// ```
-// SecMarker BEGIN_HOST_CHECK
+// ```apache
 //
-//	SecRule &REQUEST_HEADERS:Host "@eq 0" \
-//		   "id:'960008',skipAfter:END_HOST_CHECK,phase:2,rev:'2.1.1',\
-//		   t:none,block,msg:'Request Missing a Host Header',\
-//		   tag:'PROTOCOL_VIOLATION/MISSING_HEADER_HOST',tag:'WASCTC/WASC-21',\
-//		   tag:'OWASP_TOP_10/A7',tag:'PCI/6.5.10',\
-//		   severity:'5',setvar:'tx.msg=%{rule.msg}',setvar:tx.anomaly_score=+%{tx.notice_anomaly_score},\
-//		   setvar:tx.protocol_violation_score=+%{tx.notice_anomaly_score},\
-//		   setvar:tx.%{rule.id}-PROTOCOL_VIOLATION/MISSING_HEADER-%{matched_var_name}=%{matched_var}"
+//	SecMarker BEGIN_HOST_CHECK
 //
+//	SecRule &REQUEST_HEADERS:Host "@eq 0" \
+//		"id:'960008',skipAfter:END_HOST_CHECK,phase:2,rev:'2.1.1',\
+//		t:none,block,msg:'Request Missing a Host Header',\
+//		tag:'PROTOCOL_VIOLATION/MISSING_HEADER_HOST',tag:'WASCTC/WASC-21',\
+//		tag:'OWASP_TOP_10/A7',tag:'PCI/6.5.10',\
+//		severity:'5',setvar:'tx.msg=%{rule.msg}',setvar:tx.anomaly_score=+%{tx.notice_anomaly_score},\
+//		setvar:tx.protocol_violation_score=+%{tx.notice_anomaly_score},\
+//		setvar:tx.%{rule.id}-PROTOCOL_VIOLATION/MISSING_HEADER-%{matched_var_name}=%{matched_var}"
 //	SecRule REQUEST_HEADERS:Host "^$" \
-//		   "id:'960008',phase:2,rev:'2.1.1',t:none,block,msg:'Request Missing a Host Header',\
-//		   tag:'PROTOCOL_VIOLATION/MISSING_HEADER_HOST',tag:'WASCTC/WASC-21',\
-//		   tag:'OWASP_TOP_10/A7',tag:'PCI/6.5.10',severity:'5',\
-//		   setvar:'tx.msg=%{rule.msg}',setvar:tx.anomaly_score=+%{tx.notice_anomaly_score},\
-//		   setvar:tx.protocol_violation_score=+%{tx.notice_anomaly_score},\
-//		   setvar:tx.%{rule.id}-PROTOCOL_VIOLATION/MISSING_HEADER-%{matched_var_name}=%{matched_var}"
+//		"id:'960008',phase:2,rev:'2.1.1',t:none,block,msg:'Request Missing a Host Header',\
+//		tag:'PROTOCOL_VIOLATION/MISSING_HEADER_HOST',tag:'WASCTC/WASC-21',\
+//		tag:'OWASP_TOP_10/A7',tag:'PCI/6.5.10',severity:'5',\
+//		setvar:'tx.msg=%{rule.msg}',setvar:tx.anomaly_score=+%{tx.notice_anomaly_score},\
+//		setvar:tx.protocol_violation_score=+%{tx.notice_anomaly_score},\
+//		setvar:tx.%{rule.id}-PROTOCOL_VIOLATION/MISSING_HEADER-%{matched_var_name}=%{matched_var}"
+//
+//	SecMarker END_HOST_CHECK
 //
-// SecMarker END_HOST_CHECK
 // ```
 func directiveSecMarker(options *DirectiveOptions) error {
 	rule := corazawaf.NewRule()
@@ -104,7 +105,7 @@ func directiveSecMarker(options *DirectiveOptions) error {
 // Syntax: SecAction "action1,action2,action3,..."
 // ----
 // This directive is commonly used to set variables and initialize persistent collections using the
-// initcol action. The syntax of the parameter is identical to that of the third parameter of SecRule.
+// `initcol` action. The syntax of the parameter is identical to that of the third parameter of `SecRule`.
 //
 // Example:
 // ```apache
@@ -134,10 +135,10 @@ func directiveSecAction(options *DirectiveOptions) error {
 // ---
 // Every rule must provide one or more variables along with the operator that should
 // be used to inspect them. If no actions are provided, the default list will be used.
-// (There is always a default list, even if one was not explicitly set with SecDefaultAction.)
+// (There is always a default list, even if one was not explicitly set with `SecDefaultAction`.)
 // If there are actions specified in a rule, they will be merged with the default list
 // to form the final actions that will be used. (The actions in the rule will overwrite
-// those in the default list.) Refer to SecDefaultAction for more information.
+// those in the default list.) Refer to `SecDefaultAction` for more information.
 //
 // Example:
 // ```apache
@@ -175,7 +176,7 @@ func directiveSecRule(options *DirectiveOptions) error {
 // This directive is required if you plan to inspect HTML responses and implement
 // response blocking. Possible values are:
 // - On: buffer response bodies (but only if the response MIME type matches the list
-// configured with SecResponseBodyMimeType).
+// configured with `SecResponseBodyMimeType`).
 // - Off: do not buffer response bodies.
 func directiveSecResponseBodyAccess(options *DirectiveOptions) error {
 	b, err := parseBoolean(strings.ToLower(options.Opts))
@@ -281,7 +282,7 @@ func directiveSecResponseBodyMimeType(options *DirectiveOptions) error {
 }
 
 // Description: Controls what happens once a response body limit, configured with
-// SecResponseBodyLimit, is encountered.
+// `SecResponseBodyLimit`, is encountered.
 // Syntax: SecResponseBodyLimitAction Reject|ProcessPartial
 // ---
 // By default, Coraza will reject a response body that is longer than specified.
@@ -345,7 +346,7 @@ func directiveSecRequestBodyLimitAction(options *DirectiveOptions) error {
 // Default: 131072 (128 KB)
 // Syntax: SecRequestBodyInMemoryLimit [LIMIT_IN_BYTES]
 // ---
-// When a multipart/form-data request is being processed, once the in-memory limit is reached,
+// When a `multipart/form-data` request is being processed, once the in-memory limit is reached,
 // the request body will start to be streamed into a temporary file on disk.
 func directiveSecRequestBodyInMemoryLimit(options *DirectiveOptions) error {
 	var err error
@@ -416,7 +417,7 @@ func directiveSecHashEngine(options *DirectiveOptions) error {
 // Default: phase:2,log,auditlog,pass
 // Syntax: SecDefaultAction "phase:2,log,auditlog,deny,status:403,tag:'SLA 24/7'"
 // ---
-// Every rule following a previous SecDefaultAction directive in the same configuration
+// Every rule following a previous `SecDefaultAction` directive in the same configuration
 // context will inherit its settings unless more specific actions are used.
 //
 // Rulesets like OWASP Core Ruleset uses this to define operation modes:
@@ -426,7 +427,8 @@ func directiveSecHashEngine(options *DirectiveOptions) error {
 // - You can set the default disruptive action to deny and each risky rule will interrupt
 // the connection.
 //
-// Important: Every SecDefaultAction directive must specify a disruptive action and a processing phase and cannot contain metadata actions.
+// Important: Every `SecDefaultAction` directive must specify a disruptive action and a processing
+// phase and cannot contain metadata actions.
 func directiveSecDefaultAction(options *DirectiveOptions) error {
 	da, _ := options.Config.Get("rule_default_actions", []string{}).([]string)
 	da = append(da, options.Opts)
@@ -556,7 +558,7 @@ func directiveSecAuditLogDirMode(options *DirectiveOptions) error {
 }
 
 // Description: Configures the mode (permissions) of any files created for concurrent
-// audit logs using an octal mode (as used in chmod). See SecAuditLogDirMode for
+// audit logs using an octal mode (as used in `chmod`). See `SecAuditLogDirMode` for
 // controlling the mode of created audit log directories.
 // Syntax: SecAuditLogFileMode octal_mode|"default"
 // Default: 0600
@@ -594,13 +596,13 @@ func directiveSecAuditLogFileMode(options *DirectiveOptions) error {
 // ```
 // This example would log all 5xx and 4xx level status codes,
 // except for 404s. Although you could achieve the same effect with a rule in phase 5,
-// SecAuditLogRelevantStatus is sometimes better, because it continues to work even when SecRuleEngine
-// is disabled.
+// `SecAuditLogRelevantStatus` is sometimes better, because it continues to work even when
+// `SecRuleEngine` is disabled.
 //
-// Note: Must have SecAuditEngine set to RelevantOnly. Additionally, the auditlog action
-// is present by default in rules, this will make the engine bypass the SecAuditLogRelevantStatus
+// Note: Must have `SecAuditEngine` set to `RelevantOnly`. Additionally, the auditlog action
+// is present by default in rules, this will make the engine bypass the `SecAuditLogRelevantStatus`
 // and send rule matches to the audit log regardless of status. You must specify noauditlog in the
-// rules manually or set it in SecDefaultAction.
+// rules manually or set it in `SecDefaultAction`.
 func directiveSecAuditLogRelevantStatus(options *DirectiveOptions) error {
 	var err error
 	options.WAF.AuditLogRelevantStatus, err = regexp.Compile(options.Opts)
@@ -612,7 +614,7 @@ func directiveSecAuditLogRelevantStatus(options *DirectiveOptions) error {
 // in the list then the equivalent part will be recorded. See below for the list of
 // all parts.
 // Syntax: SecAuditLogParts [PARTLETTERS]
-// Default: ABCFHZ Note
+// Default: ABCFHZ
 // ---
 // The format of the audit log format is documented in detail in the Audit Log Data
 // Format Documentation.
@@ -624,27 +626,27 @@ func directiveSecAuditLogRelevantStatus(options *DirectiveOptions) error {
 //
 // Available audit log parts:
 //
-// A: Audit log header (mandatory).
-// B: Request headers.
-// C: Request body (present only if the request body exists and Coraza is configured
-// to intercept it. This would require SecRequestBodyAccess to be set to on).
-// D: Reserved for intermediary response headers; not implemented yet.
-// E: Intermediary response body (present only if Coraza is configured to intercept
+// - A: Audit log header (mandatory).
+// - B: Request headers.
+// - C: Request body (present only if the request body exists and Coraza is configured
+// to intercept it. This would require `SecRequestBodyAccess` to be set to on).
+// - D: Reserved for intermediary response headers; not implemented yet.
+// - E: Intermediary response body (present only if Coraza is configured to intercept
 // response bodies, and if the audit log engine is configured to record it. Intercepting
-// response bodies requires SecResponseBodyAccess to be enabled). Intermediary response
+// response bodies requires `SecResponseBodyAccess` to be enabled). Intermediary response
 // body is the same as the actual response body unless Coraza intercepts the intermediary
 // response body, in which case the actual response body will contain the error message.
-// F: Final response headers.
-// G: Reserved for the actual response body; not implemented yet.
-// H: Audit log trailer.
-// I: This part is a replacement for part C. It will log the same data as C in all cases except when
-// multipart/form-data encoding in used. In this case, it will log a fake application/x-www-form-urlencoded
+// - F: Final response headers.
+// - G: Reserved for the actual response body; not implemented yet.
+// - H: Audit log trailer.
+// - I: This part is a replacement for part C. It will log the same data as C in all cases except when
+// `multipart/form-data` encoding in used. In this case, it will log a fake `application/x-www-form-urlencoded`
 // body that contains the information about parameters but not about the files. This is handy if
 // you don’t want to have (often large) files stored in your audit logs.
-// J: This part contains information about the files uploaded using multipart/form-data encoding.
-// K: This part contains a full list of every rule that matched (one per line) in the order they were
+// - J: This part contains information about the files uploaded using `multipart/form-data` encoding.
+// - K: This part contains a full list of every rule that matched (one per line) in the order they were
 // matched. The rules are fully qualified and will thus show inherited actions and default operators.
-// Z: Final boundary, signifies the end of the entry (mandatory).
+// - Z: Final boundary, signifies the end of the entry (mandatory).
 func directiveSecAuditLogParts(options *DirectiveOptions) error {
 	options.WAF.AuditLogParts = types.AuditLogParts(options.Opts)
 	return nil
@@ -654,20 +656,20 @@ func directiveSecAuditLogParts(options *DirectiveOptions) error {
 // Syntax: SecAuditEngine RelevantOnly
 // Default: Off
 // ---
-// The SecAuditEngine directive is used to configure the audit engine, which logs complete
+// The `SecAuditEngine` directive is used to configure the audit engine, which logs complete
 // transactions.
 //
 // The possible values for the audit log engine are as follows:
 //   - On: log all transactions
 //   - Off: do not log any transactions
 //   - RelevantOnly: only the log transactions that have triggered a warning or an error, or have
-//     a status code that is considered to be relevant (as determined by the SecAuditLogRelevantStatus
+//     a status code that is considered to be relevant (as determined by the `SecAuditLogRelevantStatus`
 //     directive)
 //
 // Note: If you need to change the audit log engine configuration on a per-transaction basis (e.g.,
-// in response to some transaction data), use the ctl action.
+// in response to some transaction data), use the `ctl` action.
 //
-// The following example demonstrates how SecAuditEngine is used:
+// The following example demonstrates how `SecAuditEngine` is used:
 // ```apache
 // SecAuditEngine RelevantOnly
 // SecAuditLog logs/audit/audit.log
@@ -719,7 +721,7 @@ func directiveSecUploadDir(options *DirectiveOptions) error {
 // buffering, excluding the size of any files being transported in the request.
 // This directive is useful to reduce susceptibility to DoS attacks when someone is
 // sending request bodies of very large sizes. Web applications that require file uploads
-// must configure SecRequestBodyLimit to a high value, but because large files are streamed
+// must configure `SecRequestBodyLimit` to a high value, but because large files are streamed
 // to disk, file uploads will not increase memory consumption. However, it’s still possible
 // for someone to take advantage of a large request body limit and send non-upload requests
 // with large body sizes. This directive eliminates that loophole.
@@ -753,12 +755,12 @@ func directiveSecDebugLog(options *DirectiveOptions) error {
 // written to caddy server error logs.
 // The possible values for the debug log level are:
 //
-// 0: Fatal
-// 1: Panic
-// 2: Error
-// 3: Warning
-// 4: details of how transactions are handled
-// 5: log everything, including very detailed debugging information
+// - 0: Fatal
+// - 1: Panic
+// - 2: Error
+// - 3: Warning
+// - 4: details of how transactions are handled
+// - 5: log everything, including very detailed debugging information
 //
 // All levels over 5 will be considered as 5.
 func directiveSecDebugLogLevel(options *DirectiveOptions) error {
diff --git a/magefile.go b/magefile.go
index 18e63af..1b597a1 100644
--- a/magefile.go
+++ b/magefile.go
@@ -57,7 +57,7 @@ func Lint() error {
 		return err
 	}
 
-	if sh.Run("git", "diff", "--exit-code") != nil {
+	if sh.Run("git", "diff", "--exit-code", "--", "'*.gen.go'") != nil {
 		return errUpdateGeneratedFiles
 	}
 
diff --git a/types/transaction.go b/types/transaction.go
index ec1640c..637e217 100644
--- a/types/transaction.go
+++ b/types/transaction.go
@@ -33,7 +33,6 @@ type Transaction interface {
 	// ProcessConnection 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.
-	// Important: Remember to check for a possible intervention.
 	ProcessConnection(client string, cPort int, server string, sPort int)
 
 	// ProcessURI Performs the analysis on the URI and all the query string variables.
@@ -48,6 +47,13 @@ type Transaction interface {
 	// note: This function won't add GET arguments, they must be added with AddArgument
 	ProcessURI(uri string, method string, httpVersion string)
 
+	// SetServerName allows to set server name details.
+	// The API consumer is in charge of retrieving the value (e.g. from the host header)
+	// before providing it to this method.
+	// In order to be able to check SERVER_NAME variable since phase 1, it is expected
+	// to execute SetServerName before calling ProcessRequestHeaders.
+	SetServerName(serverName string)
+
 	// AddRequestHeader Adds a request header
 	//
 	// With this method it is possible to feed Coraza with a request header.
diff --git a/waf.go b/waf.go
index a4e666a..69f74f4 100644
--- a/waf.go
+++ b/waf.go
@@ -25,6 +25,8 @@ type WAF interface {
 	NewTransactionWithID(id string) types.Transaction
 }
 
+const _1gb = 1073741824
+
 // NewWAF creates a new WAF instance with the provided configuration.
 func NewWAF(config WAFConfig) (WAF, error) {
 	c := config.(*wafConfig)
@@ -82,13 +84,20 @@ func NewWAF(config WAFConfig) (WAF, error) {
 			return nil, errors.New("request body limit should be bigger than 0")
 		}
 
-		if c.requestBodyLimit < c.requestBodyInMemoryLimit {
-			return nil, errors.New("request body limit should be at least the memory limit")
+		if c.requestBodyLimit > _1gb {
+			return nil, errors.New("request body limit should be at most 1GB")
 		}
+
 		waf.RequestBodyLimit = int64(c.requestBodyLimit)
 	}
 
 	if c.requestBodyInMemoryLimit != unsetLimit {
+		if c.requestBodyLimit != unsetLimit {
+			if c.requestBodyLimit < c.requestBodyInMemoryLimit {
+				return nil, errors.New("request body limit should be at least the memory limit")
+			}
+		}
+
 		if c.requestBodyInMemoryLimit <= 0 {
 			return nil, errors.New("request body memory limit should be bigger than 0")
 		}
@@ -103,6 +112,11 @@ func NewWAF(config WAFConfig) (WAF, error) {
 		if c.responseBodyLimit <= 0 {
 			return nil, errors.New("response body limit should be bigger than 0")
 		}
+
+		if c.responseBodyLimit > _1gb {
+			return nil, errors.New("response body limit should be at most 1GB")
+		}
+
 		waf.ResponseBodyLimit = int64(c.responseBodyLimit)
 	}
 


Test patch:
diff --git a/internal/corazawaf/transaction_test.go b/internal/corazawaf/transaction_test.go
index 3e13ba4..3c42612 100644
--- a/internal/corazawaf/transaction_test.go
+++ b/internal/corazawaf/transaction_test.go
@@ -946,6 +946,28 @@ func TestTxProcessConnection(t *testing.T) {
 	}
 }
 
+func TestTxSetServerName(t *testing.T) {
+
+	l := &inspectableLogger{}
+	waf := NewWAF()
+	waf.Logger.SetOutput(l)
+	waf.Logger.SetLevel(loggers.LogLevelWarn)
+	tx := waf.NewTransaction()
+	tx.LastPhase = types.PhaseRequestHeaders
+	tx.SetServerName("coraza.io")
+	if tx.variables.serverName.String() != "coraza.io" {
+		t.Error("failed to set server name")
+	}
+	if want, have := 1, len(l.entries); want != have {
+		t.Fatalf("unexpected number of log entries, want %d, have %d", want, have)
+	}
+
+	if want, have := "SetServerName has been called after ProcessRequestHeaders", l.entries[0]; !strings.Contains(have, want) {
+		t.Fatalf("unexpected message, want %q, have %q", want, have)
+	}
+
+}
+
 func TestTxAddArgument(t *testing.T) {
 	waf := NewWAF()
 	tx := waf.NewTransaction()
diff --git a/waf_test.go b/waf_test.go
index d7e9d90..59f75b4 100644
--- a/waf_test.go
+++ b/waf_test.go
@@ -3,44 +3,100 @@
 
 package coraza
 
-import "testing"
+import (
+	"errors"
+	"testing"
+)
 
-func TestNewWAFLimits(t *testing.T) {
+func TestRequestBodyLimit(t *testing.T) {
 	testCases := map[string]struct {
-		expectedErr   string
+		expectedErr   error
 		limit         int
 		inMemoryLimit int
 	}{
 		"empty limit": {
-			expectedErr:   "request body limit should be bigger than 0",
 			limit:         0,
 			inMemoryLimit: 2,
+			expectedErr:   errors.New("request body limit should be bigger than 0"),
 		},
 		"empty memory limit": {
-			expectedErr:   "request body memory limit should be bigger than 0",
 			limit:         2,
 			inMemoryLimit: 0,
+			expectedErr:   errors.New("request body memory limit should be bigger than 0"),
 		},
 		"memory limit bigger than limit": {
 			limit:         5,
 			inMemoryLimit: 9,
-			expectedErr:   "request body limit should be at least the memory limit",
+			expectedErr:   errors.New("request body limit should be at least the memory limit"),
+		},
+		"limit bigger than the hard limit": {
+			limit:       1073741825,
+			expectedErr: errors.New("request body limit should be at most 1GB"),
+		},
+		"right limits": {
+			limit:         100,
+			inMemoryLimit: 50,
+		},
+	}
+
+	for name, tCase := range testCases {
+		t.Run(name, func(t *testing.T) {
+			cfg := NewWAFConfig().(*wafConfig)
+			cfg.requestBodyLimit = tCase.limit
+			cfg.requestBodyInMemoryLimit = tCase.inMemoryLimit
+
+			_, err := NewWAF(cfg)
+			if tCase.expectedErr == nil {
+				if err != nil {
+					t.Fatalf("unexpected error: %s", err.Error())
+				}
+			} else {
+				if err == nil {
+					t.Fatal("expected error")
+				}
+
+				if want, have := tCase.expectedErr, err; want.Error() != have.Error() {
+					t.Fatalf("unexpected error: want %q, have %q", want, have)
+				}
+			}
+		})
+	}
+}
+
+func TestResponseBodyLimit(t *testing.T) {
+	testCases := map[string]struct {
+		expectedErr error
+		limit       int
+	}{
+		"empty limit": {
+			limit:       0,
+			expectedErr: errors.New("response body limit should be bigger than 0"),
+		},
+		"limit bigger than the hard limit": {
+			limit:       1073741825,
+			expectedErr: errors.New("response body limit should be at most 1GB"),
+		},
+		"right limit": {
+			limit: 100,
 		},
 	}
 
 	for name, tCase := range testCases {
 		t.Run(name, func(t *testing.T) {
-			_, err := NewWAF(&wafConfig{
-				requestBodyLimit:         tCase.limit,
-				requestBodyInMemoryLimit: tCase.inMemoryLimit,
-			})
+			cfg := NewWAFConfig().(*wafConfig)
+			cfg.responseBodyLimit = tCase.limit
 
-			if tCase.expectedErr != "" {
+			_, err := NewWAF(cfg)
+			if tCase.expectedErr == nil {
+				if err != nil {
+					t.Fatalf("unexpected error: %s", err.Error())
+				}
+			} else {
 				if err == nil {
 					t.Fatal("expected error")
 				}
 
-				if want, have := tCase.expectedErr, err.Error(); want != have {
+				if want, have := tCase.expectedErr, err; want.Error() != have.Error() {
 					t.Fatalf("unexpected error: want %q, have %q", want, have)
 				}
 			}