We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Google link/crawler blocked at PL2
### Description
Hello everyone,

Here is another false positive found in our production.
The `ARGS:gclid` contains a token in URL when someone visits a website by clicking a shared link on google/youtube.
However, it matches the following rules:

942440 PL2 SQL Comment Sequence Detected
949110 PL1 Inbound Anomaly Score Exceeded (Total Score: 5)
980170 PL1 Anomaly Scores: (Inbound Scores: blocking=5, detection=5, per_pl=0-5-0-0, threshold=5) - (Outbound Scores: blocking=0, detection=0, per_pl=0-0-0-0, threshold=4) - (SQLI=5, XSS=0, RFI=0, LFI=0, RCE=0, PHPI=0, HTTP=0, SESS=0)

Example:
`example.com/file/?gclid=j0KCQiA1NebBhDDARIsAANiDD3_RJeMv8zScF--mC1jf8fO8PDYJCxD9xdwT7iQ59QIIwL-86ncQtMaAh0lEALw_wcB`

Test on sandbox:
`curl -s -H "x-format-output: txt-matched-rules" -H 'x-crs-paranoia-level: 2' 'https://sandbox.coreruleset.org/file/?gclid=Cj0KCQiA1NebBhDDARIsAANiDD3_RJeMv8zScF--mC1jf8fO8PDYJCxD9xdwT7iQ59QIIwL-86ncQtMaAh0lEALw_wcB'`

We excluded following way:
```
SecRule &ARGS:gclid "@gt 0" "id:xxxxxxxx,\
    ....,\
    ....,\
    ctl:ruleRemoveTargetById=942440;ARGS:gclid,\
    chain"
    SecRule ARGS:gclid "@rx ^[a-zA-Z0-9_-]{0,100}$" "t:none"

```
### Confirmation

- [x] I have removed any personal data (email addresses, IP addresses,
    passwords, domain names) from any logs posted.

