knowledge-rag 3.3.0__tar.gz → 3.3.2__tar.gz
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.
- {knowledge_rag-3.3.0 → knowledge_rag-3.3.2}/PKG-INFO +25 -8
- {knowledge_rag-3.3.0 → knowledge_rag-3.3.2}/README.md +24 -7
- {knowledge_rag-3.3.0 → knowledge_rag-3.3.2}/mcp_server/__init__.py +1 -1
- knowledge_rag-3.3.2/mcp_server/config.py +487 -0
- {knowledge_rag-3.3.0 → knowledge_rag-3.3.2}/mcp_server/server.py +52 -4
- {knowledge_rag-3.3.0 → knowledge_rag-3.3.2}/pyproject.toml +8 -1
- knowledge_rag-3.3.0/mcp_server/config.py +0 -278
- {knowledge_rag-3.3.0 → knowledge_rag-3.3.2}/.gitignore +0 -0
- {knowledge_rag-3.3.0 → knowledge_rag-3.3.2}/LICENSE +0 -0
- {knowledge_rag-3.3.0 → knowledge_rag-3.3.2}/config.example.yaml +0 -0
- {knowledge_rag-3.3.0 → knowledge_rag-3.3.2}/documents/examples/sample-document.md +0 -0
- {knowledge_rag-3.3.0 → knowledge_rag-3.3.2}/mcp_server/ingestion.py +0 -0
- {knowledge_rag-3.3.0 → knowledge_rag-3.3.2}/presets/cybersecurity.yaml +0 -0
- {knowledge_rag-3.3.0 → knowledge_rag-3.3.2}/presets/developer.yaml +0 -0
- {knowledge_rag-3.3.0 → knowledge_rag-3.3.2}/presets/general.yaml +0 -0
- {knowledge_rag-3.3.0 → knowledge_rag-3.3.2}/presets/research.yaml +0 -0
- {knowledge_rag-3.3.0 → knowledge_rag-3.3.2}/requirements.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: knowledge-rag
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.2
|
|
4
4
|
Summary: Local RAG System for Claude Code — Hybrid search + Cross-encoder Reranking + 12 MCP Tools. Zero external servers.
|
|
5
5
|
Project-URL: Homepage, https://github.com/lyonzin/knowledge-rag
|
|
6
6
|
Project-URL: Repository, https://github.com/lyonzin/knowledge-rag
|
|
@@ -38,7 +38,7 @@ Description-Content-Type: text/markdown
|
|
|
38
38
|
|
|
39
39
|
<div align="center">
|
|
40
40
|
|
|
41
|
-

|
|
42
42
|

|
|
43
43
|

|
|
44
44
|

|
|
@@ -408,21 +408,23 @@ flowchart LR
|
|
|
408
408
|
|
|
409
409
|
### Quick Start (3 steps)
|
|
410
410
|
|
|
411
|
-
**Step 1:
|
|
411
|
+
**Step 1: Install**
|
|
412
412
|
|
|
413
413
|
```bash
|
|
414
|
-
#
|
|
414
|
+
# Option A: pip install (recommended)
|
|
415
|
+
pip install knowledge-rag
|
|
416
|
+
knowledge-rag init # Exports config template, presets, creates documents/
|
|
417
|
+
|
|
418
|
+
# Option B: Clone from source
|
|
415
419
|
git clone https://github.com/lyonzin/knowledge-rag.git ~/knowledge-rag
|
|
416
420
|
cd ~/knowledge-rag
|
|
417
|
-
|
|
418
|
-
# Create virtual environment and install
|
|
419
421
|
python3 -m venv venv
|
|
420
422
|
source venv/bin/activate # Linux/macOS
|
|
421
423
|
# .\venv\Scripts\activate # Windows
|
|
422
424
|
pip install -r requirements.txt
|
|
423
425
|
```
|
|
424
426
|
|
|
425
|
-
> **Windows users**:
|
|
427
|
+
> **Windows users**: Use `python` instead of `python3`.
|
|
426
428
|
|
|
427
429
|
**Step 2: Configure Claude Code**
|
|
428
430
|
|
|
@@ -1161,7 +1163,7 @@ pip install rank-bm25
|
|
|
1161
1163
|
|
|
1162
1164
|
### "ModuleNotFoundError: No module named 'mcp_server'"
|
|
1163
1165
|
|
|
1164
|
-
This occurs when Claude Code doesn't set the working directory correctly. Use the `cmd /c "cd /d ... && python"` wrapper in your MCP config (see [Installation](#
|
|
1166
|
+
This occurs when Claude Code doesn't set the working directory correctly. Use the `cmd /c "cd /d ... && python"` wrapper in your MCP config (see [Installation](#installation)).
|
|
1165
1167
|
|
|
1166
1168
|
### Dimension mismatch after upgrade
|
|
1167
1169
|
|
|
@@ -1193,6 +1195,21 @@ With ~200 documents, expect ~300-500MB RAM. The embedding model (~50MB) and rera
|
|
|
1193
1195
|
|
|
1194
1196
|
## Changelog
|
|
1195
1197
|
|
|
1198
|
+
### v3.3.2 (2026-04-06)
|
|
1199
|
+
|
|
1200
|
+
- **FIX**: Full type validation on all YAML config values — wrong types warn and fall back to defaults
|
|
1201
|
+
- **FIX**: Bounds validation for chunk_size, chunk_overlap, default_results, max_results, embedding_dim
|
|
1202
|
+
- **FIX**: `keyword_routes` with string values instead of lists detected and removed with warning
|
|
1203
|
+
- **FIX**: `reranker_enabled: "yes"` (string) corrected to boolean with warning
|
|
1204
|
+
- **FIX**: Synced version strings across all source files
|
|
1205
|
+
- **FIX**: Broken README anchor, duplicate keyword, error handling in `knowledge-rag init`
|
|
1206
|
+
|
|
1207
|
+
### v3.3.1 (2026-04-06)
|
|
1208
|
+
|
|
1209
|
+
- **FIX**: YAML null values (`category_mappings:` without value) no longer crash the server — falls back to defaults
|
|
1210
|
+
- **FIX**: Presets and config template now bundled in pip wheel (`knowledge-rag init` exports them)
|
|
1211
|
+
- **NEW**: `knowledge-rag init` CLI command — exports config template, presets, and creates documents/ in current directory
|
|
1212
|
+
|
|
1196
1213
|
### v3.3.0 (2026-04-05)
|
|
1197
1214
|
|
|
1198
1215
|
- **NEW**: YAML configuration system — fully customizable via `config.yaml`
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|

|
|
8
8
|

