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.
@@ -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
+ [![PyPI version](https://badge.fury.io/py/secrulekit.svg)](https://badge.fury.io/py/secrulekit)
67
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
68
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
69
+ [![CI](https://github.com/lkop32788/SecRuleKit/actions/workflows/ci.yml/badge.svg)](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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ secrulekit = secrulekit.cli:main
@@ -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