Thanks as always, @theMiddleBlue 


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `util/find-rules-without-test/find-rules-without-test.py`
Content:
```
1 #!/usr/bin/env python3
2 
3 # This file helps to find the rules which does not have any test cases.
4 #
5 # You just have to pass the CORERULESET_ROOT as argument.
6 #
7 # At the end, the script will print the list of rules without any tests.
8 #
9 # Please note, that there are some exclusions:
10 # * only REQUEST-NNN rules are checked
11 # * there are some hardcoded exlucions:
12 #   * REQUEST-900-
13 #   * REQUEST-901-
14 #   * REQUEST-905-
15 #   * REQUEST-910-
16 #   * REQUEST-912.
17 #   * REQUEST-949-
18 #
19 #   and the rule 921170
20 
21 import sys
22 import glob
23 import msc_pyparser
24 import argparse
25 
26 EXCLUSION_LIST = ["900", "901", "905", "910", "912", "949", "921170"]
27 oformat = "native"
28 
29 def find_ids(s, test_cases):
30     """
31         s: the parsed structure
32         test_cases: all available test cases
33     """
34     rids = {}
35     for i in s:
36         # only SecRule counts
37         if i['type'] == "SecRule":
38             for a in i['actions']:
39                 # find the `id` action
40                 if a['act_name'] == "id":
41                     # get the argument of the action
42                     rid = int(a['act_arg']) # int
43                     srid = a['act_arg']     # string
44                     if (rid%1000) >= 100:   # skip the PL control rules
45                         # also skip these hardcoded rules
46                         need_check = True
47                         for excl in EXCLUSION_LIST:
48                             if srid[:len(excl)] == excl:
49                                 need_check = False
50                         if need_check:
51                             # if there is no test cases, just print it
52                             if rid not in test_cases:
53                                 rids[rid] = a['lineno']
54     return rids
55 
56 def errmsgf(msg):
57     if oformat == "github":
58         print("::error file={file},line={line},endLine={endLine},title={title}::{message}".format(**msg))
59     else:
60         print("file={file}, line={line}, endLine={endLine}, title={title}: {message}".format(**msg))
61 
62 if __name__ == "__main__":
63 
64     desc = """This script helps to find the rules without test cases. It needs a mandatory
65 argument where you pass the path to your coreruleset. The tool collects the
66 tests with name REQUEST-*, but not with RESPONSE-*. Then reads the rule id's,
67 and check which rule does not have any test. Some rules does not need test
68 case, these are hardcoded as exclusions: 900NNN, 901NNN, 905NNN, 910NNN,
69 912NNN, 949NNN."""
70 
71     parser = argparse.ArgumentParser(description=desc, formatter_class=argparse.RawTextHelpFormatter)
72     parser.add_argument("--output", dest="output", help="Output format native[default]|github", required=False)
73     parser.add_argument('crspath', metavar='/path/to/coreruleset', type=str,
74                         help='Directory path to CRS')
75     args = parser.parse_args()
76 
77     if args.output is not None:
78         if args.output not in ["native", "github"]:
79             print("--output can be one of the 'native' or 'github'. Default value is 'native'")
80             sys.exit(1)
81     oformat = args.output
82 
83     test_cases = {}
84     # from argument, build the rules path and regression test paths
85     crspath = args.crspath.rstrip("/") + "/rules/*.conf"
86     testpath = args.crspath.rstrip("/") + "/tests/regression/tests/*"
87     retval = 0
88     # collect rules
89     flist = glob.glob(crspath)
90     flist.sort()
91     if len(flist) == 0:
92         print("Can't open files in given path!")
93         sys.exit(1)
94 
95     # collect test cases
96     tlist = glob.glob(testpath)
97     tlist.sort()
98     if len(tlist) == 0:
99         print("Can't open files in given path (%s)!" % (testpath))
100         sys.exit(1)
101     # find the yaml files with name REQUEST at the begin
102     # collect them in a dictionary
103     for t in tlist:
104         tname = t.split("/")[-1]
105         if tname[:7] == "REQUEST":
106             testlist = glob.glob(t + "/*.yaml")
107             testlist.sort()
108             for tc in testlist:
109                 tcname = tc.split("/")[-1].split(".")[0]
110                 test_cases[int(tcname)] = 1
111 
112     # iterate the rule files
113     for f in flist:
114         fname = f.split("/")[-1]
115         if fname[:7] == "REQUEST":
116             try:
117                 with open(f, 'r') as inputfile:
118                     data = inputfile.read()
119             except:
120                 print("Can't open file: %s" % f)
121                 print(sys.exc_info())
122                 sys.exit(1)
123 
124             try:
125                 # make a structure
126                 mparser = msc_pyparser.MSCParser()
127                 mparser.parser.parse(data)
128                 # add the parsed structure to a function, which finds the 'id'-s,
129                 # and the collected test cases
130                 rids = find_ids(mparser.configlines, test_cases)
131                 for k in rids.keys():
132                     errmsgf({'file': f, 'line': rids[k], 'endLine': rids[k], 'title': "Test file missing", 'message': ("rule %d does not have any regression test" % k)})
133             except:
134                 print("Can't parse config file: %s" % (f))
135                 print(sys.exc_info()[1])
136                 sys.exit(1)
137     sys.exit(retval)
138
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/util/find-rules-without-test/find-rules-without-test.py b/util/find-rules-without-test/find-rules-without-test.py
--- a/util/find-rules-without-test/find-rules-without-test.py
+++ b/util/find-rules-without-test/find-rules-without-test.py
@@ -23,7 +23,7 @@
 import msc_pyparser
 import argparse
 
-EXCLUSION_LIST = ["900", "901", "905", "910", "912", "949", "921170"]
+EXCLUSION_LIST = ["900", "901", "905", "910", "912", "949", "921170", "942441", "942442"]
 oformat = "native"
 
 def find_ids(s, test_cases):

{"golden_diff": "diff --git a/util/find-rules-without-test/find-rules-without-test.py b/util/find-rules-without-test/find-rules-without-test.py\n--- a/util/find-rules-without-test/find-rules-without-test.py\n+++ b/util/find-rules-without-test/find-rules-without-test.py\n@@ -23,7 +23,7 @@\n import msc_pyparser\n import argparse\n \n-EXCLUSION_LIST = [\"900\", \"901\", \"905\", \"910\", \"912\", \"949\", \"921170\"]\n+EXCLUSION_LIST = [\"900\", \"901\", \"905\", \"910\", \"912\", \"949\", \"921170\", \"942441\", \"942442\"]\n oformat = \"native\"\n \n def find_ids(s, test_cases):\n", "issue": "Google link/crawler blocked at PL2\n### Description\r\nHello everyone,\r\n\r\nHere is another false positive found in our production.\r\nThe `ARGS:gclid` contains a token in URL when someone visits a website by clicking a shared link on google/youtube.\r\nHowever, it matches the following rules:\r\n\r\n942440 PL2 SQL Comment Sequence Detected\r\n949110 PL1 Inbound Anomaly Score Exceeded (Total Score: 5)\r\n980170 PL1 Anomaly Scores: (Inbound Scores: blocking=5, detection=5, per_pl=0-5-0-0, threshold=5) - (Outbound Scores: blocking=0, detection=0, per_pl=0-0-0-0, threshold=4) - (SQLI=5, XSS=0, RFI=0, LFI=0, RCE=0, PHPI=0, HTTP=0, SESS=0)\r\n\r\nExample:\r\n`example.com/file/?gclid=j0KCQiA1NebBhDDARIsAANiDD3_RJeMv8zScF--mC1jf8fO8PDYJCxD9xdwT7iQ59QIIwL-86ncQtMaAh0lEALw_wcB`\r\n\r\nTest on sandbox:\r\n`curl -s -H \"x-format-output: txt-matched-rules\" -H 'x-crs-paranoia-level: 2' 'https://sandbox.coreruleset.org/file/?gclid=Cj0KCQiA1NebBhDDARIsAANiDD3_RJeMv8zScF--mC1jf8fO8PDYJCxD9xdwT7iQ59QIIwL-86ncQtMaAh0lEALw_wcB'`\r\n\r\nWe excluded following way:\r\n```\r\nSecRule &ARGS:gclid \"@gt 0\" \"id:xxxxxxxx,\\\r\n    ....,\\\r\n    ....,\\\r\n    ctl:ruleRemoveTargetById=942440;ARGS:gclid,\\\r\n    chain\"\r\n    SecRule ARGS:gclid \"@rx ^[a-zA-Z0-9_-]{0,100}$\" \"t:none\"\r\n\r\n```\r\n### Confirmation\r\n\r\n- [x] I have removed any personal data (email addresses, IP addresses,\r\n    passwords, domain names) from any logs posted.\r\n\r\nThanks as always, @theMiddleBlue \r\n\n", "before_files": [{"content": "#!/usr/bin/env python3\n\n# This file helps to find the rules which does not have any test cases.\n#\n# You just have to pass the CORERULESET_ROOT as argument.\n#\n# At the end, the script will print the list of rules without any tests.\n#\n# Please note, that there are some exclusions:\n# * only REQUEST-NNN rules are checked\n# * there are some hardcoded exlucions:\n#   * REQUEST-900-\n#   * REQUEST-901-\n#   * REQUEST-905-\n#   * REQUEST-910-\n#   * REQUEST-912.\n#   * REQUEST-949-\n#\n#   and the rule 921170\n\nimport sys\nimport glob\nimport msc_pyparser\nimport argparse\n\nEXCLUSION_LIST = [\"900\", \"901\", \"905\", \"910\", \"912\", \"949\", \"921170\"]\noformat = \"native\"\n\ndef find_ids(s, test_cases):\n    \"\"\"\n        s: the parsed structure\n        test_cases: all available test cases\n    \"\"\"\n    rids = {}\n    for i in s:\n        # only SecRule counts\n        if i['type'] == \"SecRule\":\n            for a in i['actions']:\n                # find the `id` action\n                if a['act_name'] == \"id\":\n                    # get the argument of the action\n                    rid = int(a['act_arg']) # int\n                    srid = a['act_arg']     # string\n                    if (rid%1000) >= 100:   # skip the PL control rules\n                        # also skip these hardcoded rules\n                        need_check = True\n                        for excl in EXCLUSION_LIST:\n                            if srid[:len(excl)] == excl:\n                                need_check = False\n                        if need_check:\n                            # if there is no test cases, just print it\n                            if rid not in test_cases:\n                                rids[rid] = a['lineno']\n    return rids\n\ndef errmsgf(msg):\n    if oformat == \"github\":\n        print(\"::error file={file},line={line},endLine={endLine},title={title}::{message}\".format(**msg))\n    else:\n        print(\"file={file}, line={line}, endLine={endLine}, title={title}: {message}\".format(**msg))\n\nif __name__ == \"__main__\":\n\n    desc = \"\"\"This script helps to find the rules without test cases. It needs a mandatory\nargument where you pass the path to your coreruleset. The tool collects the\ntests with name REQUEST-*, but not with RESPONSE-*. Then reads the rule id's,\nand check which rule does not have any test. Some rules does not need test\ncase, these are hardcoded as exclusions: 900NNN, 901NNN, 905NNN, 910NNN,\n912NNN, 949NNN.\"\"\"\n\n    parser = argparse.ArgumentParser(description=desc, formatter_class=argparse.RawTextHelpFormatter)\n    parser.add_argument(\"--output\", dest=\"output\", help=\"Output format native[default]|github\", required=False)\n    parser.add_argument('crspath', metavar='/path/to/coreruleset', type=str,\n                        help='Directory path to CRS')\n    args = parser.parse_args()\n\n    if args.output is not None:\n        if args.output not in [\"native\", \"github\"]:\n            print(\"--output can be one of the 'native' or 'github'. Default value is 'native'\")\n            sys.exit(1)\n    oformat = args.output\n\n    test_cases = {}\n    # from argument, build the rules path and regression test paths\n    crspath = args.crspath.rstrip(\"/\") + \"/rules/*.conf\"\n    testpath = args.crspath.rstrip(\"/\") + \"/tests/regression/tests/*\"\n    retval = 0\n    # collect rules\n    flist = glob.glob(crspath)\n    flist.sort()\n    if len(flist) == 0:\n        print(\"Can't open files in given path!\")\n        sys.exit(1)\n\n    # collect test cases\n    tlist = glob.glob(testpath)\n    tlist.sort()\n    if len(tlist) == 0:\n        print(\"Can't open files in given path (%s)!\" % (testpath))\n        sys.exit(1)\n    # find the yaml files with name REQUEST at the begin\n    # collect them in a dictionary\n    for t in tlist:\n        tname = t.split(\"/\")[-1]\n        if tname[:7] == \"REQUEST\":\n            testlist = glob.glob(t + \"/*.yaml\")\n            testlist.sort()\n            for tc in testlist:\n                tcname = tc.split(\"/\")[-1].split(\".\")[0]\n                test_cases[int(tcname)] = 1\n\n    # iterate the rule files\n    for f in flist:\n        fname = f.split(\"/\")[-1]\n        if fname[:7] == \"REQUEST\":\n            try:\n                with open(f, 'r') as inputfile:\n                    data = inputfile.read()\n            except:\n                print(\"Can't open file: %s\" % f)\n                print(sys.exc_info())\n                sys.exit(1)\n\n            try:\n                # make a structure\n                mparser = msc_pyparser.MSCParser()\n                mparser.parser.parse(data)\n                # add the parsed structure to a function, which finds the 'id'-s,\n                # and the collected test cases\n                rids = find_ids(mparser.configlines, test_cases)\n                for k in rids.keys():\n                    errmsgf({'file': f, 'line': rids[k], 'endLine': rids[k], 'title': \"Test file missing\", 'message': (\"rule %d does not have any regression test\" % k)})\n            except:\n                print(\"Can't parse config file: %s\" % (f))\n                print(sys.exc_info()[1])\n                sys.exit(1)\n    sys.exit(retval)\n", "path": "util/find-rules-without-test/find-rules-without-test.py"}], "after_files": [{"content": "#!/usr/bin/env python3\n\n# This file helps to find the rules which does not have any test cases.\n#\n# You just have to pass the CORERULESET_ROOT as argument.\n#\n# At the end, the script will print the list of rules without any tests.\n#\n# Please note, that there are some exclusions:\n# * only REQUEST-NNN rules are checked\n# * there are some hardcoded exlucions:\n#   * REQUEST-900-\n#   * REQUEST-901-\n#   * REQUEST-905-\n#   * REQUEST-910-\n#   * REQUEST-912.\n#   * REQUEST-949-\n#\n#   and the rule 921170\n\nimport sys\nimport glob\nimport msc_pyparser\nimport argparse\n\nEXCLUSION_LIST = [\"900\", \"901\", \"905\", \"910\", \"912\", \"949\", \"921170\", \"942441\", \"942442\"]\noformat = \"native\"\n\ndef find_ids(s, test_cases):\n    \"\"\"\n        s: the parsed structure\n        test_cases: all available test cases\n    \"\"\"\n    rids = {}\n    for i in s:\n        # only SecRule counts\n        if i['type'] == \"SecRule\":\n            for a in i['actions']:\n                # find the `id` action\n                if a['act_name'] == \"id\":\n                    # get the argument of the action\n                    rid = int(a['act_arg']) # int\n                    srid = a['act_arg']     # string\n                    if (rid%1000) >= 100:   # skip the PL control rules\n                        # also skip these hardcoded rules\n                        need_check = True\n                        for excl in EXCLUSION_LIST:\n                            if srid[:len(excl)] == excl:\n                                need_check = False\n                        if need_check:\n                            # if there is no test cases, just print it\n                            if rid not in test_cases:\n                                rids[rid] = a['lineno']\n    return rids\n\ndef errmsgf(msg):\n    if oformat == \"github\":\n        print(\"::error file={file},line={line},endLine={endLine},title={title}::{message}\".format(**msg))\n    else:\n        print(\"file={file}, line={line}, endLine={endLine}, title={title}: {message}\".format(**msg))\n\nif __name__ == \"__main__\":\n\n    desc = \"\"\"This script helps to find the rules without test cases. It needs a mandatory\nargument where you pass the path to your coreruleset. The tool collects the\ntests with name REQUEST-*, but not with RESPONSE-*. Then reads the rule id's,\nand check which rule does not have any test. Some rules does not need test\ncase, these are hardcoded as exclusions: 900NNN, 901NNN, 905NNN, 910NNN,\n912NNN, 949NNN.\"\"\"\n\n    parser = argparse.ArgumentParser(description=desc, formatter_class=argparse.RawTextHelpFormatter)\n    parser.add_argument(\"--output\", dest=\"output\", help=\"Output format native[default]|github\", required=False)\n    parser.add_argument('crspath', metavar='/path/to/coreruleset', type=str,\n                        help='Directory path to CRS')\n    args = parser.parse_args()\n\n    if args.output is not None:\n        if args.output not in [\"native\", \"github\"]:\n            print(\"--output can be one of the 'native' or 'github'. Default value is 'native'\")\n            sys.exit(1)\n    oformat = args.output\n\n    test_cases = {}\n    # from argument, build the rules path and regression test paths\n    crspath = args.crspath.rstrip(\"/\") + \"/rules/*.conf\"\n    testpath = args.crspath.rstrip(\"/\") + \"/tests/regression/tests/*\"\n    retval = 0\n    # collect rules\n    flist = glob.glob(crspath)\n    flist.sort()\n    if len(flist) == 0:\n        print(\"Can't open files in given path!\")\n        sys.exit(1)\n\n    # collect test cases\n    tlist = glob.glob(testpath)\n    tlist.sort()\n    if len(tlist) == 0:\n        print(\"Can't open files in given path (%s)!\" % (testpath))\n        sys.exit(1)\n    # find the yaml files with name REQUEST at the begin\n    # collect them in a dictionary\n    for t in tlist:\n        tname = t.split(\"/\")[-1]\n        if tname[:7] == \"REQUEST\":\n            testlist = glob.glob(t + \"/*.yaml\")\n            testlist.sort()\n            for tc in testlist:\n                tcname = tc.split(\"/\")[-1].split(\".\")[0]\n                test_cases[int(tcname)] = 1\n\n    # iterate the rule files\n    for f in flist:\n        fname = f.split(\"/\")[-1]\n        if fname[:7] == \"REQUEST\":\n            try:\n                with open(f, 'r') as inputfile:\n                    data = inputfile.read()\n            except:\n                print(\"Can't open file: %s\" % f)\n                print(sys.exc_info())\n                sys.exit(1)\n\n            try:\n                # make a structure\n                mparser = msc_pyparser.MSCParser()\n                mparser.parser.parse(data)\n                # add the parsed structure to a function, which finds the 'id'-s,\n                # and the collected test cases\n                rids = find_ids(mparser.configlines, test_cases)\n                for k in rids.keys():\n                    errmsgf({'file': f, 'line': rids[k], 'endLine': rids[k], 'title': \"Test file missing\", 'message': (\"rule %d does not have any regression test\" % k)})\n            except:\n                print(\"Can't parse config file: %s\" % (f))\n                print(sys.exc_info()[1])\n                sys.exit(1)\n    sys.exit(retval)\n", "path": "util/find-rules-without-test/find-rules-without-test.py"}]}