secrulekit 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- secrulekit/__init__.py +19 -0
- secrulekit/cli.py +244 -0
- secrulekit/converters/__init__.py +3 -0
- secrulekit/converters/converter.py +208 -0
- secrulekit/parsers/__init__.py +5 -0
- secrulekit/parsers/sigma_parser.py +109 -0
- secrulekit/parsers/suricata_parser.py +125 -0
- secrulekit/parsers/yara_parser.py +138 -0
- secrulekit/reporters/__init__.py +4 -0
- secrulekit/reporters/html_reporter.py +92 -0
- secrulekit/reporters/json_reporter.py +38 -0
- secrulekit/utils/__init__.py +3 -0
- secrulekit/utils/helpers.py +104 -0
- secrulekit/validators/__init__.py +3 -0
- secrulekit/validators/rule_validator.py +276 -0
- secrulekit-1.2.0.dist-info/METADATA +241 -0
- secrulekit-1.2.0.dist-info/RECORD +21 -0
- secrulekit-1.2.0.dist-info/WHEEL +5 -0
- secrulekit-1.2.0.dist-info/entry_points.txt +2 -0
- secrulekit-1.2.0.dist-info/licenses/LICENSE +21 -0
- secrulekit-1.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""Multi-format rule validator."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Iterable, List, Optional, Union
|
|
10
|
+
|
|
11
|
+
from secrulekit.parsers.yara_parser import YaraRule, YaraParser, YaraParseError
|
|
12
|
+
from secrulekit.parsers.sigma_parser import SigmaRule, SigmaParser, SigmaParseError
|
|
13
|
+
from secrulekit.parsers.suricata_parser import SuricataRule, SuricataParser, SuricataParseError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Severity(str, Enum):
|
|
17
|
+
ERROR = "error"
|
|
18
|
+
WARNING = "warning"
|
|
19
|
+
INFO = "info"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Finding:
|
|
24
|
+
severity: Severity
|
|
25
|
+
code: str
|
|
26
|
+
message: str
|
|
27
|
+
rule_name: Optional[str] = None
|
|
28
|
+
file: Optional[Path] = None
|
|
29
|
+
line: int = 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class ValidationResult:
|
|
34
|
+
findings: List[Finding] = field(default_factory=list)
|
|
35
|
+
valid_count: int = 0
|
|
36
|
+
error_count: int = 0
|
|
37
|
+
warning_count: int = 0
|
|
38
|
+
|
|
39
|
+
def add(self, finding: Finding) -> None:
|
|
40
|
+
self.findings.append(finding)
|
|
41
|
+
if finding.severity == Severity.ERROR:
|
|
42
|
+
self.error_count += 1
|
|
43
|
+
elif finding.severity == Severity.WARNING:
|
|
44
|
+
self.warning_count += 1
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def passed(self) -> bool:
|
|
48
|
+
return self.error_count == 0
|
|
49
|
+
|
|
50
|
+
def summary(self) -> str:
|
|
51
|
+
total = self.valid_count + self.error_count
|
|
52
|
+
return (
|
|
53
|
+
f"{self.valid_count}/{total} rules valid, "
|
|
54
|
+
f"{self.error_count} errors, {self.warning_count} warnings"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class RuleValidator:
|
|
59
|
+
"""Validate YARA, Sigma, and Suricata rules."""
|
|
60
|
+
|
|
61
|
+
def __init__(self, strict: bool = False):
|
|
62
|
+
self.strict = strict
|
|
63
|
+
|
|
64
|
+
# ------------------------------------------------------------------ #
|
|
65
|
+
# YARA
|
|
66
|
+
# ------------------------------------------------------------------ #
|
|
67
|
+
|
|
68
|
+
def validate_yara(self, rule: YaraRule) -> List[Finding]:
|
|
69
|
+
findings: List[Finding] = []
|
|
70
|
+
|
|
71
|
+
if not rule.condition:
|
|
72
|
+
findings.append(Finding(
|
|
73
|
+
Severity.ERROR, "YARA001",
|
|
74
|
+
"Rule has no condition block",
|
|
75
|
+
rule_name=rule.name, file=rule.source_file, line=rule.line_number,
|
|
76
|
+
))
|
|
77
|
+
|
|
78
|
+
if not rule.strings and "filesize" not in rule.condition and "all" not in rule.condition:
|
|
79
|
+
findings.append(Finding(
|
|
80
|
+
Severity.WARNING, "YARA002",
|
|
81
|
+
"Rule has no strings and condition may never match",
|
|
82
|
+
rule_name=rule.name, file=rule.source_file, line=rule.line_number,
|
|
83
|
+
))
|
|
84
|
+
|
|
85
|
+
# Warn about overly broad conditions
|
|
86
|
+
if rule.condition.strip() in ("true", "any of them"):
|
|
87
|
+
findings.append(Finding(
|
|
88
|
+
Severity.WARNING, "YARA003",
|
|
89
|
+
f"Overly broad condition: '{rule.condition.strip()}'",
|
|
90
|
+
rule_name=rule.name, file=rule.source_file, line=rule.line_number,
|
|
91
|
+
))
|
|
92
|
+
|
|
93
|
+
# Check for potentially slow regex strings (no anchoring hint)
|
|
94
|
+
for s in rule.strings:
|
|
95
|
+
if s.type == "regex" and not s.value.startswith("/^"):
|
|
96
|
+
findings.append(Finding(
|
|
97
|
+
Severity.INFO, "YARA004",
|
|
98
|
+
f"Unanchored regex string '{s.name}' may be slow on large files",
|
|
99
|
+
rule_name=rule.name, file=rule.source_file, line=rule.line_number,
|
|
100
|
+
))
|
|
101
|
+
|
|
102
|
+
# Missing recommended meta fields
|
|
103
|
+
for meta_key in ("description", "author"):
|
|
104
|
+
if meta_key not in rule.meta:
|
|
105
|
+
findings.append(Finding(
|
|
106
|
+
Severity.WARNING if self.strict else Severity.INFO,
|
|
107
|
+
"YARA005",
|
|
108
|
+
f"Missing recommended meta field: {meta_key}",
|
|
109
|
+
rule_name=rule.name, file=rule.source_file, line=rule.line_number,
|
|
110
|
+
))
|
|
111
|
+
|
|
112
|
+
return findings
|
|
113
|
+
|
|
114
|
+
# ------------------------------------------------------------------ #
|
|
115
|
+
# Sigma
|
|
116
|
+
# ------------------------------------------------------------------ #
|
|
117
|
+
|
|
118
|
+
def validate_sigma(self, rule: SigmaRule) -> List[Finding]:
|
|
119
|
+
findings: List[Finding] = []
|
|
120
|
+
|
|
121
|
+
if not rule.logsource:
|
|
122
|
+
findings.append(Finding(
|
|
123
|
+
Severity.ERROR, "SIGMA001",
|
|
124
|
+
"Missing logsource block",
|
|
125
|
+
rule_name=rule.title, file=rule.source_file,
|
|
126
|
+
))
|
|
127
|
+
|
|
128
|
+
if not rule.detection:
|
|
129
|
+
findings.append(Finding(
|
|
130
|
+
Severity.ERROR, "SIGMA002",
|
|
131
|
+
"Missing detection block",
|
|
132
|
+
rule_name=rule.title, file=rule.source_file,
|
|
133
|
+
))
|
|
134
|
+
|
|
135
|
+
if "condition" not in rule.detection:
|
|
136
|
+
findings.append(Finding(
|
|
137
|
+
Severity.ERROR, "SIGMA003",
|
|
138
|
+
"detection block has no condition field",
|
|
139
|
+
rule_name=rule.title, file=rule.source_file,
|
|
140
|
+
))
|
|
141
|
+
|
|
142
|
+
if rule.level not in (None, "informational", "low", "medium", "high", "critical"):
|
|
143
|
+
findings.append(Finding(
|
|
144
|
+
Severity.WARNING, "SIGMA004",
|
|
145
|
+
f"Unknown severity level: '{rule.level}'",
|
|
146
|
+
rule_name=rule.title, file=rule.source_file,
|
|
147
|
+
))
|
|
148
|
+
|
|
149
|
+
if not rule.id:
|
|
150
|
+
findings.append(Finding(
|
|
151
|
+
Severity.WARNING if self.strict else Severity.INFO,
|
|
152
|
+
"SIGMA005",
|
|
153
|
+
"Rule has no UUID id field",
|
|
154
|
+
rule_name=rule.title, file=rule.source_file,
|
|
155
|
+
))
|
|
156
|
+
|
|
157
|
+
return findings
|
|
158
|
+
|
|
159
|
+
# ------------------------------------------------------------------ #
|
|
160
|
+
# Suricata
|
|
161
|
+
# ------------------------------------------------------------------ #
|
|
162
|
+
|
|
163
|
+
def validate_suricata(self, rule: SuricataRule) -> List[Finding]:
|
|
164
|
+
findings: List[Finding] = []
|
|
165
|
+
|
|
166
|
+
if not rule.sid:
|
|
167
|
+
findings.append(Finding(
|
|
168
|
+
Severity.ERROR, "SURI001",
|
|
169
|
+
"Rule has no sid option",
|
|
170
|
+
file=rule.source_file, line=rule.line_number,
|
|
171
|
+
))
|
|
172
|
+
|
|
173
|
+
if not rule.msg:
|
|
174
|
+
findings.append(Finding(
|
|
175
|
+
Severity.WARNING, "SURI002",
|
|
176
|
+
"Rule has no msg option",
|
|
177
|
+
file=rule.source_file, line=rule.line_number,
|
|
178
|
+
))
|
|
179
|
+
|
|
180
|
+
if "rev" not in rule.options:
|
|
181
|
+
findings.append(Finding(
|
|
182
|
+
Severity.INFO, "SURI003",
|
|
183
|
+
"Rule has no rev option",
|
|
184
|
+
file=rule.source_file, line=rule.line_number,
|
|
185
|
+
))
|
|
186
|
+
|
|
187
|
+
return findings
|
|
188
|
+
|
|
189
|
+
# ------------------------------------------------------------------ #
|
|
190
|
+
# Batch helpers
|
|
191
|
+
# ------------------------------------------------------------------ #
|
|
192
|
+
|
|
193
|
+
def validate_batch(
|
|
194
|
+
self,
|
|
195
|
+
rules: Iterable[Union[YaraRule, SigmaRule, SuricataRule]],
|
|
196
|
+
) -> ValidationResult:
|
|
197
|
+
result = ValidationResult()
|
|
198
|
+
for rule in rules:
|
|
199
|
+
if isinstance(rule, YaraRule):
|
|
200
|
+
findings = self.validate_yara(rule)
|
|
201
|
+
elif isinstance(rule, SigmaRule):
|
|
202
|
+
findings = self.validate_sigma(rule)
|
|
203
|
+
elif isinstance(rule, SuricataRule):
|
|
204
|
+
findings = self.validate_suricata(rule)
|
|
205
|
+
else:
|
|
206
|
+
continue
|
|
207
|
+
|
|
208
|
+
if any(f.severity == Severity.ERROR for f in findings):
|
|
209
|
+
result.error_count += 1
|
|
210
|
+
else:
|
|
211
|
+
result.valid_count += 1
|
|
212
|
+
for f in findings:
|
|
213
|
+
result.add(f)
|
|
214
|
+
|
|
215
|
+
return result
|
|
216
|
+
|
|
217
|
+
def validate_path(self, path: Path | str, fmt: str = "auto") -> ValidationResult:
|
|
218
|
+
"""Validate rules at a file or directory path."""
|
|
219
|
+
path = Path(path)
|
|
220
|
+
result = ValidationResult()
|
|
221
|
+
|
|
222
|
+
if fmt == "auto":
|
|
223
|
+
fmt = _detect_format(path)
|
|
224
|
+
|
|
225
|
+
if fmt == "yara":
|
|
226
|
+
parser = YaraParser()
|
|
227
|
+
try:
|
|
228
|
+
rules = list(parser.parse_directory(path) if path.is_dir() else parser.parse_file(path))
|
|
229
|
+
except YaraParseError as e:
|
|
230
|
+
result.add(Finding(Severity.ERROR, "PARSE001", str(e), file=path))
|
|
231
|
+
return result
|
|
232
|
+
for rule in rules:
|
|
233
|
+
for f in self.validate_yara(rule):
|
|
234
|
+
result.add(f)
|
|
235
|
+
if not any(f.severity == Severity.ERROR for f in self.validate_yara(rule)):
|
|
236
|
+
result.valid_count += 1
|
|
237
|
+
else:
|
|
238
|
+
result.error_count += 1
|
|
239
|
+
|
|
240
|
+
elif fmt == "sigma":
|
|
241
|
+
parser = SigmaParser()
|
|
242
|
+
try:
|
|
243
|
+
rules = list(parser.parse_directory(path) if path.is_dir() else [parser.parse_file(path)])
|
|
244
|
+
except SigmaParseError as e:
|
|
245
|
+
result.add(Finding(Severity.ERROR, "PARSE001", str(e), file=path))
|
|
246
|
+
return result
|
|
247
|
+
result = self.validate_batch(rules)
|
|
248
|
+
|
|
249
|
+
elif fmt == "suricata":
|
|
250
|
+
parser = SuricataParser()
|
|
251
|
+
try:
|
|
252
|
+
rules = list(parser.parse_directory(path) if path.is_dir() else parser.parse_file(path))
|
|
253
|
+
except SuricataParseError as e:
|
|
254
|
+
result.add(Finding(Severity.ERROR, "PARSE001", str(e), file=path))
|
|
255
|
+
return result
|
|
256
|
+
result = self.validate_batch(rules)
|
|
257
|
+
|
|
258
|
+
return result
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _detect_format(path: Path) -> str:
|
|
262
|
+
if path.is_dir():
|
|
263
|
+
# Guess by majority extension
|
|
264
|
+
yara = sum(1 for _ in path.glob("**/*.ya*"))
|
|
265
|
+
sigma = sum(1 for _ in path.glob("**/*.yml")) + sum(1 for _ in path.glob("**/*.yaml"))
|
|
266
|
+
suricata = sum(1 for _ in path.glob("**/*.rules"))
|
|
267
|
+
return max((("yara", yara), ("sigma", sigma), ("suricata", suricata)), key=lambda x: x[1])[0]
|
|
268
|
+
|
|
269
|
+
suffix = path.suffix.lower()
|
|
270
|
+
if suffix in (".yar", ".yara"):
|
|
271
|
+
return "yara"
|
|
272
|
+
if suffix in (".yml", ".yaml"):
|
|
273
|
+
return "sigma"
|
|
274
|
+
if suffix == ".rules":
|
|
275
|
+
return "suricata"
|
|
276
|
+
return "yara"
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: secrulekit
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: Parse, validate, convert, and manage YARA, Sigma, and Suricata security detection rules
|
|
5
|
+
Author-email: "Cherno.x" <long544844@gmail.com>
|
|
6
|
+
Maintainer-email: "Cherno.x" <long544844@gmail.com>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2026 Cherno.x
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
29
|
+
Project-URL: Homepage, https://github.com/lkop32788/SecRuleKit
|
|
30
|
+
Project-URL: Documentation, https://github.com/lkop32788/SecRuleKit#readme
|
|
31
|
+
Project-URL: Repository, https://github.com/lkop32788/SecRuleKit
|
|
32
|
+
Project-URL: Issues, https://github.com/lkop32788/SecRuleKit/issues
|
|
33
|
+
Project-URL: Changelog, https://github.com/lkop32788/SecRuleKit/blob/main/CHANGELOG.md
|
|
34
|
+
Project-URL: Security Policy, https://github.com/lkop32788/SecRuleKit/blob/main/SECURITY.md
|
|
35
|
+
Keywords: yara,sigma,suricata,detection-rules,security,siem,threat-hunting
|
|
36
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
37
|
+
Classifier: Intended Audience :: Information Technology
|
|
38
|
+
Classifier: Intended Audience :: System Administrators
|
|
39
|
+
Classifier: Topic :: Security
|
|
40
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
41
|
+
Classifier: Programming Language :: Python :: 3
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
45
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
46
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
47
|
+
Classifier: Operating System :: OS Independent
|
|
48
|
+
Classifier: Environment :: Console
|
|
49
|
+
Requires-Python: >=3.8
|
|
50
|
+
Description-Content-Type: text/markdown
|
|
51
|
+
License-File: LICENSE
|
|
52
|
+
Requires-Dist: click>=8.0
|
|
53
|
+
Requires-Dist: pyyaml>=6.0
|
|
54
|
+
Requires-Dist: rich>=13.0
|
|
55
|
+
Requires-Dist: typing-extensions>=4.0; python_version < "3.10"
|
|
56
|
+
Provides-Extra: dev
|
|
57
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
58
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
59
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
60
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
61
|
+
Requires-Dist: types-PyYAML; extra == "dev"
|
|
62
|
+
Dynamic: license-file
|
|
63
|
+
|
|
64
|
+
# SecRuleKit
|
|
65
|
+
|
|
66
|
+
[](https://badge.fury.io/py/secrulekit)
|
|
67
|
+
[](https://www.python.org/downloads/)
|
|
68
|
+
[](https://opensource.org/licenses/MIT)
|
|
69
|
+
[](https://github.com/lkop32788/SecRuleKit/actions/workflows/ci.yml)
|
|
70
|
+
|
|
71
|
+
A Python toolkit for parsing, validating, converting, and managing security detection rules — supporting **YARA**, **Sigma**, and **Suricata/Snort** formats.
|
|
72
|
+
|
|
73
|
+
Security teams and researchers rely on SecRuleKit to validate rule syntax before deployment, convert rules across platforms, deduplicate rule libraries, and generate audit-ready reports.
|
|
74
|
+
|
|
75
|
+
## Features
|
|
76
|
+
|
|
77
|
+
- **Multi-format support** — Parse and validate YARA, Sigma, and Suricata rules from files, directories, or strings
|
|
78
|
+
- **Format conversion** — Convert Sigma rules to YARA skeleton, Suricata, ElasticSearch Query DSL, and Splunk SPL
|
|
79
|
+
- **Rule deduplication** — Fingerprint and detect duplicate/near-duplicate rules across large rule sets
|
|
80
|
+
- **Metadata enrichment** — Tag rules with MITRE ATT&CK tactics/techniques, severity, and platform
|
|
81
|
+
- **Batch reporting** — Export validation results as JSON, CSV, or HTML audit reports
|
|
82
|
+
- **CLI interface** — Full-featured command-line tool for CI/CD pipeline integration
|
|
83
|
+
- **Python API** — Clean library API for embedding in security tooling
|
|
84
|
+
|
|
85
|
+
## Installation
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pip install secrulekit
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Or install from source:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
git clone https://github.com/lkop32788/SecRuleKit.git
|
|
95
|
+
cd secrulekit
|
|
96
|
+
pip install -e ".[dev]"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Quick Start
|
|
100
|
+
|
|
101
|
+
### CLI
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# Validate a directory of YARA rules
|
|
105
|
+
secrulekit validate --format yara ./rules/yara/
|
|
106
|
+
|
|
107
|
+
# Validate Sigma rules and output JSON report
|
|
108
|
+
secrulekit validate --format sigma ./rules/sigma/ --report report.json
|
|
109
|
+
|
|
110
|
+
# Convert Sigma rule to Splunk SPL
|
|
111
|
+
secrulekit convert --to splunk ./rules/sigma/lateral_movement.yml
|
|
112
|
+
|
|
113
|
+
# Deduplicate a large rule set
|
|
114
|
+
secrulekit dedup --format yara ./rules/ --output ./rules_deduped/
|
|
115
|
+
|
|
116
|
+
# Show rule metadata and ATT&CK mapping
|
|
117
|
+
secrulekit info ./rules/sigma/t1059_powershell.yml
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Python API
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from secrulekit import YaraParser, SigmaParser, RuleValidator, RuleConverter
|
|
124
|
+
|
|
125
|
+
# Parse and validate YARA rules
|
|
126
|
+
parser = YaraParser()
|
|
127
|
+
rules = parser.parse_directory("./rules/yara/")
|
|
128
|
+
|
|
129
|
+
validator = RuleValidator()
|
|
130
|
+
results = validator.validate_batch(rules)
|
|
131
|
+
print(f"Valid: {results.valid_count}, Errors: {results.error_count}")
|
|
132
|
+
|
|
133
|
+
# Convert Sigma to Splunk
|
|
134
|
+
converter = RuleConverter()
|
|
135
|
+
sigma = SigmaParser().parse_file("./rules/sigma/t1059.yml")
|
|
136
|
+
splunk_query = converter.to_splunk(sigma)
|
|
137
|
+
print(splunk_query)
|
|
138
|
+
|
|
139
|
+
# Generate HTML report
|
|
140
|
+
from secrulekit.reporters import HtmlReporter
|
|
141
|
+
HtmlReporter().generate(results, output="audit_report.html")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Supported Rule Formats
|
|
145
|
+
|
|
146
|
+
| Format | Parse | Validate | Convert From | Convert To |
|
|
147
|
+
|------------|-------|----------|--------------|------------|
|
|
148
|
+
| YARA | ✅ | ✅ | — | — |
|
|
149
|
+
| Sigma | ✅ | ✅ | ✅ | ✅ |
|
|
150
|
+
| Suricata | ✅ | ✅ | — | ✅ |
|
|
151
|
+
| Snort | ✅ | ✅ | — | — |
|
|
152
|
+
|
|
153
|
+
## Sigma Conversion Targets
|
|
154
|
+
|
|
155
|
+
| Target | Flag |
|
|
156
|
+
|---------------------|---------------|
|
|
157
|
+
| Splunk SPL | `--to splunk` |
|
|
158
|
+
| ElasticSearch Query | `--to elastic`|
|
|
159
|
+
| YARA-L | `--to yaral` |
|
|
160
|
+
| QRadar AQL | `--to qradar` |
|
|
161
|
+
| Suricata | `--to suricata`|
|
|
162
|
+
|
|
163
|
+
## CLI Reference
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
Usage: secrulekit [OPTIONS] COMMAND [ARGS]...
|
|
167
|
+
|
|
168
|
+
Commands:
|
|
169
|
+
validate Validate rule files for syntax and semantic errors
|
|
170
|
+
convert Convert rules between formats
|
|
171
|
+
dedup Find and remove duplicate rules
|
|
172
|
+
info Display rule metadata and ATT&CK mapping
|
|
173
|
+
report Generate audit reports from cached results
|
|
174
|
+
stats Show statistics for a rule collection
|
|
175
|
+
|
|
176
|
+
Options:
|
|
177
|
+
--version Show version
|
|
178
|
+
--help Show help
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### `secrulekit validate`
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
Options:
|
|
185
|
+
-f, --format [yara|sigma|suricata|snort|auto]
|
|
186
|
+
Rule format (default: auto-detect)
|
|
187
|
+
-r, --recursive Recurse into subdirectories
|
|
188
|
+
--strict Treat warnings as errors
|
|
189
|
+
--report FILE Write JSON/HTML report to FILE
|
|
190
|
+
--no-color Disable colored output
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### `secrulekit convert`
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
Options:
|
|
197
|
+
--to [splunk|elastic|yaral|qradar|suricata]
|
|
198
|
+
Target format (required)
|
|
199
|
+
-o, --output FILE Write output to file (default: stdout)
|
|
200
|
+
--batch Convert all .yml files in directory
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Project Structure
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
secrulekit/
|
|
207
|
+
├── secrulekit/
|
|
208
|
+
│ ├── parsers/
|
|
209
|
+
│ │ ├── yara_parser.py # YARA rule parser
|
|
210
|
+
│ │ ├── sigma_parser.py # Sigma rule parser
|
|
211
|
+
│ │ └── suricata_parser.py # Suricata/Snort parser
|
|
212
|
+
│ ├── validators/
|
|
213
|
+
│ │ └── rule_validator.py # Multi-format validator
|
|
214
|
+
│ ├── converters/
|
|
215
|
+
│ │ └── converter.py # Format conversion engine
|
|
216
|
+
│ ├── reporters/
|
|
217
|
+
│ │ ├── json_reporter.py # JSON output
|
|
218
|
+
│ │ └── html_reporter.py # HTML audit report
|
|
219
|
+
│ ├── utils/
|
|
220
|
+
│ │ └── helpers.py # Fingerprinting, dedup, ATT&CK mapping
|
|
221
|
+
│ └── cli.py # CLI entry point
|
|
222
|
+
├── tests/
|
|
223
|
+
├── rules/ # Example rule sets
|
|
224
|
+
│ ├── yara/
|
|
225
|
+
│ └── sigma/
|
|
226
|
+
└── docs/
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Contributing
|
|
230
|
+
|
|
231
|
+
Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request.
|
|
232
|
+
|
|
233
|
+
## Security
|
|
234
|
+
|
|
235
|
+
To report a security vulnerability in SecRuleKit itself (e.g., rule parsing that could cause denial-of-service or arbitrary code execution via crafted rule files), please follow the process in [SECURITY.md](SECURITY.md).
|
|
236
|
+
|
|
237
|
+
**Security Maintainer:** [@lkop32788](https://github.com/lkop32788)
|
|
238
|
+
|
|
239
|
+
## License
|
|
240
|
+
|
|
241
|
+
MIT © 2026 Cherno.x — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
secrulekit/__init__.py,sha256=T2IGK_aCvsEU4lmkvlY42mfrJC0S2LdqkOs7tBgMvDc,578
|
|
2
|
+
secrulekit/cli.py,sha256=n81W4sG1UbVOA-WBm1w1CFV5ew_NyyJM3ifGpL70LNc,9393
|
|
3
|
+
secrulekit/converters/__init__.py,sha256=KctqW9cXCrI26xezL4Ref1CVFlLjTdryZYxOsZcES5A,87
|
|
4
|
+
secrulekit/converters/converter.py,sha256=ILvs6_T2Z232TDxU3VDK8-7AFDnhYrFM6H5fvtblCIE,8418
|
|
5
|
+
secrulekit/parsers/__init__.py,sha256=2htcrhdbudQKzUZty87fhaB-cNCgUx9d9hfe7iwrpSw,231
|
|
6
|
+
secrulekit/parsers/sigma_parser.py,sha256=7kpDFIBYSkr8iaIKgqGAHavAawYvJansvmDnvGSb0ZQ,4082
|
|
7
|
+
secrulekit/parsers/suricata_parser.py,sha256=2Q1pDYh4QACpa6RwqpAxS3xA2W-phf6mLNP9sr1rNXI,3997
|
|
8
|
+
secrulekit/parsers/yara_parser.py,sha256=lP0HO_XPrJCH7UFIcvw7bixaYkCDzNyxYOxdguXjWqM,4797
|
|
9
|
+
secrulekit/reporters/__init__.py,sha256=-7Q7AoUzqEOFwX025dufsNdkoaXKzUr3OJjecDoLW_0,164
|
|
10
|
+
secrulekit/reporters/html_reporter.py,sha256=ihJzR5E0tpV-pNBCOpNJVYldhKcDmZ26d5tjimpBmN8,3606
|
|
11
|
+
secrulekit/reporters/json_reporter.py,sha256=Zj8XB5cFWIZsW104RUyt2H_G8PqgDumCn7UlVq0FMb8,1167
|
|
12
|
+
secrulekit/utils/__init__.py,sha256=8h_ZTZRmlinoGJ4RMTzHpkdqdbyBKzT3XFpujbklfeU,126
|
|
13
|
+
secrulekit/utils/helpers.py,sha256=LZfBfmPeJ5f9WjqNKqWXAnRgQYWALzAIo-qy1iA5MHM,3494
|
|
14
|
+
secrulekit/validators/__init__.py,sha256=nYkd_uFObF75w_di9N_qwSqJBVG3-ihUg3vdmJzjI2U,150
|
|
15
|
+
secrulekit/validators/rule_validator.py,sha256=lxouAJp_QbjvTRVi71DAbExARPvI7z-FQ_Ktn1THSkk,9717
|
|
16
|
+
secrulekit-1.2.0.dist-info/licenses/LICENSE,sha256=ouHqLPmSmwMlmSc7tWOzeRLgz3jLcz1N3Pcys9ai-bI,1065
|
|
17
|
+
secrulekit-1.2.0.dist-info/METADATA,sha256=9CtuxZlQlrvEJMj0ht_lhsd3MnWFy-nTvg-oBWoVaz4,9458
|
|
18
|
+
secrulekit-1.2.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
19
|
+
secrulekit-1.2.0.dist-info/entry_points.txt,sha256=KsJgDvejtkcaRM8lFF49LK8uQN1tbMS-DwCUtjmAOw8,51
|
|
20
|
+
secrulekit-1.2.0.dist-info/top_level.txt,sha256=MSbt3LaSgXXW8bilgn7O0Clu3Rl-dKEwNfGzdG-uKYo,11
|
|
21
|
+
secrulekit-1.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cherno.x
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
secrulekit
|