|
|
@@ -372,21 +372,23 @@ flowchart LR
|
|
|
372
372
|
|
|
373
373
|
### Quick Start (3 steps)
|
|
374
374
|
|
|
375
|
-
**Step 1:
|
|
375
|
+
**Step 1: Install**
|
|
376
376
|
|
|
377
377
|
```bash
|
|
378
|
-
#
|
|
378
|
+
# Option A: pip install (recommended)
|
|
379
|
+
pip install knowledge-rag
|
|
380
|
+
knowledge-rag init # Exports config template, presets, creates documents/
|
|
381
|
+
|
|
382
|
+
# Option B: Clone from source
|
|
379
383
|
git clone https://github.com/lyonzin/knowledge-rag.git ~/knowledge-rag
|
|
380
384
|
cd ~/knowledge-rag
|
|
381
|
-
|
|
382
|
-
# Create virtual environment and install
|
|
383
385
|
python3 -m venv venv
|
|
384
386
|
source venv/bin/activate # Linux/macOS
|
|
385
387
|
# .\venv\Scripts\activate # Windows
|
|
386
388
|
pip install -r requirements.txt
|
|
387
389
|
```
|
|
388
390
|
|
|
389
|
-
> **Windows users**:
|
|
391
|
+
> **Windows users**: Use `python` instead of `python3`.
|
|
390
392
|
|
|
391
393
|
**Step 2: Configure Claude Code**
|
|
392
394
|
|
|
@@ -1125,7 +1127,7 @@ pip install rank-bm25
|
|
|
1125
1127
|
|
|
1126
1128
|
### "ModuleNotFoundError: No module named 'mcp_server'"
|
|
1127
1129
|
|
|
1128
|
-
This occurs when Claude Code doesn't set the working directory correctly. Use the `cmd /c "cd /d ... && python"` wrapper in your MCP config (see [Installation](#
|
|
1130
|
+
This occurs when Claude Code doesn't set the working directory correctly. Use the `cmd /c "cd /d ... && python"` wrapper in your MCP config (see [Installation](#installation)).
|
|
1129
1131
|
|
|
1130
1132
|
### Dimension mismatch after upgrade
|
|
1131
1133
|
|
|
@@ -1157,6 +1159,21 @@ With ~200 documents, expect ~300-500MB RAM. The embedding model (~50MB) and rera
|
|
|
1157
1159
|
|
|
1158
1160
|
## Changelog
|
|
1159
1161
|
|
|
1162
|
+
### v3.3.2 (2026-04-06)
|
|
1163
|
+
|
|
1164
|
+
- **FIX**: Full type validation on all YAML config values — wrong types warn and fall back to defaults
|
|
1165
|
+
- **FIX**: Bounds validation for chunk_size, chunk_overlap, default_results, max_results, embedding_dim
|
|
1166
|
+
- **FIX**: `keyword_routes` with string values instead of lists detected and removed with warning
|
|
1167
|
+
- **FIX**: `reranker_enabled: "yes"` (string) corrected to boolean with warning
|
|
1168
|
+
- **FIX**: Synced version strings across all source files
|
|
1169
|
+
- **FIX**: Broken README anchor, duplicate keyword, error handling in `knowledge-rag init`
|
|
1170
|
+
|
|
1171
|
+
### v3.3.1 (2026-04-06)
|
|
1172
|
+
|
|
1173
|
+
- **FIX**: YAML null values (`category_mappings:` without value) no longer crash the server — falls back to defaults
|
|
1174
|
+
- **FIX**: Presets and config template now bundled in pip wheel (`knowledge-rag init` exports them)
|
|
1175
|
+
- **NEW**: `knowledge-rag init` CLI command — exports config template, presets, and creates documents/ in current directory
|
|
1176
|
+
|
|
1160
1177
|
### v3.3.0 (2026-04-05)
|
|
1161
1178
|
|
|
1162
1179
|
- **NEW**: YAML configuration system — fully customizable via `config.yaml`
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
"""Configuration for Knowledge RAG System v3.3.2 — YAML-configurable"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, List
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
# ============================================================================
|
|
11
|
+
# BASE DIRECTORY RESOLUTION
|
|
12
|
+
# ============================================================================
|
|
13
|
+
# Priority: 1. KNOWLEDGE_RAG_DIR env var 2. Source checkout 3. CWD
|
|
14
|
+
|
|
15
|
+
_source_dir = Path(__file__).parent.parent
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_SUPPORTED_SUFFIXES = frozenset([".md", ".txt", ".pdf", ".py", ".json", ".docx", ".xlsx", ".pptx", ".csv"])
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _has_documents(path: Path) -> bool:
|
|
22
|
+
"""Check if path has a documents/ dir with actual supported files (follows symlinks)."""
|
|
23
|
+
docs_dir = path / "documents"
|
|
24
|
+
if not docs_dir.exists():
|
|
25
|
+
return False
|
|
26
|
+
for root, _, files in os.walk(docs_dir, followlinks=True):
|
|
27
|
+
for f in files:
|
|
28
|
+
if Path(f).suffix.lower() in _SUPPORTED_SUFFIXES:
|
|
29
|
+
return True
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if os.environ.get("KNOWLEDGE_RAG_DIR"):
|
|
34
|
+
BASE_DIR = Path(os.environ["KNOWLEDGE_RAG_DIR"])
|
|
35
|
+
elif _has_documents(_source_dir):
|
|
36
|
+
BASE_DIR = _source_dir
|
|
37
|
+
elif _has_documents(Path.cwd()):
|
|
38
|
+
BASE_DIR = Path.cwd()
|
|
39
|
+
else:
|
|
40
|
+
BASE_DIR = Path.cwd()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ============================================================================
|
|
44
|
+
# YAML CONFIG LOADER
|
|
45
|
+
# ============================================================================
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _load_yaml_config() -> dict:
|
|
49
|
+
"""Load config.yaml from BASE_DIR if it exists, otherwise return empty dict."""
|
|
50
|
+
config_path = BASE_DIR / "config.yaml"
|
|
51
|
+
if not config_path.exists():
|
|
52
|
+
return {}
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
56
|
+
data = yaml.safe_load(f)
|
|
57
|
+
if not isinstance(data, dict):
|
|
58
|
+
print("[WARN] config.yaml is not a valid mapping, ignoring")
|
|
59
|
+
return {}
|
|
60
|
+
print(f"[INFO] Loaded config from {config_path}")
|
|
61
|
+
return data
|
|
62
|
+
except yaml.YAMLError as e:
|
|
63
|
+
print(f"[WARN] Failed to parse config.yaml: {e} — using defaults")
|
|
64
|
+
return {}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
_yaml = _load_yaml_config()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _get(section: str, key: str, default):
|
|
71
|
+
"""Get a value from the YAML config with section.key path, falling back to default."""
|
|
72
|
+
s = _yaml.get(section, {})
|
|
73
|
+
if not isinstance(s, dict):
|
|
74
|
+
return default
|
|
75
|
+
val = s.get(key)
|
|
76
|
+
if val is None:
|
|
77
|
+
return default
|
|
78
|
+
# Skip type check when default is None (caller handles validation)
|
|
79
|
+
if default is None:
|
|
80
|
+
return val
|
|
81
|
+
# YAML parses "yes"/"no" as bool, but explicit string "yes" stays str
|
|
82
|
+
if not isinstance(val, type(default)):
|
|
83
|
+
print(
|
|
84
|
+
f"[WARN] config.yaml: {section}.{key} has wrong type "
|
|
85
|
+
f"(expected {type(default).__name__}, got {type(val).__name__}), using default"
|
|
86
|
+
)
|
|
87
|
+
return default
|
|
88
|
+
return val
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _get_top(key: str, default):
|
|
92
|
+
"""Get a top-level value from YAML, falling back to default if missing or None."""
|
|
93
|
+
val = _yaml.get(key)
|
|
94
|
+
if val is None:
|
|
95
|
+
return default
|
|
96
|
+
if not isinstance(val, dict):
|
|
97
|
+
print(f"[WARN] config.yaml: {key} has wrong type (expected dict, got {type(val).__name__}), using default")
|
|
98
|
+
return default
|
|
99
|
+
return val
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ============================================================================
|
|
103
|
+
# DEFAULTS (used when no config.yaml or field is omitted)
|
|
104
|
+
# ============================================================================
|
|
105
|
+
|
|
106
|
+
_DEFAULT_CATEGORY_MAPPINGS = {
|
|
107
|
+
"security/redteam": "redteam",
|
|
108
|
+
"security/blueteam": "blueteam",
|
|
109
|
+
"security/ctf": "ctf",
|
|
110
|
+
"security": "security",
|
|
111
|
+
"aar": "aar",
|
|
112
|
+
"logscale": "logscale",
|
|
113
|
+
"development": "development",
|
|
114
|
+
"general": "general",
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
_DEFAULT_KEYWORD_ROUTES = {
|
|
118
|
+
"logscale": [
|
|
119
|
+
"logscale",
|
|
120
|
+
"lql",
|
|
121
|
+
"cql",
|
|
122
|
+
"humio",
|
|
123
|
+
"crowdstrike query",
|
|
124
|
+
"formattime",
|
|
125
|
+
"groupby",
|
|
126
|
+
"base64decode",
|
|
127
|
+
"case{}",
|
|
128
|
+
"regex",
|
|
129
|
+
],
|
|
130
|
+
"redteam": [
|
|
131
|
+
"pentest",
|
|
132
|
+
"exploit",
|
|
133
|
+
"payload",
|
|
134
|
+
"reverse shell",
|
|
135
|
+
"privilege escalation",
|
|
136
|
+
"lateral movement",
|
|
137
|
+
"c2",
|
|
138
|
+
"beacon",
|
|
139
|
+
"cobalt strike",
|
|
140
|
+
"metasploit",
|
|
141
|
+
"gtfobins",
|
|
142
|
+
"lolbas",
|
|
143
|
+
"lolbin",
|
|
144
|
+
"suid",
|
|
145
|
+
"sudo",
|
|
146
|
+
"byovd",
|
|
147
|
+
"lol driver",
|
|
148
|
+
"lolad",
|
|
149
|
+
"lolapps",
|
|
150
|
+
"hacktricks",
|
|
151
|
+
"privesc",
|
|
152
|
+
"kerberoast",
|
|
153
|
+
"dcsync",
|
|
154
|
+
"golden ticket",
|
|
155
|
+
"pass-the-hash",
|
|
156
|
+
"bloodhound",
|
|
157
|
+
"mimikatz",
|
|
158
|
+
"rubeus",
|
|
159
|
+
"certipy",
|
|
160
|
+
"adcs",
|
|
161
|
+
"sqli",
|
|
162
|
+
"xss",
|
|
163
|
+
"ssti",
|
|
164
|
+
"ssrf",
|
|
165
|
+
"lfi",
|
|
166
|
+
"rfi",
|
|
167
|
+
"xxe",
|
|
168
|
+
"deserialization",
|
|
169
|
+
"ysoserial",
|
|
170
|
+
"upload bypass",
|
|
171
|
+
"web shell",
|
|
172
|
+
"hash cracking",
|
|
173
|
+
"hashcat",
|
|
174
|
+
"waf bypass",
|
|
175
|
+
"amsi bypass",
|
|
176
|
+
"uac bypass",
|
|
177
|
+
"potato",
|
|
178
|
+
"searchsploit",
|
|
179
|
+
"exploit-db",
|
|
180
|
+
"cve",
|
|
181
|
+
],
|
|
182
|
+
"blueteam": [
|
|
183
|
+
"detection",
|
|
184
|
+
"sigma",
|
|
185
|
+
"yara",
|
|
186
|
+
"ioc",
|
|
187
|
+
"threat hunting",
|
|
188
|
+
"incident response",
|
|
189
|
+
"forensics",
|
|
190
|
+
"malware analysis",
|
|
191
|
+
],
|
|
192
|
+
"ctf": [
|
|
193
|
+
"ctf",
|
|
194
|
+
"flag",
|
|
195
|
+
"hackthebox",
|
|
196
|
+
"htb",
|
|
197
|
+
"tryhackme",
|
|
198
|
+
"picoctf",
|
|
199
|
+
"writeup",
|
|
200
|
+
"challenge",
|
|
201
|
+
],
|
|
202
|
+
"development": [
|
|
203
|
+
"python",
|
|
204
|
+
"typescript",
|
|
205
|
+
"javascript",
|
|
206
|
+
"api",
|
|
207
|
+
"fastapi",
|
|
208
|
+
"django",
|
|
209
|
+
"react",
|
|
210
|
+
"nodejs",
|
|
211
|
+
],
|
|
212
|
+
"security": [
|
|
213
|
+
"anti-bot",
|
|
214
|
+
"antibot",
|
|
215
|
+
"js challenge",
|
|
216
|
+
"javascript challenge",
|
|
217
|
+
"cdp detection",
|
|
218
|
+
"runtime.enable",
|
|
219
|
+
"puppeteer",
|
|
220
|
+
"playwright",
|
|
221
|
+
"selenium",
|
|
222
|
+
"nodriver",
|
|
223
|
+
"stealth",
|
|
224
|
+
"undetected",
|
|
225
|
+
"ja3",
|
|
226
|
+
"ja4",
|
|
227
|
+
"tls fingerprint",
|
|
228
|
+
"fingerprinting",
|
|
229
|
+
"curl_cffi",
|
|
230
|
+
"got-scraping",
|
|
231
|
+
"impersonate",
|
|
232
|
+
"http/2 settings",
|
|
233
|
+
"browser fingerprint",
|
|
234
|
+
"canvas fingerprint",
|
|
235
|
+
"webgl fingerprint",
|
|
236
|
+
"navigator.webdriver",
|
|
237
|
+
"audio context",
|
|
238
|
+
"hardware concurrency",
|
|
239
|
+
"waf bypass",
|
|
240
|
+
"aws waf",
|
|
241
|
+
"cloudflare bypass",
|
|
242
|
+
"akamai bypass",
|
|
243
|
+
"datadome",
|
|
244
|
+
"perimeterx",
|
|
245
|
+
"imperva bypass",
|
|
246
|
+
"8kb bypass",
|
|
247
|
+
"body size limit",
|
|
248
|
+
"json sqli",
|
|
249
|
+
"behavioral",
|
|
250
|
+
"mouse movement",
|
|
251
|
+
"ghost-cursor",
|
|
252
|
+
"humanized",
|
|
253
|
+
"flaresolverr",
|
|
254
|
+
"turnstile",
|
|
255
|
+
"rebrowser",
|
|
256
|
+
"botbrowser",
|
|
257
|
+
],
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
_DEFAULT_QUERY_EXPANSIONS = {
|
|
261
|
+
"sqli": ["sql injection", "sqli"],
|
|
262
|
+
"sql injection": ["sql injection", "sqli"],
|
|
263
|
+
"xss": ["cross-site scripting", "xss"],
|
|
264
|
+
"cross-site scripting": ["cross-site scripting", "xss"],
|
|
265
|
+
"ssrf": ["server-side request forgery", "ssrf"],
|
|
266
|
+
"lfi": ["local file inclusion", "lfi"],
|
|
267
|
+
"rfi": ["remote file inclusion", "rfi"],
|
|
268
|
+
"rce": ["remote code execution", "rce"],
|
|
269
|
+
"xxe": ["xml external entity", "xxe"],
|
|
270
|
+
"ssti": ["server-side template injection", "ssti"],
|
|
271
|
+
"idor": ["insecure direct object reference", "idor"],
|
|
272
|
+
"csrf": ["cross-site request forgery", "csrf"],
|
|
273
|
+
"privesc": ["privilege escalation", "privesc"],
|
|
274
|
+
"priv esc": ["privilege escalation", "privesc"],
|
|
275
|
+
"privilege escalation": ["privilege escalation", "privesc"],
|
|
276
|
+
"deserialization": ["deserialization", "deserialisation", "insecure deserialization"],
|
|
277
|
+
"pth": ["pass-the-hash", "pth"],
|
|
278
|
+
"pass-the-hash": ["pass-the-hash", "pth"],
|
|
279
|
+
"dcsync": ["dcsync", "dc sync", "domain controller sync"],
|
|
280
|
+
"kerberoast": ["kerberoasting", "kerberoast"],
|
|
281
|
+
"kerberoasting": ["kerberoasting", "kerberoast"],
|
|
282
|
+
"asrep": ["as-rep roasting", "asrep", "asreproast"],
|
|
283
|
+
"bloodhound": ["bloodhound", "sharphound"],
|
|
284
|
+
"mimikatz": ["mimikatz", "sekurlsa", "logonpasswords"],
|
|
285
|
+
"hashcat": ["hashcat", "hash cracking", "hash crack"],
|
|
286
|
+
"john": ["john the ripper", "john", "jtr"],
|
|
287
|
+
"revshell": ["reverse shell", "revshell", "rev shell"],
|
|
288
|
+
"reverse shell": ["reverse shell", "revshell"],
|
|
289
|
+
"webshell": ["web shell", "webshell"],
|
|
290
|
+
"web shell": ["web shell", "webshell"],
|
|
291
|
+
"waf": ["web application firewall", "waf"],
|
|
292
|
+
"amsi": ["antimalware scan interface", "amsi", "amsi bypass"],
|
|
293
|
+
"uac": ["user account control", "uac", "uac bypass"],
|
|
294
|
+
"potato": ["potato", "juicypotato", "sweetpotato", "godpotato", "efspotato", "printspoofer"],
|
|
295
|
+
"ntlm": ["ntlm", "net-ntlmv2", "ntlmv2"],
|
|
296
|
+
"smb": ["smb", "server message block", "samba"],
|
|
297
|
+
"ldap": ["ldap", "lightweight directory access protocol"],
|
|
298
|
+
"ad": ["active directory", "ad"],
|
|
299
|
+
"active directory": ["active directory", "ad"],
|
|
300
|
+
"defender": ["windows defender", "defender", "wdfilter"],
|
|
301
|
+
"responder": ["responder", "llmnr", "nbt-ns", "netbios"],
|
|
302
|
+
"suid": ["suid", "setuid", "set-uid"],
|
|
303
|
+
"cron": ["cron", "crontab", "cronjob", "scheduled task"],
|
|
304
|
+
"lolbin": ["lolbin", "lolbas", "living off the land"],
|
|
305
|
+
"c2": ["c2", "command and control", "command-and-control", "beacon"],
|
|
306
|
+
"sliver": ["sliver", "sliver c2"],
|
|
307
|
+
"cobalt": ["cobalt strike", "cobalt", "cs beacon"],
|
|
308
|
+
"phishing": ["phishing", "spearphishing", "social engineering"],
|
|
309
|
+
"forensics": ["forensics", "forensic", "dfir"],
|
|
310
|
+
"volatility": ["volatility", "memory forensics", "memory analysis"],
|
|
311
|
+
"steganography": ["steganography", "stego", "steghide"],
|
|
312
|
+
"stego": ["steganography", "stego", "steghide"],
|
|
313
|
+
"rbcd": ["resource-based constrained delegation", "rbcd"],
|
|
314
|
+
"dpapi": ["dpapi", "data protection api", "credential manager"],
|
|
315
|
+
"printnightmare": ["printnightmare", "cve-2021-34527", "spoolsv", "printspooler"],
|
|
316
|
+
"cve-2021-34527": ["printnightmare", "cve-2021-34527", "spoolsv"],
|
|
317
|
+
"eternalblue": ["eternalblue", "ms17-010", "smbv1"],
|
|
318
|
+
"ms17-010": ["eternalblue", "ms17-010", "smbv1"],
|
|
319
|
+
"pwnkit": ["pwnkit", "cve-2021-4034", "pkexec"],
|
|
320
|
+
"cve-2021-4034": ["pwnkit", "cve-2021-4034", "pkexec"],
|
|
321
|
+
"log4shell": ["log4shell", "cve-2021-44228", "log4j"],
|
|
322
|
+
"cve-2021-44228": ["log4shell", "cve-2021-44228", "log4j"],
|
|
323
|
+
"zerologon": ["zerologon", "cve-2020-1472", "netlogon"],
|
|
324
|
+
"cve-2020-1472": ["zerologon", "cve-2020-1472", "netlogon"],
|
|
325
|
+
"petitpotam": ["petitpotam", "cve-2021-36942", "efs", "ntlm relay"],
|
|
326
|
+
"certifried": ["certifried", "cve-2022-26923", "adcs"],
|
|
327
|
+
"nopac": ["nopac", "samaccountname", "cve-2021-42278", "cve-2021-42287"],
|
|
328
|
+
"proxylogon": ["proxylogon", "cve-2021-26855", "exchange"],
|
|
329
|
+
"proxyshell": ["proxyshell", "cve-2021-34473", "exchange"],
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
# ============================================================================
|
|
334
|
+
# CONFIG DATACLASS
|
|
335
|
+
# ============================================================================
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _resolve_path(raw, default: Path) -> Path:
|
|
339
|
+
"""Resolve a path from YAML (string) or use default (Path)."""
|
|
340
|
+
if raw is None:
|
|
341
|
+
return default
|
|
342
|
+
p = Path(raw)
|
|
343
|
+
if not p.is_absolute():
|
|
344
|
+
p = BASE_DIR / p
|
|
345
|
+
return p
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@dataclass
|
|
349
|
+
class Config:
|
|
350
|
+
"""Central configuration for the RAG system — loads from config.yaml when available."""
|
|
351
|
+
|
|
352
|
+
# Paths
|
|
353
|
+
data_dir: Path = field(default_factory=lambda: _resolve_path(_get("paths", "data_dir", None), BASE_DIR / "data"))
|
|
354
|
+
chroma_dir: Path = field(
|
|
355
|
+
default_factory=lambda: _resolve_path(_get("paths", "data_dir", None), BASE_DIR / "data") / "chroma_db"
|
|
356
|
+
)
|
|
357
|
+
documents_dir: Path = field(
|
|
358
|
+
default_factory=lambda: _resolve_path(_get("paths", "documents_dir", None), BASE_DIR / "documents")
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
# Chunking
|
|
362
|
+
chunk_size: int = field(
|
|
363
|
+
default_factory=lambda: (
|
|
364
|
+
_get("documents", "chunking", {}).get("chunk_size", 1000)
|
|
365
|
+
if isinstance(_get("documents", "chunking", {}), dict)
|
|
366
|
+
else 1000
|
|
367
|
+
)
|
|
368
|
+
)
|
|
369
|
+
chunk_overlap: int = field(
|
|
370
|
+
default_factory=lambda: (
|
|
371
|
+
_get("documents", "chunking", {}).get("chunk_overlap", 200)
|
|
372
|
+
if isinstance(_get("documents", "chunking", {}), dict)
|
|
373
|
+
else 200
|
|
374
|
+
)
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# Embeddings
|
|
378
|
+
embedding_model: str = field(
|
|
379
|
+
default_factory=lambda: (
|
|
380
|
+
_get("models", "embedding", {}).get("model", "BAAI/bge-small-en-v1.5")
|
|
381
|
+
if isinstance(_get("models", "embedding", {}), dict)
|
|
382
|
+
else "BAAI/bge-small-en-v1.5"
|
|
383
|
+
)
|
|
384
|
+
)
|
|
385
|
+
embedding_dim: int = field(
|
|
386
|
+
default_factory=lambda: (
|
|
387
|
+
_get("models", "embedding", {}).get("dimensions", 384)
|
|
388
|
+
if isinstance(_get("models", "embedding", {}), dict)
|
|
389
|
+
else 384
|
|
390
|
+
)
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
# Reranker
|
|
394
|
+
reranker_model: str = field(
|
|
395
|
+
default_factory=lambda: (
|
|
396
|
+
_get("models", "reranker", {}).get("model", "Xenova/ms-marco-MiniLM-L-6-v2")
|
|
397
|
+
if isinstance(_get("models", "reranker", {}), dict)
|
|
398
|
+
else "Xenova/ms-marco-MiniLM-L-6-v2"
|
|
399
|
+
)
|
|
400
|
+
)
|
|
401
|
+
reranker_enabled: bool = field(
|
|
402
|
+
default_factory=lambda: (
|
|
403
|
+
_get("models", "reranker", {}).get("enabled", True)
|
|
404
|
+
if isinstance(_get("models", "reranker", {}), dict)
|
|
405
|
+
else True
|
|
406
|
+
)
|
|
407
|
+
)
|
|
408
|
+
reranker_top_k_multiplier: int = field(
|
|
409
|
+
default_factory=lambda: (
|
|
410
|
+
_get("models", "reranker", {}).get("top_k_multiplier", 3)
|
|
411
|
+
if isinstance(_get("models", "reranker", {}), dict)
|
|
412
|
+
else 3
|
|
413
|
+
)
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
# ChromaDB
|
|
417
|
+
collection_name: str = field(default_factory=lambda: _get("search", "collection_name", "knowledge_base"))
|
|
418
|
+
|
|
419
|
+
# Supported formats
|
|
420
|
+
supported_formats: List[str] = field(
|
|
421
|
+
default_factory=lambda: _get(
|
|
422
|
+
"documents", "supported_formats", [".md", ".txt", ".pdf", ".py", ".json", ".docx", ".xlsx", ".pptx", ".csv"]
|
|
423
|
+
)
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
# Category mappings
|
|
427
|
+
category_mappings: Dict[str, str] = field(
|
|
428
|
+
default_factory=lambda: _get_top("category_mappings", _DEFAULT_CATEGORY_MAPPINGS)
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
# Keyword routes
|
|
432
|
+
keyword_routes: Dict[str, List[str]] = field(
|
|
433
|
+
default_factory=lambda: _get_top("keyword_routes", _DEFAULT_KEYWORD_ROUTES)
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
# Query expansions
|
|
437
|
+
query_expansions: Dict[str, List[str]] = field(
|
|
438
|
+
default_factory=lambda: _get_top("query_expansions", _DEFAULT_QUERY_EXPANSIONS)
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
# Search settings
|
|
442
|
+
default_results: int = field(default_factory=lambda: _get("search", "default_results", 5))
|
|
443
|
+
max_results: int = field(default_factory=lambda: _get("search", "max_results", 20))
|
|
444
|
+
|
|
445
|
+
def __post_init__(self):
|
|
446
|
+
"""Validate config values and ensure directories exist."""
|
|
447
|
+
# Bounds validation
|
|
448
|
+
if not isinstance(self.chunk_size, int) or self.chunk_size < 100:
|
|
449
|
+
print(f"[WARN] chunk_size={self.chunk_size} invalid, using 1000")
|
|
450
|
+
self.chunk_size = 1000
|
|
451
|
+
if not isinstance(self.chunk_overlap, int) or self.chunk_overlap < 0:
|
|
452
|
+
print(f"[WARN] chunk_overlap={self.chunk_overlap} invalid, using 200")
|
|
453
|
+
self.chunk_overlap = 200
|
|
454
|
+
if self.chunk_overlap >= self.chunk_size:
|
|
455
|
+
print(
|
|
456
|
+
f"[WARN] chunk_overlap ({self.chunk_overlap}) >= chunk_size ({self.chunk_size}), using {self.chunk_size // 5}"
|
|
457
|
+
)
|
|
458
|
+
self.chunk_overlap = self.chunk_size // 5
|
|
459
|
+
if not isinstance(self.default_results, int) or self.default_results < 1:
|
|
460
|
+
self.default_results = 5
|
|
461
|
+
if not isinstance(self.max_results, int) or self.max_results < 1:
|
|
462
|
+
self.max_results = 20
|
|
463
|
+
if not isinstance(self.embedding_dim, int) or self.embedding_dim < 1:
|
|
464
|
+
self.embedding_dim = 384
|
|
465
|
+
if not isinstance(self.reranker_enabled, bool):
|
|
466
|
+
print(f"[WARN] reranker_enabled={self.reranker_enabled!r} invalid, using True")
|
|
467
|
+
self.reranker_enabled = True
|
|
468
|
+
if not isinstance(self.reranker_top_k_multiplier, int) or self.reranker_top_k_multiplier < 1:
|
|
469
|
+
self.reranker_top_k_multiplier = 3
|
|
470
|
+
if not isinstance(self.supported_formats, list) or not self.supported_formats:
|
|
471
|
+
print("[WARN] supported_formats is empty or invalid, using defaults")
|
|
472
|
+
self.supported_formats = [".md", ".txt", ".pdf", ".py", ".json", ".docx", ".xlsx", ".pptx", ".csv"]
|
|
473
|
+
|
|
474
|
+
# Validate keyword_routes values are lists (not strings)
|
|
475
|
+
for cat, keywords in list(self.keyword_routes.items()):
|
|
476
|
+
if not isinstance(keywords, list):
|
|
477
|
+
print(f"[WARN] keyword_routes.{cat} is not a list, removing")
|
|
478
|
+
del self.keyword_routes[cat]
|
|
479
|
+
|
|
480
|
+
# Ensure directories exist
|
|
481
|
+
self.data_dir.mkdir(parents=True, exist_ok=True)
|
|
482
|
+
self.chroma_dir.mkdir(parents=True, exist_ok=True)
|
|
483
|
+
self.documents_dir.mkdir(parents=True, exist_ok=True)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
# Global config instance
|
|
487
|
+
config = Config()
|
|
@@ -19,10 +19,8 @@ Features:
|
|
|
19
19
|
- CRUD operations via MCP tools (add, update, remove docs)
|
|
20
20
|
|
|
21
21
|
Autor: Lyon (Ailton Rocha)
|
|
22
|
-
Versao: 3.
|
|
23
|
-
Data: 2026-
|
|
24
|
-
|
|
25
|
-
By Lyon :) Legal Ne?
|
|
22
|
+
Versao: 3.3.2
|
|
23
|
+
Data: 2026-04-06
|
|
26
24
|
"""
|
|
27
25
|
|
|
28
26
|
import hashlib
|
|
@@ -1827,8 +1825,58 @@ def evaluate_retrieval(test_cases: str) -> str:
|
|
|
1827
1825
|
# =============================================================================
|
|
1828
1826
|
|
|
1829
1827
|
|
|
1828
|
+
def _handle_init():
|
|
1829
|
+
"""Export config template and presets to current directory."""
|
|
1830
|
+
import shutil
|
|
1831
|
+
|
|
1832
|
+
data_dir = Path(__file__).parent / "data"
|
|
1833
|
+
if not data_dir.exists():
|
|
1834
|
+
print("[ERROR] Bundled data not found. If installed from git, use presets/ directly.")
|
|
1835
|
+
return
|
|
1836
|
+
|
|
1837
|
+
cwd = Path.cwd()
|
|
1838
|
+
|
|
1839
|
+
try:
|
|
1840
|
+
# Copy config.example.yaml
|
|
1841
|
+
src = data_dir / "config.example.yaml"
|
|
1842
|
+
if src.exists():
|
|
1843
|
+
dst = cwd / "config.example.yaml"
|
|
1844
|
+
shutil.copy2(src, dst)
|
|
1845
|
+
print(f"[OK] {dst}")
|
|
1846
|
+
|
|
1847
|
+
# Copy presets
|
|
1848
|
+
presets_dir = cwd / "presets"
|
|
1849
|
+
presets_dir.mkdir(exist_ok=True)
|
|
1850
|
+
for f in data_dir.glob("*.yaml"):
|
|
1851
|
+
if f.name == "config.example.yaml":
|
|
1852
|
+
continue
|
|
1853
|
+
dst = presets_dir / f.name
|
|
1854
|
+
shutil.copy2(f, dst)
|
|
1855
|
+
print(f"[OK] {dst}")
|
|
1856
|
+
|
|
1857
|
+
# Create documents dir
|
|
1858
|
+
docs_dir = cwd / "documents"
|
|
1859
|
+
docs_dir.mkdir(exist_ok=True)
|
|
1860
|
+
print(f"[OK] {docs_dir}/")
|
|
1861
|
+
|
|
1862
|
+
print("\nDone. Quick start:")
|
|
1863
|
+
print(" cp presets/general.yaml config.yaml # or cybersecurity, developer, research")
|
|
1864
|
+
print(" # Add your documents to documents/")
|
|
1865
|
+
print(" # Restart Claude Code")
|
|
1866
|
+
except PermissionError:
|
|
1867
|
+
print("[ERROR] Permission denied. Run from a writable directory.")
|
|
1868
|
+
except OSError as e:
|
|
1869
|
+
print(f"[ERROR] Failed to write files: {e}")
|
|
1870
|
+
|
|
1871
|
+
|
|
1830
1872
|
def main():
|
|
1831
1873
|
"""Run the MCP server"""
|
|
1874
|
+
import sys
|
|
1875
|
+
|
|
1876
|
+
if len(sys.argv) > 1 and sys.argv[1] == "init":
|
|
1877
|
+
_handle_init()
|
|
1878
|
+
return
|
|
1879
|
+
|
|
1832
1880
|
orchestrator = get_orchestrator()
|
|
1833
1881
|
|
|
1834
1882
|
# Migration: check dimension mismatch AFTER full init (avoids segfault during __init__)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "knowledge-rag"
|
|
7
|
-
version = "3.3.
|
|
7
|
+
version = "3.3.2"
|
|
8
8
|
description = "Local RAG System for Claude Code — Hybrid search + Cross-encoder Reranking + 12 MCP Tools. Zero external servers."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -55,6 +55,13 @@ knowledge-rag = "mcp_server.server:main"
|
|
|
55
55
|
[tool.hatch.build.targets.wheel]
|
|
56
56
|
packages = ["mcp_server"]
|
|
57
57
|
|
|
58
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
59
|
+
"config.example.yaml" = "mcp_server/data/config.example.yaml"
|
|
60
|
+
"presets/cybersecurity.yaml" = "mcp_server/data/cybersecurity.yaml"
|
|
61
|
+
"presets/developer.yaml" = "mcp_server/data/developer.yaml"
|
|
62
|
+
"presets/research.yaml" = "mcp_server/data/research.yaml"
|
|
63
|
+
"presets/general.yaml" = "mcp_server/data/general.yaml"
|
|
64
|
+
|
|
58
65
|
[tool.hatch.build.targets.sdist]
|
|
59
66
|
include = [
|
|
60
67
|
"mcp_server/",
|
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
"""Configuration for Knowledge RAG System v3.1 — YAML-configurable"""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
from dataclasses import dataclass, field
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import Dict, List
|
|
7
|
-
|
|
8
|
-
import yaml
|
|
9
|
-
|
|
10
|
-
# ============================================================================
|
|
11
|
-
# BASE DIRECTORY RESOLUTION
|
|
12
|
-
# ============================================================================
|
|
13
|
-
# Priority: 1. KNOWLEDGE_RAG_DIR env var 2. Source checkout 3. CWD
|
|
14
|
-
|
|
15
|
-
_source_dir = Path(__file__).parent.parent
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
_SUPPORTED_SUFFIXES = frozenset([".md", ".txt", ".pdf", ".py", ".json", ".docx", ".xlsx", ".pptx", ".csv"])
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _has_documents(path: Path) -> bool:
|
|
22
|
-
"""Check if path has a documents/ dir with actual supported files (follows symlinks)."""
|
|
23
|
-
docs_dir = path / "documents"
|
|
24
|
-
if not docs_dir.exists():
|
|
25
|
-
return False
|
|
26
|
-
for root, _, files in os.walk(docs_dir, followlinks=True):
|
|
27
|
-
for f in files:
|
|
28
|
-
if Path(f).suffix.lower() in _SUPPORTED_SUFFIXES:
|
|
29
|
-
return True
|
|
30
|
-
return False
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if os.environ.get("KNOWLEDGE_RAG_DIR"):
|
|
34
|
-
BASE_DIR = Path(os.environ["KNOWLEDGE_RAG_DIR"])
|
|
35
|
-
elif _has_documents(_source_dir):
|
|
36
|
-
BASE_DIR = _source_dir
|
|
37
|
-
elif _has_documents(Path.cwd()):
|
|
38
|
-
BASE_DIR = Path.cwd()
|
|
39
|
-
else:
|
|
40
|
-
BASE_DIR = Path.cwd()
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
# ============================================================================
|
|
44
|
-
# YAML CONFIG LOADER
|
|
45
|
-
# ============================================================================
|
|
46
|
-
|
|
47
|
-
def _load_yaml_config() -> dict:
|
|
48
|
-
"""Load config.yaml from BASE_DIR if it exists, otherwise return empty dict."""
|
|
49
|
-
config_path = BASE_DIR / "config.yaml"
|
|
50
|
-
if not config_path.exists():
|
|
51
|
-
return {}
|
|
52
|
-
|
|
53
|
-
try:
|
|
54
|
-
with open(config_path, "r", encoding="utf-8") as f:
|
|
55
|
-
data = yaml.safe_load(f)
|
|
56
|
-
if not isinstance(data, dict):
|
|
57
|
-
print(f"[WARN] config.yaml is not a valid mapping, ignoring")
|
|
58
|
-
return {}
|
|
59
|
-
print(f"[INFO] Loaded config from {config_path}")
|
|
60
|
-
return data
|
|
61
|
-
except yaml.YAMLError as e:
|
|
62
|
-
print(f"[WARN] Failed to parse config.yaml: {e} — using defaults")
|
|
63
|
-
return {}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
_yaml = _load_yaml_config()
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def _get(section: str, key: str, default):
|
|
70
|
-
"""Get a value from the YAML config with section.key path, falling back to default."""
|
|
71
|
-
s = _yaml.get(section, {})
|
|
72
|
-
if not isinstance(s, dict):
|
|
73
|
-
return default
|
|
74
|
-
val = s.get(key)
|
|
75
|
-
return val if val is not None else default
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
# ============================================================================
|
|
79
|
-
# DEFAULTS (used when no config.yaml or field is omitted)
|
|
80
|
-
# ============================================================================
|
|
81
|
-
|
|
82
|
-
_DEFAULT_CATEGORY_MAPPINGS = {
|
|
83
|
-
"security/redteam": "redteam",
|
|
84
|
-
"security/blueteam": "blueteam",
|
|
85
|
-
"security/ctf": "ctf",
|
|
86
|
-
"security": "security",
|
|
87
|
-
"aar": "aar",
|
|
88
|
-
"logscale": "logscale",
|
|
89
|
-
"development": "development",
|
|
90
|
-
"general": "general",
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
_DEFAULT_KEYWORD_ROUTES = {
|
|
94
|
-
"logscale": [
|
|
95
|
-
"logscale", "lql", "cql", "humio", "crowdstrike query",
|
|
96
|
-
"formattime", "groupby", "base64decode", "case{}", "regex",
|
|
97
|
-
],
|
|
98
|
-
"redteam": [
|
|
99
|
-
"pentest", "exploit", "payload", "reverse shell", "privilege escalation",
|
|
100
|
-
"lateral movement", "c2", "beacon", "cobalt strike", "metasploit",
|
|
101
|
-
"gtfobins", "lolbas", "lolbin", "suid", "sudo", "byovd", "lol driver",
|
|
102
|
-
"lolad", "lolapps", "hacktricks", "privesc", "kerberoast", "dcsync",
|
|
103
|
-
"golden ticket", "pass-the-hash", "bloodhound", "mimikatz", "rubeus",
|
|
104
|
-
"certipy", "adcs", "sqli", "xss", "ssti", "ssrf", "lfi", "rfi", "xxe",
|
|
105
|
-
"deserialization", "ysoserial", "upload bypass", "reverse shell",
|
|
106
|
-
"web shell", "hash cracking", "hashcat", "waf bypass", "amsi bypass",
|
|
107
|
-
"uac bypass", "potato", "searchsploit", "exploit-db", "cve",
|
|
108
|
-
],
|
|
109
|
-
"blueteam": [
|
|
110
|
-
"detection", "sigma", "yara", "ioc", "threat hunting",
|
|
111
|
-
"incident response", "forensics", "malware analysis",
|
|
112
|
-
],
|
|
113
|
-
"ctf": [
|
|
114
|
-
"ctf", "flag", "hackthebox", "htb", "tryhackme",
|
|
115
|
-
"picoctf", "writeup", "challenge",
|
|
116
|
-
],
|
|
117
|
-
"development": [
|
|
118
|
-
"python", "typescript", "javascript", "api",
|
|
119
|
-
"fastapi", "django", "react", "nodejs",
|
|
120
|
-
],
|
|
121
|
-
"security": [
|
|
122
|
-
"anti-bot", "antibot", "js challenge", "javascript challenge",
|
|
123
|
-
"cdp detection", "runtime.enable", "puppeteer", "playwright",
|
|
124
|
-
"selenium", "nodriver", "stealth", "undetected", "ja3", "ja4",
|
|
125
|
-
"tls fingerprint", "fingerprinting", "curl_cffi", "got-scraping",
|
|
126
|
-
"impersonate", "http/2 settings", "browser fingerprint",
|
|
127
|
-
"canvas fingerprint", "webgl fingerprint", "navigator.webdriver",
|
|
128
|
-
"audio context", "hardware concurrency", "waf bypass", "aws waf",
|
|
129
|
-
"cloudflare bypass", "akamai bypass", "datadome", "perimeterx",
|
|
130
|
-
"imperva bypass", "8kb bypass", "body size limit", "json sqli",
|
|
131
|
-
"behavioral", "mouse movement", "ghost-cursor", "humanized",
|
|
132
|
-
"flaresolverr", "turnstile", "rebrowser", "botbrowser",
|
|
133
|
-
],
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
_DEFAULT_QUERY_EXPANSIONS = {
|
|
137
|
-
"sqli": ["sql injection", "sqli"],
|
|
138
|
-
"sql injection": ["sql injection", "sqli"],
|
|
139
|
-
"xss": ["cross-site scripting", "xss"],
|
|
140
|
-
"cross-site scripting": ["cross-site scripting", "xss"],
|
|
141
|
-
"ssrf": ["server-side request forgery", "ssrf"],
|
|
142
|
-
"lfi": ["local file inclusion", "lfi"],
|
|
143
|
-
"rfi": ["remote file inclusion", "rfi"],
|
|
144
|
-
"rce": ["remote code execution", "rce"],
|
|
145
|
-
"xxe": ["xml external entity", "xxe"],
|
|
146
|
-
"ssti": ["server-side template injection", "ssti"],
|
|
147
|
-
"idor": ["insecure direct object reference", "idor"],
|
|
148
|
-
"csrf": ["cross-site request forgery", "csrf"],
|
|
149
|
-
"privesc": ["privilege escalation", "privesc"],
|
|
150
|
-
"priv esc": ["privilege escalation", "privesc"],
|
|
151
|
-
"privilege escalation": ["privilege escalation", "privesc"],
|
|
152
|
-
"deserialization": ["deserialization", "deserialisation", "insecure deserialization"],
|
|
153
|
-
"pth": ["pass-the-hash", "pth"],
|
|
154
|
-
"pass-the-hash": ["pass-the-hash", "pth"],
|
|
155
|
-
"dcsync": ["dcsync", "dc sync", "domain controller sync"],
|
|
156
|
-
"kerberoast": ["kerberoasting", "kerberoast"],
|
|
157
|
-
"kerberoasting": ["kerberoasting", "kerberoast"],
|
|
158
|
-
"asrep": ["as-rep roasting", "asrep", "asreproast"],
|
|
159
|
-
"bloodhound": ["bloodhound", "sharphound"],
|
|
160
|
-
"mimikatz": ["mimikatz", "sekurlsa", "logonpasswords"],
|
|
161
|
-
"hashcat": ["hashcat", "hash cracking", "hash crack"],
|
|
162
|
-
"john": ["john the ripper", "john", "jtr"],
|
|
163
|
-
"revshell": ["reverse shell", "revshell", "rev shell"],
|
|
164
|
-
"reverse shell": ["reverse shell", "revshell"],
|
|
165
|
-
"webshell": ["web shell", "webshell"],
|
|
166
|
-
"web shell": ["web shell", "webshell"],
|
|
167
|
-
"waf": ["web application firewall", "waf"],
|
|
168
|
-
"amsi": ["antimalware scan interface", "amsi", "amsi bypass"],
|
|
169
|
-
"uac": ["user account control", "uac", "uac bypass"],
|
|
170
|
-
"potato": ["potato", "juicypotato", "sweetpotato", "godpotato", "efspotato", "printspoofer"],
|
|
171
|
-
"ntlm": ["ntlm", "net-ntlmv2", "ntlmv2"],
|
|
172
|
-
"smb": ["smb", "server message block", "samba"],
|
|
173
|
-
"ldap": ["ldap", "lightweight directory access protocol"],
|
|
174
|
-
"ad": ["active directory", "ad"],
|
|
175
|
-
"active directory": ["active directory", "ad"],
|
|
176
|
-
"defender": ["windows defender", "defender", "wdfilter"],
|
|
177
|
-
"responder": ["responder", "llmnr", "nbt-ns", "netbios"],
|
|
178
|
-
"suid": ["suid", "setuid", "set-uid"],
|
|
179
|
-
"cron": ["cron", "crontab", "cronjob", "scheduled task"],
|
|
180
|
-
"lolbin": ["lolbin", "lolbas", "living off the land"],
|
|
181
|
-
"c2": ["c2", "command and control", "command-and-control", "beacon"],
|
|
182
|
-
"sliver": ["sliver", "sliver c2"],
|
|
183
|
-
"cobalt": ["cobalt strike", "cobalt", "cs beacon"],
|
|
184
|
-
"phishing": ["phishing", "spearphishing", "social engineering"],
|
|
185
|
-
"forensics": ["forensics", "forensic", "dfir"],
|
|
186
|
-
"volatility": ["volatility", "memory forensics", "memory analysis"],
|
|
187
|
-
"steganography": ["steganography", "stego", "steghide"],
|
|
188
|
-
"stego": ["steganography", "stego", "steghide"],
|
|
189
|
-
"rbcd": ["resource-based constrained delegation", "rbcd"],
|
|
190
|
-
"dpapi": ["dpapi", "data protection api", "credential manager"],
|
|
191
|
-
"printnightmare": ["printnightmare", "cve-2021-34527", "spoolsv", "printspooler"],
|
|
192
|
-
"cve-2021-34527": ["printnightmare", "cve-2021-34527", "spoolsv"],
|
|
193
|
-
"eternalblue": ["eternalblue", "ms17-010", "smbv1"],
|
|
194
|
-
"ms17-010": ["eternalblue", "ms17-010", "smbv1"],
|
|
195
|
-
"pwnkit": ["pwnkit", "cve-2021-4034", "pkexec"],
|
|
196
|
-
"cve-2021-4034": ["pwnkit", "cve-2021-4034", "pkexec"],
|
|
197
|
-
"log4shell": ["log4shell", "cve-2021-44228", "log4j"],
|
|
198
|
-
"cve-2021-44228": ["log4shell", "cve-2021-44228", "log4j"],
|
|
199
|
-
"zerologon": ["zerologon", "cve-2020-1472", "netlogon"],
|
|
200
|
-
"cve-2020-1472": ["zerologon", "cve-2020-1472", "netlogon"],
|
|
201
|
-
"petitpotam": ["petitpotam", "cve-2021-36942", "efs", "ntlm relay"],
|
|
202
|
-
"certifried": ["certifried", "cve-2022-26923", "adcs"],
|
|
203
|
-
"nopac": ["nopac", "samaccountname", "cve-2021-42278", "cve-2021-42287"],
|
|
204
|
-
"proxylogon": ["proxylogon", "cve-2021-26855", "exchange"],
|
|
205
|
-
"proxyshell": ["proxyshell", "cve-2021-34473", "exchange"],
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
# ============================================================================
|
|
210
|
-
# CONFIG DATACLASS
|
|
211
|
-
# ============================================================================
|
|
212
|
-
|
|
213
|
-
def _resolve_path(raw, default: Path) -> Path:
|
|
214
|
-
"""Resolve a path from YAML (string) or use default (Path)."""
|
|
215
|
-
if raw is None:
|
|
216
|
-
return default
|
|
217
|
-
p = Path(raw)
|
|
218
|
-
if not p.is_absolute():
|
|
219
|
-
p = BASE_DIR / p
|
|
220
|
-
return p
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
@dataclass
|
|
224
|
-
class Config:
|
|
225
|
-
"""Central configuration for the RAG system — loads from config.yaml when available."""
|
|
226
|
-
|
|
227
|
-
# Paths
|
|
228
|
-
data_dir: Path = field(default_factory=lambda: _resolve_path(
|
|
229
|
-
_get("paths", "data_dir", None), BASE_DIR / "data"
|
|
230
|
-
))
|
|
231
|
-
chroma_dir: Path = field(default_factory=lambda: _resolve_path(
|
|
232
|
-
_get("paths", "data_dir", None), BASE_DIR / "data"
|
|
233
|
-
) / "chroma_db")
|
|
234
|
-
documents_dir: Path = field(default_factory=lambda: _resolve_path(
|
|
235
|
-
_get("paths", "documents_dir", None), BASE_DIR / "documents"
|
|
236
|
-
))
|
|
237
|
-
|
|
238
|
-
# Chunking
|
|
239
|
-
chunk_size: int = field(default_factory=lambda: _get("documents", "chunking", {}).get("chunk_size", 1000) if isinstance(_get("documents", "chunking", {}), dict) else 1000)
|
|
240
|
-
chunk_overlap: int = field(default_factory=lambda: _get("documents", "chunking", {}).get("chunk_overlap", 200) if isinstance(_get("documents", "chunking", {}), dict) else 200)
|
|
241
|
-
|
|
242
|
-
# Embeddings
|
|
243
|
-
embedding_model: str = field(default_factory=lambda: _get("models", "embedding", {}).get("model", "BAAI/bge-small-en-v1.5") if isinstance(_get("models", "embedding", {}), dict) else "BAAI/bge-small-en-v1.5")
|
|
244
|
-
embedding_dim: int = field(default_factory=lambda: _get("models", "embedding", {}).get("dimensions", 384) if isinstance(_get("models", "embedding", {}), dict) else 384)
|
|
245
|
-
|
|
246
|
-
# Reranker
|
|
247
|
-
reranker_model: str = field(default_factory=lambda: _get("models", "reranker", {}).get("model", "Xenova/ms-marco-MiniLM-L-6-v2") if isinstance(_get("models", "reranker", {}), dict) else "Xenova/ms-marco-MiniLM-L-6-v2")
|
|
248
|
-
reranker_enabled: bool = field(default_factory=lambda: _get("models", "reranker", {}).get("enabled", True) if isinstance(_get("models", "reranker", {}), dict) else True)
|
|
249
|
-
reranker_top_k_multiplier: int = field(default_factory=lambda: _get("models", "reranker", {}).get("top_k_multiplier", 3) if isinstance(_get("models", "reranker", {}), dict) else 3)
|
|
250
|
-
|
|
251
|
-
# ChromaDB
|
|
252
|
-
collection_name: str = field(default_factory=lambda: _get("search", "collection_name", "knowledge_base"))
|
|
253
|
-
|
|
254
|
-
# Supported formats
|
|
255
|
-
supported_formats: List[str] = field(default_factory=lambda: _get("documents", "supported_formats", [".md", ".txt", ".pdf", ".py", ".json", ".docx", ".xlsx", ".pptx", ".csv"]))
|
|
256
|
-
|
|
257
|
-
# Category mappings
|
|
258
|
-
category_mappings: Dict[str, str] = field(default_factory=lambda: _yaml.get("category_mappings", _DEFAULT_CATEGORY_MAPPINGS))
|
|
259
|
-
|
|
260
|
-
# Keyword routes
|
|
261
|
-
keyword_routes: Dict[str, List[str]] = field(default_factory=lambda: _yaml.get("keyword_routes", _DEFAULT_KEYWORD_ROUTES))
|
|
262
|
-
|
|
263
|
-
# Query expansions
|
|
264
|
-
query_expansions: Dict[str, List[str]] = field(default_factory=lambda: _yaml.get("query_expansions", _DEFAULT_QUERY_EXPANSIONS))
|
|
265
|
-
|
|
266
|
-
# Search settings
|
|
267
|
-
default_results: int = field(default_factory=lambda: _get("search", "default_results", 5))
|
|
268
|
-
max_results: int = field(default_factory=lambda: _get("search", "max_results", 20))
|
|
269
|
-
|
|
270
|
-
def __post_init__(self):
|
|
271
|
-
"""Ensure directories exist"""
|
|
272
|
-
self.data_dir.mkdir(parents=True, exist_ok=True)
|
|
273
|
-
self.chroma_dir.mkdir(parents=True, exist_ok=True)
|
|
274
|
-
self.documents_dir.mkdir(parents=True, exist_ok=True)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
# Global config instance
|
|
278
|
-
config = Config()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|