We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
sigma2attack does not support collections
Collections parsing happens only in [`collection.py`](tools/sigma/parser/collection.py) it seems but [`sigma2attack`](/tools/sigma/sigma2attack.py#L24) uses good old `yaml.safe_load` on his own.

that leads to errors when parsing and rules being ignored

```
[snip]
Ignoring rule rules\windows\other\win_tool_psexec.yml (parsing failed)
Ignoring rule rules\windows\powershell\win_powershell_web_request.yml (parsing failed)
Ignoring rule rules\windows\process_access\sysmon_cmstp_execution.yml (parsing failed)
Ignoring rule rules\windows\process_creation\win_apt_chafer_mar18.yml (parsing failed)
Ignoring rule rules\windows\process_creation\win_apt_empiremonkey.yml (parsing failed)
Ignoring rule rules\windows\process_creation\win_apt_gallium.yml (parsing failed)
[snip]
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `tools/sigma/sigma2attack.py`
Content:
```
1 #!/usr/bin/env python3
2 
3 import argparse
4 import glob
5 import json
6 import os
7 import sys
8 
9 import yaml
10 
11 def main():
12     parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
13     parser.add_argument("--rules-directory", "-d", dest="rules_dir", default="rules", help="Directory to read rules from")
14     parser.add_argument("--out-file", "-o", dest="out_file", default="heatmap.json", help="File to write the JSON layer to")
15     parser.add_argument("--no-comment", dest="no_comment", action="store_true", help="Don't store rule names in comments")
16     args = parser.parse_args()
17 
18     rule_files = glob.glob(os.path.join(args.rules_dir, "**/*.yml"), recursive=True)
19     techniques_to_rules = {}
20     curr_max_technique_count = 0
21     num_rules_used = 0
22     for rule_file in rule_files:
23         try:
24             rule = yaml.safe_load(open(rule_file, encoding="utf-8").read())
25         except yaml.YAMLError:
26             sys.stderr.write("Ignoring rule " + rule_file + " (parsing failed)\n")
27             continue
28         if "tags" not in rule:
29             sys.stderr.write("Ignoring rule " + rule_file + " (no tags)\n")
30             continue
31         tags = rule["tags"]
32         for tag in tags:
33             if tag.lower().startswith("attack.t"):
34                 technique_id = tag[len("attack."):].upper()
35                 num_rules_used += 1
36                 if technique_id not in techniques_to_rules:
37                     techniques_to_rules[technique_id] = []
38                 techniques_to_rules[technique_id].append(os.path.basename(rule_file))
39                 curr_max_technique_count = max(curr_max_technique_count, len(techniques_to_rules[technique_id]))
40 
41 
42     scores = []
43     for technique in techniques_to_rules:
44         entry = {
45             "techniqueID": technique, 
46             "score": len(techniques_to_rules[technique]), 
47         }
48         if not args.no_comment:
49             entry["comment"] = "\n".join(techniques_to_rules[technique])
50 
51         scores.append(entry)
52 
53     output = {
54         "domain": "mitre-enterprise",
55         "name": "Sigma rules heatmap",
56         "gradient": {
57             "colors": [
58                 "#ffffff",
59                 "#ff6666"
60             ],
61             "maxValue": curr_max_technique_count,
62             "minValue": 0
63         },
64         "versions": {
65             "navigator": "4.0",
66             "layer": "4.0"
67         },
68         "techniques": scores,
69     }
70 
71     with open(args.out_file, "w") as f:
72         f.write(json.dumps(output))
73         print("[*] Layer file written in " + args.out_file + " (" + str(num_rules_used) + " rules)")
74 
75 if __name__ == "__main__":
76     main()
77
```
--- 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/tools/sigma/sigma2attack.py b/tools/sigma/sigma2attack.py
--- a/tools/sigma/sigma2attack.py
+++ b/tools/sigma/sigma2attack.py
@@ -8,6 +8,7 @@
 
 import yaml
 
+
 def main():
     parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
     parser.add_argument("--rules-directory", "-d", dest="rules_dir", default="rules", help="Directory to read rules from")
@@ -20,24 +21,25 @@
     curr_max_technique_count = 0
     num_rules_used = 0
     for rule_file in rule_files:
-        try:
-            rule = yaml.safe_load(open(rule_file, encoding="utf-8").read())
-        except yaml.YAMLError:
-            sys.stderr.write("Ignoring rule " + rule_file + " (parsing failed)\n")
-            continue
-        if "tags" not in rule:
-            sys.stderr.write("Ignoring rule " + rule_file + " (no tags)\n")
-            continue
-        tags = rule["tags"]
-        for tag in tags:
-            if tag.lower().startswith("attack.t"):
-                technique_id = tag[len("attack."):].upper()
-                num_rules_used += 1
-                if technique_id not in techniques_to_rules:
-                    techniques_to_rules[technique_id] = []
-                techniques_to_rules[technique_id].append(os.path.basename(rule_file))
-                curr_max_technique_count = max(curr_max_technique_count, len(techniques_to_rules[technique_id]))
-
+        with open(rule_file,encoding='utf-8') as f:
+            docs = yaml.load_all(f, Loader=yaml.FullLoader)
+            double = False
+            for rule in docs:
+                if "tags" not in rule :
+                    if double == False : # Only 1 warning
+                        sys.stderr.write("Ignoring rule " + rule_file + " (no tags)\n")
+                    double = True # action globle no tag
+                    continue
+                tags = rule["tags"]
+                double = True
+                for tag in tags:
+                    if tag.lower().startswith("attack.t"):
+                        technique_id = tag[len("attack."):].upper()
+                        num_rules_used += 1
+                        if technique_id not in techniques_to_rules:
+                            techniques_to_rules[technique_id] = []
+                        techniques_to_rules[technique_id].append(os.path.basename(rule_file))
+                        curr_max_technique_count = max(curr_max_technique_count, len(techniques_to_rules[technique_id]))
 
     scores = []
     for technique in techniques_to_rules:

{"golden_diff": "diff --git a/tools/sigma/sigma2attack.py b/tools/sigma/sigma2attack.py\n--- a/tools/sigma/sigma2attack.py\n+++ b/tools/sigma/sigma2attack.py\n@@ -8,6 +8,7 @@\n \n import yaml\n \n+\n def main():\n     parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)\n     parser.add_argument(\"--rules-directory\", \"-d\", dest=\"rules_dir\", default=\"rules\", help=\"Directory to read rules from\")\n@@ -20,24 +21,25 @@\n     curr_max_technique_count = 0\n     num_rules_used = 0\n     for rule_file in rule_files:\n-        try:\n-            rule = yaml.safe_load(open(rule_file, encoding=\"utf-8\").read())\n-        except yaml.YAMLError:\n-            sys.stderr.write(\"Ignoring rule \" + rule_file + \" (parsing failed)\\n\")\n-            continue\n-        if \"tags\" not in rule:\n-            sys.stderr.write(\"Ignoring rule \" + rule_file + \" (no tags)\\n\")\n-            continue\n-        tags = rule[\"tags\"]\n-        for tag in tags:\n-            if tag.lower().startswith(\"attack.t\"):\n-                technique_id = tag[len(\"attack.\"):].upper()\n-                num_rules_used += 1\n-                if technique_id not in techniques_to_rules:\n-                    techniques_to_rules[technique_id] = []\n-                techniques_to_rules[technique_id].append(os.path.basename(rule_file))\n-                curr_max_technique_count = max(curr_max_technique_count, len(techniques_to_rules[technique_id]))\n-\n+        with open(rule_file,encoding='utf-8') as f:\n+            docs = yaml.load_all(f, Loader=yaml.FullLoader)\n+            double = False\n+            for rule in docs:\n+                if \"tags\" not in rule :\n+                    if double == False : # Only 1 warning\n+                        sys.stderr.write(\"Ignoring rule \" + rule_file + \" (no tags)\\n\")\n+                    double = True # action globle no tag\n+                    continue\n+                tags = rule[\"tags\"]\n+                double = True\n+                for tag in tags:\n+                    if tag.lower().startswith(\"attack.t\"):\n+                        technique_id = tag[len(\"attack.\"):].upper()\n+                        num_rules_used += 1\n+                        if technique_id not in techniques_to_rules:\n+                            techniques_to_rules[technique_id] = []\n+                        techniques_to_rules[technique_id].append(os.path.basename(rule_file))\n+                        curr_max_technique_count = max(curr_max_technique_count, len(techniques_to_rules[technique_id]))\n \n     scores = []\n     for technique in techniques_to_rules:\n", "issue": "sigma2attack does not support collections\nCollections parsing happens only in [`collection.py`](tools/sigma/parser/collection.py) it seems but [`sigma2attack`](/tools/sigma/sigma2attack.py#L24) uses good old `yaml.safe_load` on his own.\r\n\r\nthat leads to errors when parsing and rules being ignored\r\n\r\n```\r\n[snip]\r\nIgnoring rule rules\\windows\\other\\win_tool_psexec.yml (parsing failed)\r\nIgnoring rule rules\\windows\\powershell\\win_powershell_web_request.yml (parsing failed)\r\nIgnoring rule rules\\windows\\process_access\\sysmon_cmstp_execution.yml (parsing failed)\r\nIgnoring rule rules\\windows\\process_creation\\win_apt_chafer_mar18.yml (parsing failed)\r\nIgnoring rule rules\\windows\\process_creation\\win_apt_empiremonkey.yml (parsing failed)\r\nIgnoring rule rules\\windows\\process_creation\\win_apt_gallium.yml (parsing failed)\r\n[snip]\r\n```\n", "before_files": [{"content": "#!/usr/bin/env python3\n\nimport argparse\nimport glob\nimport json\nimport os\nimport sys\n\nimport yaml\n\ndef main():\n    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)\n    parser.add_argument(\"--rules-directory\", \"-d\", dest=\"rules_dir\", default=\"rules\", help=\"Directory to read rules from\")\n    parser.add_argument(\"--out-file\", \"-o\", dest=\"out_file\", default=\"heatmap.json\", help=\"File to write the JSON layer to\")\n    parser.add_argument(\"--no-comment\", dest=\"no_comment\", action=\"store_true\", help=\"Don't store rule names in comments\")\n    args = parser.parse_args()\n\n    rule_files = glob.glob(os.path.join(args.rules_dir, \"**/*.yml\"), recursive=True)\n    techniques_to_rules = {}\n    curr_max_technique_count = 0\n    num_rules_used = 0\n    for rule_file in rule_files:\n        try:\n            rule = yaml.safe_load(open(rule_file, encoding=\"utf-8\").read())\n        except yaml.YAMLError:\n            sys.stderr.write(\"Ignoring rule \" + rule_file + \" (parsing failed)\\n\")\n            continue\n        if \"tags\" not in rule:\n            sys.stderr.write(\"Ignoring rule \" + rule_file + \" (no tags)\\n\")\n            continue\n        tags = rule[\"tags\"]\n        for tag in tags:\n            if tag.lower().startswith(\"attack.t\"):\n                technique_id = tag[len(\"attack.\"):].upper()\n                num_rules_used += 1\n                if technique_id not in techniques_to_rules:\n                    techniques_to_rules[technique_id] = []\n                techniques_to_rules[technique_id].append(os.path.basename(rule_file))\n                curr_max_technique_count = max(curr_max_technique_count, len(techniques_to_rules[technique_id]))\n\n\n    scores = []\n    for technique in techniques_to_rules:\n        entry = {\n            \"techniqueID\": technique, \n            \"score\": len(techniques_to_rules[technique]), \n        }\n        if not args.no_comment:\n            entry[\"comment\"] = \"\\n\".join(techniques_to_rules[technique])\n\n        scores.append(entry)\n\n    output = {\n        \"domain\": \"mitre-enterprise\",\n        \"name\": \"Sigma rules heatmap\",\n        \"gradient\": {\n            \"colors\": [\n                \"#ffffff\",\n                \"#ff6666\"\n            ],\n            \"maxValue\": curr_max_technique_count,\n            \"minValue\": 0\n        },\n        \"versions\": {\n            \"navigator\": \"4.0\",\n            \"layer\": \"4.0\"\n        },\n        \"techniques\": scores,\n    }\n\n    with open(args.out_file, \"w\") as f:\n        f.write(json.dumps(output))\n        print(\"[*] Layer file written in \" + args.out_file + \" (\" + str(num_rules_used) + \" rules)\")\n\nif __name__ == \"__main__\":\n    main()\n", "path": "tools/sigma/sigma2attack.py"}], "after_files": [{"content": "#!/usr/bin/env python3\n\nimport argparse\nimport glob\nimport json\nimport os\nimport sys\n\nimport yaml\n\n\ndef main():\n    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)\n    parser.add_argument(\"--rules-directory\", \"-d\", dest=\"rules_dir\", default=\"rules\", help=\"Directory to read rules from\")\n    parser.add_argument(\"--out-file\", \"-o\", dest=\"out_file\", default=\"heatmap.json\", help=\"File to write the JSON layer to\")\n    parser.add_argument(\"--no-comment\", dest=\"no_comment\", action=\"store_true\", help=\"Don't store rule names in comments\")\n    args = parser.parse_args()\n\n    rule_files = glob.glob(os.path.join(args.rules_dir, \"**/*.yml\"), recursive=True)\n    techniques_to_rules = {}\n    curr_max_technique_count = 0\n    num_rules_used = 0\n    for rule_file in rule_files:\n        with open(rule_file,encoding='utf-8') as f:\n            docs = yaml.load_all(f, Loader=yaml.FullLoader)\n            double = False\n            for rule in docs:\n                if \"tags\" not in rule :\n                    if double == False : # Only 1 warning\n                        sys.stderr.write(\"Ignoring rule \" + rule_file + \" (no tags)\\n\")\n                    double = True # action globle no tag\n                    continue\n                tags = rule[\"tags\"]\n                double = True\n                for tag in tags:\n                    if tag.lower().startswith(\"attack.t\"):\n                        technique_id = tag[len(\"attack.\"):].upper()\n                        num_rules_used += 1\n                        if technique_id not in techniques_to_rules:\n                            techniques_to_rules[technique_id] = []\n                        techniques_to_rules[technique_id].append(os.path.basename(rule_file))\n                        curr_max_technique_count = max(curr_max_technique_count, len(techniques_to_rules[technique_id]))\n\n    scores = []\n    for technique in techniques_to_rules:\n        entry = {\n            \"techniqueID\": technique, \n            \"score\": len(techniques_to_rules[technique]), \n        }\n        if not args.no_comment:\n            entry[\"comment\"] = \"\\n\".join(techniques_to_rules[technique])\n\n        scores.append(entry)\n\n    output = {\n        \"domain\": \"mitre-enterprise\",\n        \"name\": \"Sigma rules heatmap\",\n        \"gradient\": {\n            \"colors\": [\n                \"#ffffff\",\n                \"#ff6666\"\n            ],\n            \"maxValue\": curr_max_technique_count,\n            \"minValue\": 0\n        },\n        \"versions\": {\n            \"navigator\": \"4.0\",\n            \"layer\": \"4.0\"\n        },\n        \"techniques\": scores,\n    }\n\n    with open(args.out_file, \"w\") as f:\n        f.write(json.dumps(output))\n        print(\"[*] Layer file written in \" + args.out_file + \" (\" + str(num_rules_used) + \" rules)\")\n\nif __name__ == \"__main__\":\n    main()\n", "path": "tools/sigma/sigma2attack.py"}]}