security-detections-mcp 3.0.0 → 3.1.0
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.
- package/README.md +95 -17
- package/dist/db/detections.d.ts +8 -8
- package/dist/db/detections.js +14 -7
- package/dist/db/schema.js +15 -9
- package/dist/db.d.ts +3 -3
- package/dist/db.js +27 -15
- package/dist/index.js +5 -3
- package/dist/indexer.d.ts +5 -1
- package/dist/indexer.js +40 -2
- package/dist/parsers/crowdstrike_cql.d.ts +2 -0
- package/dist/parsers/crowdstrike_cql.js +302 -0
- package/dist/parsers/elastic.js +3 -0
- package/dist/parsers/kql.js +6 -0
- package/dist/parsers/sigma.js +3 -0
- package/dist/parsers/splunk.js +3 -0
- package/dist/parsers/sublime.d.ts +2 -0
- package/dist/parsers/sublime.js +106 -0
- package/dist/resources/index.js +1 -1
- package/dist/tools/detections/analysis.js +4 -4
- package/dist/tools/detections/comparison.js +3 -3
- package/dist/tools/detections/filters.js +1 -1
- package/dist/tools/detections/search.js +1 -1
- package/dist/tools/engineering/index.js +2 -2
- package/dist/types/detection.d.ts +46 -4
- package/dist/types/detection.js +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +1 -1
- package/dist/types/stats.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
# Security Detections MCP
|
|
2
2
|
|
|
3
|
-
An MCP (Model Context Protocol) server that lets LLMs query a unified database of **Sigma**, **Splunk ESCU**, **Elastic**, and **
|
|
3
|
+
An MCP (Model Context Protocol) server that lets LLMs query a unified database of **Sigma**, **Splunk ESCU**, **Elastic**, **KQL**, **Sublime**, and **CrowdStrike CQL** security detection rules.
|
|
4
4
|
|
|
5
5
|
> **New here? Start with the [Setup Guide](./SETUP.md)** -- covers macOS, Windows (WSL & native), and Linux step by step.
|
|
6
6
|
|
|
7
|
+
## What's New in 3.1 - New Detection Sources
|
|
8
|
+
|
|
9
|
+
- **CrowdStrike CQL Hub** - Query and search CrowdStrike Query Language (CQL) detections from the CQL Hub community repository
|
|
10
|
+
- **Sublime Rules** - Query and search Sublime Security detection rules for email-based threats
|
|
11
|
+
|
|
7
12
|
## What's New in 3.0 - Autonomous Detection Platform
|
|
8
13
|
|
|
9
14
|
Version 3.0 transforms this MCP into a **fully autonomous detection engineering platform**. Feed it threat intelligence, and it automatically:
|
|
@@ -213,7 +218,7 @@ Claude will:
|
|
|
213
218
|
- **🆕 Server Instructions** - Built-in usage guide with examples for better LLM understanding
|
|
214
219
|
- **🆕 Structured Errors** - Helpful error messages with suggestions and similar items
|
|
215
220
|
- **🆕 Interactive Tools** - Gap prioritization and sprint planning with form-based input (Cursor 0.42+)
|
|
216
|
-
- **Unified Search** - Query Sigma, Splunk ESCU, Elastic, and
|
|
221
|
+
- **Unified Search** - Query Sigma, Splunk ESCU, Elastic, KQL, Sublime, and CrowdStrike CQL detections from a single interface
|
|
217
222
|
- **Full-Text Search** - SQLite FTS5 powered search across names, descriptions, queries, MITRE tactics, CVEs, process names, and more
|
|
218
223
|
- **MITRE ATT&CK Mapping** - Filter detections by technique ID or tactic
|
|
219
224
|
- **CVE Coverage** - Find detections for specific CVE vulnerabilities
|
|
@@ -221,7 +226,7 @@ Claude will:
|
|
|
221
226
|
- **Analytic Stories** - Query by Splunk analytic story (optional - enhances context)
|
|
222
227
|
- **KQL Categories** - Filter KQL queries by category (Defender For Endpoint, Azure AD, Threat Hunting, etc.)
|
|
223
228
|
- **Auto-Indexing** - Automatically indexes detections on startup from configured paths
|
|
224
|
-
- **Multi-Format Support** - YAML (Sigma, Splunk), TOML (Elastic), Markdown (KQL)
|
|
229
|
+
- **Multi-Format Support** - YAML (Sigma, Splunk, Sublime, CrowdStrike CQL), TOML (Elastic), Markdown (KQL)
|
|
225
230
|
- **Logsource Filtering** - Filter Sigma rules by category, product, or service
|
|
226
231
|
- **Severity Filtering** - Filter by criticality level
|
|
227
232
|
|
|
@@ -261,7 +266,9 @@ Add to your MCP config (`~/.cursor/mcp.json` or `.cursor/mcp.json` in your proje
|
|
|
261
266
|
"SPLUNK_PATHS": "/path/to/security_content/detections",
|
|
262
267
|
"ELASTIC_PATHS": "/path/to/detection-rules/rules",
|
|
263
268
|
"STORY_PATHS": "/path/to/security_content/stories",
|
|
264
|
-
"KQL_PATHS": "/path/to/Hunting-Queries-Detection-Rules"
|
|
269
|
+
"KQL_PATHS": "/path/to/Hunting-Queries-Detection-Rules",
|
|
270
|
+
"SUBLIME_PATHS": "/path/to/sublime-rules/detection-rules",
|
|
271
|
+
"CQL_HUB_PATHS": "/path/to/cql-hub/queries"
|
|
265
272
|
}
|
|
266
273
|
}
|
|
267
274
|
}
|
|
@@ -283,7 +290,9 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
|
283
290
|
"SPLUNK_PATHS": "/Users/you/security_content/detections",
|
|
284
291
|
"ELASTIC_PATHS": "/Users/you/detection-rules/rules",
|
|
285
292
|
"STORY_PATHS": "/Users/you/security_content/stories",
|
|
286
|
-
"KQL_PATHS": "/Users/you/Hunting-Queries-Detection-Rules"
|
|
293
|
+
"KQL_PATHS": "/Users/you/Hunting-Queries-Detection-Rules",
|
|
294
|
+
"SUBLIME_PATHS": "/Users/you/sublime-rules/detection-rules",
|
|
295
|
+
"CQL_HUB_PATHS": "/Users/you/cql-hub/queries"
|
|
287
296
|
}
|
|
288
297
|
}
|
|
289
298
|
}
|
|
@@ -305,7 +314,9 @@ Add to `~/.vscode/mcp.json`:
|
|
|
305
314
|
"SPLUNK_PATHS": "/Users/you/security_content/detections",
|
|
306
315
|
"ELASTIC_PATHS": "/Users/you/detection-rules/rules",
|
|
307
316
|
"KQL_PATHS": "/Users/you/kql-bertjanp,/Users/you/kql-jkerai1",
|
|
308
|
-
"STORY_PATHS": "/Users/you/security_content/stories"
|
|
317
|
+
"STORY_PATHS": "/Users/you/security_content/stories",
|
|
318
|
+
"SUBLIME_PATHS": "/Users/you/sublime-rules/detection-rules",
|
|
319
|
+
"CQL_HUB_PATHS": "/Users/you/cql-hub/queries"
|
|
309
320
|
}
|
|
310
321
|
}
|
|
311
322
|
}
|
|
@@ -327,7 +338,9 @@ Add to `~/.vscode/mcp.json`:
|
|
|
327
338
|
"SPLUNK_PATHS": "/Users/you/security_content/detections",
|
|
328
339
|
"ELASTIC_PATHS": "/Users/you/detection-rules/rules",
|
|
329
340
|
"KQL_PATHS": "/Users/you/kql-bertjanp,/Users/you/kql-jkerai1",
|
|
330
|
-
"STORY_PATHS": "/Users/you/security_content/stories"
|
|
341
|
+
"STORY_PATHS": "/Users/you/security_content/stories",
|
|
342
|
+
"SUBLIME_PATHS": "/Users/you/sublime-rules/detection-rules",
|
|
343
|
+
"CQL_HUB_PATHS": "/Users/you/cql-hub/queries"
|
|
331
344
|
}
|
|
332
345
|
}
|
|
333
346
|
}
|
|
@@ -341,6 +354,8 @@ Add to `~/.vscode/mcp.json`:
|
|
|
341
354
|
| `SPLUNK_PATHS` | Comma-separated paths to Splunk ESCU detection directories | At least one source required |
|
|
342
355
|
| `ELASTIC_PATHS` | Comma-separated paths to Elastic detection rule directories | At least one source required |
|
|
343
356
|
| `KQL_PATHS` | Comma-separated paths to KQL hunting query directories | At least one source required |
|
|
357
|
+
| `SUBLIME_PATHS` | Comma-separated paths to Sublime Security rule directories | At least one source required |
|
|
358
|
+
| `CQL_HUB_PATHS` | Comma-separated paths to CQL Hub (CrowdStrike) query directories | At least one source required |
|
|
344
359
|
| `STORY_PATHS` | Comma-separated paths to Splunk analytic story directories | No (enhances context) |
|
|
345
360
|
|
|
346
361
|
## Getting Detection Content
|
|
@@ -369,11 +384,20 @@ cd detection-rules && git sparse-checkout set rules && cd ..
|
|
|
369
384
|
git clone --depth 1 https://github.com/Bert-JanP/Hunting-Queries-Detection-Rules.git kql-bertjanp
|
|
370
385
|
git clone --depth 1 https://github.com/jkerai1/KQL-Queries.git kql-jkerai1
|
|
371
386
|
|
|
387
|
+
# Download Sublime Security email detection rules (~900+ rules)
|
|
388
|
+
git clone --depth 1 --filter=blob:none --sparse https://github.com/sublime-security/sublime-rules.git
|
|
389
|
+
cd sublime-rules && git sparse-checkout set detection-rules && cd ..
|
|
390
|
+
|
|
391
|
+
# Download CQL Hub CrowdStrike queries (~139+ queries)
|
|
392
|
+
git clone --depth 1 https://github.com/ByteRay-Labs/Query-Hub.git cql-hub
|
|
393
|
+
|
|
372
394
|
echo "Done! Configure your MCP with these paths:"
|
|
373
395
|
echo " SIGMA_PATHS: $(pwd)/sigma/rules,$(pwd)/sigma/rules-threat-hunting"
|
|
374
396
|
echo " SPLUNK_PATHS: $(pwd)/security_content/detections"
|
|
375
397
|
echo " ELASTIC_PATHS: $(pwd)/detection-rules/rules"
|
|
376
398
|
echo " KQL_PATHS: $(pwd)/kql-bertjanp,$(pwd)/kql-jkerai1"
|
|
399
|
+
echo " SUBLIME_PATHS: $(pwd)/sublime-rules/detection-rules"
|
|
400
|
+
echo " CQL_HUB_PATHS: $(pwd)/cql-hub/queries"
|
|
377
401
|
echo " STORY_PATHS: $(pwd)/security_content/stories"
|
|
378
402
|
```
|
|
379
403
|
|
|
@@ -398,6 +422,14 @@ git clone https://github.com/elastic/detection-rules.git
|
|
|
398
422
|
git clone https://github.com/Bert-JanP/Hunting-Queries-Detection-Rules.git
|
|
399
423
|
git clone https://github.com/jkerai1/KQL-Queries.git
|
|
400
424
|
# Use entire repos, combine paths with comma
|
|
425
|
+
|
|
426
|
+
# Sublime Security Rules
|
|
427
|
+
git clone https://github.com/sublime-security/sublime-rules.git
|
|
428
|
+
# Use detection-rules/ directory
|
|
429
|
+
|
|
430
|
+
# CQL Hub (CrowdStrike Query Language)
|
|
431
|
+
git clone https://github.com/ByteRay-Labs/Query-Hub.git
|
|
432
|
+
# Use queries/ directory
|
|
401
433
|
```
|
|
402
434
|
|
|
403
435
|
## 🆕 MCP Resources - Readable Context
|
|
@@ -427,7 +459,7 @@ The server provides **autocomplete suggestions** as you type argument values:
|
|
|
427
459
|
| `process_name` | Process names in your detections (powershell.exe, etc.) |
|
|
428
460
|
| `tactic` | All 14 MITRE tactics |
|
|
429
461
|
| `severity` | informational, low, medium, high, critical |
|
|
430
|
-
| `source_type` | sigma, splunk_escu, elastic, kql |
|
|
462
|
+
| `source_type` | sigma, splunk_escu, elastic, kql, sublime, crowdstrike_cql |
|
|
431
463
|
| `threat_profile` | ransomware, apt, initial-access, persistence, etc. |
|
|
432
464
|
|
|
433
465
|
This prevents typos and helps discover what values are available in your detection corpus.
|
|
@@ -486,7 +518,7 @@ Tool: prioritize_gaps(threat_profile="ransomware")
|
|
|
486
518
|
| `search(query, limit)` | Full-text search across all detection fields (names, descriptions, queries, CVEs, process names, etc.) |
|
|
487
519
|
| `get_by_id(id)` | Get a single detection by its ID |
|
|
488
520
|
| `list_all(limit, offset)` | Paginated list of all detections |
|
|
489
|
-
| `list_by_source(source_type)` | Filter by `sigma`, `splunk_escu`, `elastic`, or `
|
|
521
|
+
| `list_by_source(source_type)` | Filter by `sigma`, `splunk_escu`, `elastic`, `kql`, `sublime`, or `crowdstrike_cql` |
|
|
490
522
|
| `get_raw_yaml(id)` | Get the original YAML/TOML/Markdown content |
|
|
491
523
|
| `get_stats()` | Get index statistics |
|
|
492
524
|
| `rebuild_index()` | Force re-index from configured paths |
|
|
@@ -837,7 +869,7 @@ Tool: list_by_cve(cve_id="CVE-2024-27198")
|
|
|
837
869
|
```
|
|
838
870
|
LLM: "What detections do we have for credential dumping?"
|
|
839
871
|
Tool: search(query="credential dumping", limit=10)
|
|
840
|
-
→ Returns results from Sigma, Splunk, Elastic, AND
|
|
872
|
+
→ Returns results from Sigma, Splunk, Elastic, KQL, Sublime, AND CrowdStrike CQL
|
|
841
873
|
```
|
|
842
874
|
|
|
843
875
|
#### Find Web Server Attack Detections
|
|
@@ -862,6 +894,22 @@ LLM: "What KQL queries do we have for Defender For Endpoint?"
|
|
|
862
894
|
Tool: list_by_kql_category(category="Defender For Endpoint")
|
|
863
895
|
```
|
|
864
896
|
|
|
897
|
+
#### Find BEC/Phishing Email Detections
|
|
898
|
+
|
|
899
|
+
```
|
|
900
|
+
LLM: "What email detections do we have for BEC fraud?"
|
|
901
|
+
Tool: list_by_source(source_type="sublime")
|
|
902
|
+
→ Returns Sublime Security email detection rules for BEC, phishing, malware, etc.
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
#### Find CrowdStrike CQL Hunting Queries
|
|
906
|
+
|
|
907
|
+
```
|
|
908
|
+
LLM: "What CrowdStrike queries do we have for lateral movement?"
|
|
909
|
+
Tool: list_by_source(source_type="crowdstrike_cql")
|
|
910
|
+
→ Returns CQL Hub queries for CrowdStrike NextGen SIEM and Falcon LogScale
|
|
911
|
+
```
|
|
912
|
+
|
|
865
913
|
#### Search for BloodHound Detections
|
|
866
914
|
|
|
867
915
|
```
|
|
@@ -872,7 +920,7 @@ Tool: search(query="bloodhound", limit=10)
|
|
|
872
920
|
|
|
873
921
|
## Unified Schema
|
|
874
922
|
|
|
875
|
-
All detection sources (Sigma, Splunk, Elastic, KQL) are normalized to a common schema:
|
|
923
|
+
All detection sources (Sigma, Splunk, Elastic, KQL, Sublime, CrowdStrike CQL) are normalized to a common schema:
|
|
876
924
|
|
|
877
925
|
### Core Fields
|
|
878
926
|
|
|
@@ -881,8 +929,8 @@ All detection sources (Sigma, Splunk, Elastic, KQL) are normalized to a common s
|
|
|
881
929
|
| `id` | Unique identifier |
|
|
882
930
|
| `name` | Detection name/title |
|
|
883
931
|
| `description` | What the detection looks for |
|
|
884
|
-
| `query` | Detection logic (Sigma YAML, Splunk SPL, Elastic EQL, or
|
|
885
|
-
| `source_type` | `sigma`, `splunk_escu`, `elastic`, or `
|
|
932
|
+
| `query` | Detection logic (Sigma YAML, Splunk SPL, Elastic EQL, KQL, Sublime MQL, or CrowdStrike CQL) |
|
|
933
|
+
| `source_type` | `sigma`, `splunk_escu`, `elastic`, `kql`, `sublime`, or `crowdstrike_cql` |
|
|
886
934
|
| `severity` | Detection severity level |
|
|
887
935
|
| `status` | Rule status (stable, test, experimental, production, etc.) |
|
|
888
936
|
| `author` | Rule author |
|
|
@@ -914,6 +962,14 @@ All detection sources (Sigma, Splunk, Elastic, KQL) are normalized to a common s
|
|
|
914
962
|
| `kql_keywords` | Security keywords extracted for search |
|
|
915
963
|
| `platforms` | Platforms (windows, azure-ad, office-365, etc.) |
|
|
916
964
|
|
|
965
|
+
### Sublime-Specific Fields
|
|
966
|
+
|
|
967
|
+
| Field | Description |
|
|
968
|
+
|-------|-------------|
|
|
969
|
+
| `sublime_attack_types` | Attack types (BEC/Fraud, Credential Phishing, Malware/Ransomware, etc.) |
|
|
970
|
+
| `sublime_detection_methods` | Detection methods (Content analysis, URL analysis, Computer Vision, etc.) |
|
|
971
|
+
| `sublime_tactics` | Tactics and techniques (Evasion, Impersonation: Brand, Social engineering, etc.) |
|
|
972
|
+
|
|
917
973
|
## Database
|
|
918
974
|
|
|
919
975
|
The index is stored at `~/.cache/security-detections-mcp/detections.sqlite`.
|
|
@@ -951,6 +1007,22 @@ From [Elastic Detection Rules](https://github.com/elastic/detection-rules):
|
|
|
951
1007
|
- Optional: `rule.description`, `rule.query`, `rule.severity`, `rule.tags`, `rule.threat` (MITRE mappings)
|
|
952
1008
|
- Supports EQL, KQL, Lucene, and ESQL query languages
|
|
953
1009
|
|
|
1010
|
+
### Sublime Security Rules (YAML)
|
|
1011
|
+
|
|
1012
|
+
From [Sublime Security](https://github.com/sublime-security/sublime-rules):
|
|
1013
|
+
- Required: `name`, `type` (rule/exclusion), `source` (MQL query)
|
|
1014
|
+
- Optional: `description`, `severity`, `id`, `references`, `tags`, `authors`, `attack_types`, `tactics_and_techniques`, `detection_methods`, `false_positives`
|
|
1015
|
+
- Uses MQL (Message Query Language) for email-specific detection logic
|
|
1016
|
+
- Covers BEC/fraud, credential phishing, malware delivery, spam, and more
|
|
1017
|
+
|
|
1018
|
+
### CrowdStrike CQL Queries (YAML)
|
|
1019
|
+
|
|
1020
|
+
From [CQL Hub](https://github.com/ByteRay-Labs/Query-Hub):
|
|
1021
|
+
- Required: `name`, `cql` (CrowdStrike Query Language query)
|
|
1022
|
+
- Optional: `description`, `mitre_ids`, `author`, `log_sources`, `tags`, `cs_required_modules`, `explanation`
|
|
1023
|
+
- Community-driven detection and hunting queries for CrowdStrike NextGen SIEM and Falcon LogScale
|
|
1024
|
+
- Covers endpoint, network, cloud, and identity detection use cases
|
|
1025
|
+
|
|
954
1026
|
### KQL Hunting Queries (Markdown & Raw .kql)
|
|
955
1027
|
|
|
956
1028
|
Supports multiple KQL repositories:
|
|
@@ -1035,6 +1107,8 @@ SIGMA_PATHS="./detections/sigma/rules" \
|
|
|
1035
1107
|
SPLUNK_PATHS="./detections/splunk/detections" \
|
|
1036
1108
|
ELASTIC_PATHS="./detections/elastic/rules" \
|
|
1037
1109
|
KQL_PATHS="./detections/kql" \
|
|
1110
|
+
SUBLIME_PATHS="./detections/sublime-rules/detection-rules" \
|
|
1111
|
+
CQL_HUB_PATHS="./detections/cql-hub/queries" \
|
|
1038
1112
|
STORY_PATHS="./detections/splunk/stories" \
|
|
1039
1113
|
npm start
|
|
1040
1114
|
```
|
|
@@ -1049,11 +1123,13 @@ When fully indexed with all sources:
|
|
|
1049
1123
|
| Splunk ESCU | ~2,000+ |
|
|
1050
1124
|
| Elastic Rules | ~1,500+ |
|
|
1051
1125
|
| KQL Queries | ~420+ |
|
|
1126
|
+
| Sublime Rules | ~900+ |
|
|
1127
|
+
| CrowdStrike CQL | ~139+ |
|
|
1052
1128
|
| Analytic Stories | ~330 |
|
|
1053
|
-
| **Total Detections** | **~
|
|
1129
|
+
| **Total Detections** | **~8,200+** |
|
|
1054
1130
|
| **Indexed Patterns** | **10,235+** |
|
|
1055
1131
|
| **Techniques with Patterns** | **528+** |
|
|
1056
|
-
| **Detection Formats** | **
|
|
1132
|
+
| **Detection Formats** | **6** (Sigma, Splunk, Elastic, KQL, Sublime, CrowdStrike CQL) |
|
|
1057
1133
|
| **Total Tools** | **71+** |
|
|
1058
1134
|
| **MCP Prompts** | **11** |
|
|
1059
1135
|
| **MCP Resources** | **9 static + 5 templates** |
|
|
@@ -1158,7 +1234,7 @@ For detailed information on v2.1 features:
|
|
|
1158
1234
|
|
|
1159
1235
|
| MCP | Purpose |
|
|
1160
1236
|
|-----|---------|
|
|
1161
|
-
| **security-detections-mcp** | Query
|
|
1237
|
+
| **security-detections-mcp** | Query 8,100+ detection rules + 11 expert workflow prompts |
|
|
1162
1238
|
| **mitre-attack-mcp** | ATT&CK framework data, threat groups, Navigator layers |
|
|
1163
1239
|
|
|
1164
1240
|
### With MCP Prompts (Easiest)
|
|
@@ -1224,7 +1300,9 @@ LLM workflow:
|
|
|
1224
1300
|
"SIGMA_PATHS": "/path/to/sigma/rules",
|
|
1225
1301
|
"SPLUNK_PATHS": "/path/to/security_content/detections",
|
|
1226
1302
|
"ELASTIC_PATHS": "/path/to/detection-rules/rules",
|
|
1227
|
-
"KQL_PATHS": "/path/to/kql-hunting-queries"
|
|
1303
|
+
"KQL_PATHS": "/path/to/kql-hunting-queries",
|
|
1304
|
+
"SUBLIME_PATHS": "/path/to/sublime-rules/detection-rules",
|
|
1305
|
+
"CQL_HUB_PATHS": "/path/to/cql-hub/queries"
|
|
1228
1306
|
}
|
|
1229
1307
|
},
|
|
1230
1308
|
"mitre-attack": {
|
package/dist/db/detections.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface ValidationResult {
|
|
|
12
12
|
similar?: string[];
|
|
13
13
|
}
|
|
14
14
|
export interface TechniqueIdFilters {
|
|
15
|
-
source_type?: 'sigma' | 'splunk_escu' | 'elastic' | 'kql';
|
|
15
|
+
source_type?: 'sigma' | 'splunk_escu' | 'elastic' | 'kql' | 'sublime' | 'crowdstrike_cql';
|
|
16
16
|
tactic?: string;
|
|
17
17
|
severity?: string;
|
|
18
18
|
}
|
|
@@ -59,7 +59,7 @@ export interface DetectionSuggestion {
|
|
|
59
59
|
export interface NavigatorLayerOptions {
|
|
60
60
|
name: string;
|
|
61
61
|
description?: string;
|
|
62
|
-
source_type?: 'sigma' | 'splunk_escu' | 'elastic' | 'kql';
|
|
62
|
+
source_type?: 'sigma' | 'splunk_escu' | 'elastic' | 'kql' | 'sublime' | 'crowdstrike_cql';
|
|
63
63
|
tactic?: string;
|
|
64
64
|
severity?: string;
|
|
65
65
|
}
|
|
@@ -107,7 +107,7 @@ export declare function listDetections(limit?: number, offset?: number): Detecti
|
|
|
107
107
|
/**
|
|
108
108
|
* List detections filtered by source type.
|
|
109
109
|
*/
|
|
110
|
-
export declare function listBySource(sourceType: 'sigma' | 'splunk_escu' | 'elastic' | 'kql', limit?: number, offset?: number): Detection[];
|
|
110
|
+
export declare function listBySource(sourceType: 'sigma' | 'splunk_escu' | 'elastic' | 'kql' | 'sublime' | 'crowdstrike_cql', limit?: number, offset?: number): Detection[];
|
|
111
111
|
/**
|
|
112
112
|
* List detections by MITRE technique ID.
|
|
113
113
|
*/
|
|
@@ -183,15 +183,15 @@ export declare function getTechniqueIds(filters?: TechniqueIdFilters): string[];
|
|
|
183
183
|
/**
|
|
184
184
|
* Analyze coverage by tactic and identify strengths/weaknesses.
|
|
185
185
|
*/
|
|
186
|
-
export declare function analyzeCoverage(sourceType?: 'sigma' | 'splunk_escu' | 'elastic' | 'kql'): CoverageReport;
|
|
186
|
+
export declare function analyzeCoverage(sourceType?: 'sigma' | 'splunk_escu' | 'elastic' | 'kql' | 'sublime' | 'crowdstrike_cql'): CoverageReport;
|
|
187
187
|
/**
|
|
188
188
|
* Identify gaps based on a threat profile.
|
|
189
189
|
*/
|
|
190
|
-
export declare function identifyGaps(threatProfile: string, sourceType?: 'sigma' | 'splunk_escu' | 'elastic' | 'kql'): GapAnalysis;
|
|
190
|
+
export declare function identifyGaps(threatProfile: string, sourceType?: 'sigma' | 'splunk_escu' | 'elastic' | 'kql' | 'sublime' | 'crowdstrike_cql'): GapAnalysis;
|
|
191
191
|
/**
|
|
192
192
|
* Suggest detections for a technique.
|
|
193
193
|
*/
|
|
194
|
-
export declare function suggestDetections(techniqueId: string, sourceType?: 'sigma' | 'splunk_escu' | 'elastic' | 'kql'): DetectionSuggestion;
|
|
194
|
+
export declare function suggestDetections(techniqueId: string, sourceType?: 'sigma' | 'splunk_escu' | 'elastic' | 'kql' | 'sublime' | 'crowdstrike_cql'): DetectionSuggestion;
|
|
195
195
|
/**
|
|
196
196
|
* Generate an ATT&CK Navigator layer from detection coverage.
|
|
197
197
|
*/
|
|
@@ -203,7 +203,7 @@ export declare function searchDetectionList(query: string, limit?: number): Dete
|
|
|
203
203
|
/**
|
|
204
204
|
* List detections by source with optional name filter, returning lightweight results.
|
|
205
205
|
*/
|
|
206
|
-
export declare function listDetectionsBySourceLight(sourceType: 'sigma' | 'splunk_escu' | 'elastic' | 'kql', nameFilter?: string, limit?: number): DetectionListItem[];
|
|
206
|
+
export declare function listDetectionsBySourceLight(sourceType: 'sigma' | 'splunk_escu' | 'elastic' | 'kql' | 'sublime' | 'crowdstrike_cql', nameFilter?: string, limit?: number): DetectionListItem[];
|
|
207
207
|
/**
|
|
208
208
|
* Compare detections across sources for a topic.
|
|
209
209
|
*/
|
|
@@ -211,7 +211,7 @@ export declare function compareDetectionsBySource(topic: string, limit?: number)
|
|
|
211
211
|
/**
|
|
212
212
|
* Get detection names and IDs matching a pattern, grouped by source.
|
|
213
213
|
*/
|
|
214
|
-
export declare function getDetectionNamesByPattern(pattern: string, sourceType?: 'sigma' | 'splunk_escu' | 'elastic' | 'kql'): {
|
|
214
|
+
export declare function getDetectionNamesByPattern(pattern: string, sourceType?: 'sigma' | 'splunk_escu' | 'elastic' | 'kql' | 'sublime' | 'crowdstrike_cql'): {
|
|
215
215
|
source: string;
|
|
216
216
|
detections: Array<{
|
|
217
217
|
name: string;
|
package/dist/db/detections.js
CHANGED
|
@@ -75,6 +75,9 @@ function rowToDetection(row) {
|
|
|
75
75
|
kql_category: row.kql_category,
|
|
76
76
|
kql_tags: safeJsonParse(row.kql_tags, []),
|
|
77
77
|
kql_keywords: safeJsonParse(row.kql_keywords, []),
|
|
78
|
+
sublime_attack_types: safeJsonParse(row.sublime_attack_types, []),
|
|
79
|
+
sublime_detection_methods: safeJsonParse(row.sublime_detection_methods, []),
|
|
80
|
+
sublime_tactics: safeJsonParse(row.sublime_tactics, []),
|
|
78
81
|
};
|
|
79
82
|
}
|
|
80
83
|
function rowToListItem(row) {
|
|
@@ -95,15 +98,16 @@ function rowToListItem(row) {
|
|
|
95
98
|
export function insertDetection(detection) {
|
|
96
99
|
const database = getDb();
|
|
97
100
|
const stmt = database.prepare(`
|
|
98
|
-
INSERT OR REPLACE INTO detections
|
|
99
|
-
(id, name, description, query, source_type, mitre_ids, logsource_category,
|
|
100
|
-
logsource_product, logsource_service, severity, status, author,
|
|
101
|
+
INSERT OR REPLACE INTO detections
|
|
102
|
+
(id, name, description, query, source_type, mitre_ids, logsource_category,
|
|
103
|
+
logsource_product, logsource_service, severity, status, author,
|
|
101
104
|
date_created, date_modified, refs, falsepositives, tags, file_path, raw_yaml,
|
|
102
105
|
cves, analytic_stories, data_sources, detection_type, asset_type, security_domain,
|
|
103
|
-
process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords
|
|
104
|
-
|
|
106
|
+
process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords,
|
|
107
|
+
sublime_attack_types, sublime_detection_methods, sublime_tactics)
|
|
108
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
105
109
|
`);
|
|
106
|
-
stmt.run(detection.id, detection.name, detection.description, detection.query, detection.source_type, JSON.stringify(detection.mitre_ids), detection.logsource_category, detection.logsource_product, detection.logsource_service, detection.severity, detection.status, detection.author, detection.date_created, detection.date_modified, JSON.stringify(detection.references), JSON.stringify(detection.falsepositives), JSON.stringify(detection.tags), detection.file_path, detection.raw_yaml, JSON.stringify(detection.cves), JSON.stringify(detection.analytic_stories), JSON.stringify(detection.data_sources), detection.detection_type, detection.asset_type, detection.security_domain, JSON.stringify(detection.process_names), JSON.stringify(detection.file_paths), JSON.stringify(detection.registry_paths), JSON.stringify(detection.mitre_tactics), JSON.stringify(detection.platforms), detection.kql_category, JSON.stringify(detection.kql_tags), JSON.stringify(detection.kql_keywords));
|
|
110
|
+
stmt.run(detection.id, detection.name, detection.description, detection.query, detection.source_type, JSON.stringify(detection.mitre_ids), detection.logsource_category, detection.logsource_product, detection.logsource_service, detection.severity, detection.status, detection.author, detection.date_created, detection.date_modified, JSON.stringify(detection.references), JSON.stringify(detection.falsepositives), JSON.stringify(detection.tags), detection.file_path, detection.raw_yaml, JSON.stringify(detection.cves), JSON.stringify(detection.analytic_stories), JSON.stringify(detection.data_sources), detection.detection_type, detection.asset_type, detection.security_domain, JSON.stringify(detection.process_names), JSON.stringify(detection.file_paths), JSON.stringify(detection.registry_paths), JSON.stringify(detection.mitre_tactics), JSON.stringify(detection.platforms), detection.kql_category, JSON.stringify(detection.kql_tags), JSON.stringify(detection.kql_keywords), JSON.stringify(detection.sublime_attack_types), JSON.stringify(detection.sublime_detection_methods), JSON.stringify(detection.sublime_tactics));
|
|
107
111
|
}
|
|
108
112
|
/**
|
|
109
113
|
* Get a detection by its ID.
|
|
@@ -348,6 +352,7 @@ export function getStats() {
|
|
|
348
352
|
const splunk = database.prepare("SELECT COUNT(*) as count FROM detections WHERE source_type = 'splunk_escu'").get().count;
|
|
349
353
|
const elastic = database.prepare("SELECT COUNT(*) as count FROM detections WHERE source_type = 'elastic'").get().count;
|
|
350
354
|
const kql = database.prepare("SELECT COUNT(*) as count FROM detections WHERE source_type = 'kql'").get().count;
|
|
355
|
+
const sublime = database.prepare("SELECT COUNT(*) as count FROM detections WHERE source_type = 'sublime'").get().count;
|
|
351
356
|
// Count by severity
|
|
352
357
|
const severityRows = database.prepare(`
|
|
353
358
|
SELECT severity, COUNT(*) as count FROM detections
|
|
@@ -425,6 +430,7 @@ export function getStats() {
|
|
|
425
430
|
splunk_escu: splunk,
|
|
426
431
|
elastic,
|
|
427
432
|
kql,
|
|
433
|
+
sublime,
|
|
428
434
|
by_severity,
|
|
429
435
|
by_logsource_product,
|
|
430
436
|
mitre_coverage,
|
|
@@ -863,6 +869,7 @@ export function compareDetectionsBySource(topic, limit = 100) {
|
|
|
863
869
|
splunk_escu: [],
|
|
864
870
|
elastic: [],
|
|
865
871
|
kql: [],
|
|
872
|
+
sublime: [],
|
|
866
873
|
};
|
|
867
874
|
const byTactic = {};
|
|
868
875
|
for (const row of rows) {
|
|
@@ -936,7 +943,7 @@ export function countDetectionsBySource(topic) {
|
|
|
936
943
|
GROUP BY d.source_type
|
|
937
944
|
`);
|
|
938
945
|
const rows = stmt.all(topic);
|
|
939
|
-
const result = { sigma: 0, splunk_escu: 0, elastic: 0, kql: 0 };
|
|
946
|
+
const result = { sigma: 0, splunk_escu: 0, elastic: 0, kql: 0, sublime: 0, crowdstrike_cql: 0 };
|
|
940
947
|
for (const row of rows) {
|
|
941
948
|
result[row.source_type] = row.count;
|
|
942
949
|
}
|
package/dist/db/schema.js
CHANGED
|
@@ -56,7 +56,10 @@ function createDetectionsTable(db) {
|
|
|
56
56
|
platforms TEXT,
|
|
57
57
|
kql_category TEXT,
|
|
58
58
|
kql_tags TEXT,
|
|
59
|
-
kql_keywords TEXT
|
|
59
|
+
kql_keywords TEXT,
|
|
60
|
+
sublime_attack_types TEXT,
|
|
61
|
+
sublime_detection_methods TEXT,
|
|
62
|
+
sublime_tactics TEXT
|
|
60
63
|
)
|
|
61
64
|
`);
|
|
62
65
|
}
|
|
@@ -83,6 +86,9 @@ function createDetectionsFts(db) {
|
|
|
83
86
|
kql_category,
|
|
84
87
|
kql_tags,
|
|
85
88
|
kql_keywords,
|
|
89
|
+
sublime_attack_types,
|
|
90
|
+
sublime_detection_methods,
|
|
91
|
+
sublime_tactics,
|
|
86
92
|
content='detections',
|
|
87
93
|
content_rowid='rowid'
|
|
88
94
|
)
|
|
@@ -95,24 +101,24 @@ function createDetectionsTriggers(db) {
|
|
|
95
101
|
// After INSERT trigger
|
|
96
102
|
db.exec(`
|
|
97
103
|
CREATE TRIGGER IF NOT EXISTS detections_ai AFTER INSERT ON detections BEGIN
|
|
98
|
-
INSERT INTO detections_fts(rowid, id, name, description, query, mitre_ids, tags, cves, analytic_stories, data_sources, process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords)
|
|
99
|
-
VALUES (NEW.rowid, NEW.id, NEW.name, NEW.description, NEW.query, NEW.mitre_ids, NEW.tags, NEW.cves, NEW.analytic_stories, NEW.data_sources, NEW.process_names, NEW.file_paths, NEW.registry_paths, NEW.mitre_tactics, NEW.platforms, NEW.kql_category, NEW.kql_tags, NEW.kql_keywords);
|
|
104
|
+
INSERT INTO detections_fts(rowid, id, name, description, query, mitre_ids, tags, cves, analytic_stories, data_sources, process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords, sublime_attack_types, sublime_detection_methods, sublime_tactics)
|
|
105
|
+
VALUES (NEW.rowid, NEW.id, NEW.name, NEW.description, NEW.query, NEW.mitre_ids, NEW.tags, NEW.cves, NEW.analytic_stories, NEW.data_sources, NEW.process_names, NEW.file_paths, NEW.registry_paths, NEW.mitre_tactics, NEW.platforms, NEW.kql_category, NEW.kql_tags, NEW.kql_keywords, NEW.sublime_attack_types, NEW.sublime_detection_methods, NEW.sublime_tactics);
|
|
100
106
|
END
|
|
101
107
|
`);
|
|
102
108
|
// After DELETE trigger
|
|
103
109
|
db.exec(`
|
|
104
110
|
CREATE TRIGGER IF NOT EXISTS detections_ad AFTER DELETE ON detections BEGIN
|
|
105
|
-
INSERT INTO detections_fts(detections_fts, rowid, id, name, description, query, mitre_ids, tags, cves, analytic_stories, data_sources, process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords)
|
|
106
|
-
VALUES ('delete', OLD.rowid, OLD.id, OLD.name, OLD.description, OLD.query, OLD.mitre_ids, OLD.tags, OLD.cves, OLD.analytic_stories, OLD.data_sources, OLD.process_names, OLD.file_paths, OLD.registry_paths, OLD.mitre_tactics, OLD.platforms, OLD.kql_category, OLD.kql_tags, OLD.kql_keywords);
|
|
111
|
+
INSERT INTO detections_fts(detections_fts, rowid, id, name, description, query, mitre_ids, tags, cves, analytic_stories, data_sources, process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords, sublime_attack_types, sublime_detection_methods, sublime_tactics)
|
|
112
|
+
VALUES ('delete', OLD.rowid, OLD.id, OLD.name, OLD.description, OLD.query, OLD.mitre_ids, OLD.tags, OLD.cves, OLD.analytic_stories, OLD.data_sources, OLD.process_names, OLD.file_paths, OLD.registry_paths, OLD.mitre_tactics, OLD.platforms, OLD.kql_category, OLD.kql_tags, OLD.kql_keywords, OLD.sublime_attack_types, OLD.sublime_detection_methods, OLD.sublime_tactics);
|
|
107
113
|
END
|
|
108
114
|
`);
|
|
109
115
|
// After UPDATE trigger
|
|
110
116
|
db.exec(`
|
|
111
117
|
CREATE TRIGGER IF NOT EXISTS detections_au AFTER UPDATE ON detections BEGIN
|
|
112
|
-
INSERT INTO detections_fts(detections_fts, rowid, id, name, description, query, mitre_ids, tags, cves, analytic_stories, data_sources, process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords)
|
|
113
|
-
VALUES ('delete', OLD.rowid, OLD.id, OLD.name, OLD.description, OLD.query, OLD.mitre_ids, OLD.tags, OLD.cves, OLD.analytic_stories, OLD.data_sources, OLD.process_names, OLD.file_paths, OLD.registry_paths, OLD.mitre_tactics, OLD.platforms, OLD.kql_category, OLD.kql_tags, OLD.kql_keywords);
|
|
114
|
-
INSERT INTO detections_fts(rowid, id, name, description, query, mitre_ids, tags, cves, analytic_stories, data_sources, process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords)
|
|
115
|
-
VALUES (NEW.rowid, NEW.id, NEW.name, NEW.description, NEW.query, NEW.mitre_ids, NEW.tags, NEW.cves, NEW.analytic_stories, NEW.data_sources, NEW.process_names, NEW.file_paths, NEW.registry_paths, NEW.mitre_tactics, NEW.platforms, NEW.kql_category, NEW.kql_tags, NEW.kql_keywords);
|
|
118
|
+
INSERT INTO detections_fts(detections_fts, rowid, id, name, description, query, mitre_ids, tags, cves, analytic_stories, data_sources, process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords, sublime_attack_types, sublime_detection_methods, sublime_tactics)
|
|
119
|
+
VALUES ('delete', OLD.rowid, OLD.id, OLD.name, OLD.description, OLD.query, OLD.mitre_ids, OLD.tags, OLD.cves, OLD.analytic_stories, OLD.data_sources, OLD.process_names, OLD.file_paths, OLD.registry_paths, OLD.mitre_tactics, OLD.platforms, OLD.kql_category, OLD.kql_tags, OLD.kql_keywords, OLD.sublime_attack_types, OLD.sublime_detection_methods, OLD.sublime_tactics);
|
|
120
|
+
INSERT INTO detections_fts(rowid, id, name, description, query, mitre_ids, tags, cves, analytic_stories, data_sources, process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords, sublime_attack_types, sublime_detection_methods, sublime_tactics)
|
|
121
|
+
VALUES (NEW.rowid, NEW.id, NEW.name, NEW.description, NEW.query, NEW.mitre_ids, NEW.tags, NEW.cves, NEW.analytic_stories, NEW.data_sources, NEW.process_names, NEW.file_paths, NEW.registry_paths, NEW.mitre_tactics, NEW.platforms, NEW.kql_category, NEW.kql_tags, NEW.kql_keywords, NEW.sublime_attack_types, NEW.sublime_detection_methods, NEW.sublime_tactics);
|
|
116
122
|
END
|
|
117
123
|
`);
|
|
118
124
|
}
|
package/dist/db.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export declare function insertDetection(detection: Detection): void;
|
|
|
8
8
|
export declare function searchDetections(query: string, limit?: number): Detection[];
|
|
9
9
|
export declare function getDetectionById(id: string): Detection | null;
|
|
10
10
|
export declare function listDetections(limit?: number, offset?: number): Detection[];
|
|
11
|
-
export declare function listBySource(sourceType: 'sigma' | 'splunk_escu' | 'elastic' | 'kql', limit?: number, offset?: number): Detection[];
|
|
11
|
+
export declare function listBySource(sourceType: 'sigma' | 'splunk_escu' | 'elastic' | 'kql' | 'sublime' | 'crowdstrike_cql', limit?: number, offset?: number): Detection[];
|
|
12
12
|
export declare function listByMitre(techniqueId: string, limit?: number, offset?: number): Detection[];
|
|
13
13
|
export declare function listByLogsource(category?: string, product?: string, service?: string, limit?: number, offset?: number): Detection[];
|
|
14
14
|
export declare function listBySeverity(level: string, limit?: number, offset?: number): Detection[];
|
|
@@ -117,9 +117,9 @@ export interface SourceComparisonResult {
|
|
|
117
117
|
};
|
|
118
118
|
}
|
|
119
119
|
export declare function searchDetectionList(query: string, limit?: number): DetectionListItem[];
|
|
120
|
-
export declare function listDetectionsBySourceLight(sourceType: 'sigma' | 'splunk_escu' | 'elastic' | 'kql', nameFilter?: string, limit?: number): DetectionListItem[];
|
|
120
|
+
export declare function listDetectionsBySourceLight(sourceType: 'sigma' | 'splunk_escu' | 'elastic' | 'kql' | 'sublime' | 'crowdstrike_cql', nameFilter?: string, limit?: number): DetectionListItem[];
|
|
121
121
|
export declare function compareDetectionsBySource(topic: string, limit?: number): SourceComparisonResult;
|
|
122
|
-
export declare function getDetectionNamesByPattern(pattern: string, sourceType?: 'sigma' | 'splunk_escu' | 'elastic' | 'kql'): {
|
|
122
|
+
export declare function getDetectionNamesByPattern(pattern: string, sourceType?: 'sigma' | 'splunk_escu' | 'elastic' | 'kql' | 'sublime' | 'crowdstrike_cql'): {
|
|
123
123
|
source: string;
|
|
124
124
|
detections: Array<{
|
|
125
125
|
name: string;
|
package/dist/db.js
CHANGED
|
@@ -51,7 +51,10 @@ export function initDb() {
|
|
|
51
51
|
platforms TEXT,
|
|
52
52
|
kql_category TEXT,
|
|
53
53
|
kql_tags TEXT,
|
|
54
|
-
kql_keywords TEXT
|
|
54
|
+
kql_keywords TEXT,
|
|
55
|
+
sublime_attack_types TEXT,
|
|
56
|
+
sublime_detection_methods TEXT,
|
|
57
|
+
sublime_tactics TEXT
|
|
55
58
|
)
|
|
56
59
|
`);
|
|
57
60
|
// Create FTS5 virtual table for full-text search with all searchable fields
|
|
@@ -74,6 +77,9 @@ export function initDb() {
|
|
|
74
77
|
kql_category,
|
|
75
78
|
kql_tags,
|
|
76
79
|
kql_keywords,
|
|
80
|
+
sublime_attack_types,
|
|
81
|
+
sublime_detection_methods,
|
|
82
|
+
sublime_tactics,
|
|
77
83
|
content='detections',
|
|
78
84
|
content_rowid='rowid'
|
|
79
85
|
)
|
|
@@ -81,22 +87,22 @@ export function initDb() {
|
|
|
81
87
|
// Create triggers to keep FTS in sync
|
|
82
88
|
db.exec(`
|
|
83
89
|
CREATE TRIGGER IF NOT EXISTS detections_ai AFTER INSERT ON detections BEGIN
|
|
84
|
-
INSERT INTO detections_fts(rowid, id, name, description, query, mitre_ids, tags, cves, analytic_stories, data_sources, process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords)
|
|
85
|
-
VALUES (NEW.rowid, NEW.id, NEW.name, NEW.description, NEW.query, NEW.mitre_ids, NEW.tags, NEW.cves, NEW.analytic_stories, NEW.data_sources, NEW.process_names, NEW.file_paths, NEW.registry_paths, NEW.mitre_tactics, NEW.platforms, NEW.kql_category, NEW.kql_tags, NEW.kql_keywords);
|
|
90
|
+
INSERT INTO detections_fts(rowid, id, name, description, query, mitre_ids, tags, cves, analytic_stories, data_sources, process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords, sublime_attack_types, sublime_detection_methods, sublime_tactics)
|
|
91
|
+
VALUES (NEW.rowid, NEW.id, NEW.name, NEW.description, NEW.query, NEW.mitre_ids, NEW.tags, NEW.cves, NEW.analytic_stories, NEW.data_sources, NEW.process_names, NEW.file_paths, NEW.registry_paths, NEW.mitre_tactics, NEW.platforms, NEW.kql_category, NEW.kql_tags, NEW.kql_keywords, NEW.sublime_attack_types, NEW.sublime_detection_methods, NEW.sublime_tactics);
|
|
86
92
|
END
|
|
87
93
|
`);
|
|
88
94
|
db.exec(`
|
|
89
95
|
CREATE TRIGGER IF NOT EXISTS detections_ad AFTER DELETE ON detections BEGIN
|
|
90
|
-
INSERT INTO detections_fts(detections_fts, rowid, id, name, description, query, mitre_ids, tags, cves, analytic_stories, data_sources, process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords)
|
|
91
|
-
VALUES ('delete', OLD.rowid, OLD.id, OLD.name, OLD.description, OLD.query, OLD.mitre_ids, OLD.tags, OLD.cves, OLD.analytic_stories, OLD.data_sources, OLD.process_names, OLD.file_paths, OLD.registry_paths, OLD.mitre_tactics, OLD.platforms, OLD.kql_category, OLD.kql_tags, OLD.kql_keywords);
|
|
96
|
+
INSERT INTO detections_fts(detections_fts, rowid, id, name, description, query, mitre_ids, tags, cves, analytic_stories, data_sources, process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords, sublime_attack_types, sublime_detection_methods, sublime_tactics)
|
|
97
|
+
VALUES ('delete', OLD.rowid, OLD.id, OLD.name, OLD.description, OLD.query, OLD.mitre_ids, OLD.tags, OLD.cves, OLD.analytic_stories, OLD.data_sources, OLD.process_names, OLD.file_paths, OLD.registry_paths, OLD.mitre_tactics, OLD.platforms, OLD.kql_category, OLD.kql_tags, OLD.kql_keywords, OLD.sublime_attack_types, OLD.sublime_detection_methods, OLD.sublime_tactics);
|
|
92
98
|
END
|
|
93
99
|
`);
|
|
94
100
|
db.exec(`
|
|
95
101
|
CREATE TRIGGER IF NOT EXISTS detections_au AFTER UPDATE ON detections BEGIN
|
|
96
|
-
INSERT INTO detections_fts(detections_fts, rowid, id, name, description, query, mitre_ids, tags, cves, analytic_stories, data_sources, process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords)
|
|
97
|
-
VALUES ('delete', OLD.rowid, OLD.id, OLD.name, OLD.description, OLD.query, OLD.mitre_ids, OLD.tags, OLD.cves, OLD.analytic_stories, OLD.data_sources, OLD.process_names, OLD.file_paths, OLD.registry_paths, OLD.mitre_tactics, OLD.platforms, OLD.kql_category, OLD.kql_tags, OLD.kql_keywords);
|
|
98
|
-
INSERT INTO detections_fts(rowid, id, name, description, query, mitre_ids, tags, cves, analytic_stories, data_sources, process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords)
|
|
99
|
-
VALUES (NEW.rowid, NEW.id, NEW.name, NEW.description, NEW.query, NEW.mitre_ids, NEW.tags, NEW.cves, NEW.analytic_stories, NEW.data_sources, NEW.process_names, NEW.file_paths, NEW.registry_paths, NEW.mitre_tactics, NEW.platforms, NEW.kql_category, NEW.kql_tags, NEW.kql_keywords);
|
|
102
|
+
INSERT INTO detections_fts(detections_fts, rowid, id, name, description, query, mitre_ids, tags, cves, analytic_stories, data_sources, process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords, sublime_attack_types, sublime_detection_methods, sublime_tactics)
|
|
103
|
+
VALUES ('delete', OLD.rowid, OLD.id, OLD.name, OLD.description, OLD.query, OLD.mitre_ids, OLD.tags, OLD.cves, OLD.analytic_stories, OLD.data_sources, OLD.process_names, OLD.file_paths, OLD.registry_paths, OLD.mitre_tactics, OLD.platforms, OLD.kql_category, OLD.kql_tags, OLD.kql_keywords, OLD.sublime_attack_types, OLD.sublime_detection_methods, OLD.sublime_tactics);
|
|
104
|
+
INSERT INTO detections_fts(rowid, id, name, description, query, mitre_ids, tags, cves, analytic_stories, data_sources, process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords, sublime_attack_types, sublime_detection_methods, sublime_tactics)
|
|
105
|
+
VALUES (NEW.rowid, NEW.id, NEW.name, NEW.description, NEW.query, NEW.mitre_ids, NEW.tags, NEW.cves, NEW.analytic_stories, NEW.data_sources, NEW.process_names, NEW.file_paths, NEW.registry_paths, NEW.mitre_tactics, NEW.platforms, NEW.kql_category, NEW.kql_tags, NEW.kql_keywords, NEW.sublime_attack_types, NEW.sublime_detection_methods, NEW.sublime_tactics);
|
|
100
106
|
END
|
|
101
107
|
`);
|
|
102
108
|
// Create indexes for common queries
|
|
@@ -199,15 +205,16 @@ export function recreateDb() {
|
|
|
199
205
|
export function insertDetection(detection) {
|
|
200
206
|
const database = initDb();
|
|
201
207
|
const stmt = database.prepare(`
|
|
202
|
-
INSERT OR REPLACE INTO detections
|
|
203
|
-
(id, name, description, query, source_type, mitre_ids, logsource_category,
|
|
204
|
-
logsource_product, logsource_service, severity, status, author,
|
|
208
|
+
INSERT OR REPLACE INTO detections
|
|
209
|
+
(id, name, description, query, source_type, mitre_ids, logsource_category,
|
|
210
|
+
logsource_product, logsource_service, severity, status, author,
|
|
205
211
|
date_created, date_modified, refs, falsepositives, tags, file_path, raw_yaml,
|
|
206
212
|
cves, analytic_stories, data_sources, detection_type, asset_type, security_domain,
|
|
207
|
-
process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords
|
|
208
|
-
|
|
213
|
+
process_names, file_paths, registry_paths, mitre_tactics, platforms, kql_category, kql_tags, kql_keywords,
|
|
214
|
+
sublime_attack_types, sublime_detection_methods, sublime_tactics)
|
|
215
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
209
216
|
`);
|
|
210
|
-
stmt.run(detection.id, detection.name, detection.description, detection.query, detection.source_type, JSON.stringify(detection.mitre_ids), detection.logsource_category, detection.logsource_product, detection.logsource_service, detection.severity, detection.status, detection.author, detection.date_created, detection.date_modified, JSON.stringify(detection.references), JSON.stringify(detection.falsepositives), JSON.stringify(detection.tags), detection.file_path, detection.raw_yaml, JSON.stringify(detection.cves), JSON.stringify(detection.analytic_stories), JSON.stringify(detection.data_sources), detection.detection_type, detection.asset_type, detection.security_domain, JSON.stringify(detection.process_names), JSON.stringify(detection.file_paths), JSON.stringify(detection.registry_paths), JSON.stringify(detection.mitre_tactics), JSON.stringify(detection.platforms), detection.kql_category, JSON.stringify(detection.kql_tags), JSON.stringify(detection.kql_keywords));
|
|
217
|
+
stmt.run(detection.id, detection.name, detection.description, detection.query, detection.source_type, JSON.stringify(detection.mitre_ids), detection.logsource_category, detection.logsource_product, detection.logsource_service, detection.severity, detection.status, detection.author, detection.date_created, detection.date_modified, JSON.stringify(detection.references), JSON.stringify(detection.falsepositives), JSON.stringify(detection.tags), detection.file_path, detection.raw_yaml, JSON.stringify(detection.cves), JSON.stringify(detection.analytic_stories), JSON.stringify(detection.data_sources), detection.detection_type, detection.asset_type, detection.security_domain, JSON.stringify(detection.process_names), JSON.stringify(detection.file_paths), JSON.stringify(detection.registry_paths), JSON.stringify(detection.mitre_tactics), JSON.stringify(detection.platforms), detection.kql_category, JSON.stringify(detection.kql_tags), JSON.stringify(detection.kql_keywords), JSON.stringify(detection.sublime_attack_types), JSON.stringify(detection.sublime_detection_methods), JSON.stringify(detection.sublime_tactics));
|
|
211
218
|
}
|
|
212
219
|
function rowToDetection(row) {
|
|
213
220
|
return {
|
|
@@ -244,6 +251,9 @@ function rowToDetection(row) {
|
|
|
244
251
|
kql_category: row.kql_category,
|
|
245
252
|
kql_tags: JSON.parse(row.kql_tags || '[]'),
|
|
246
253
|
kql_keywords: JSON.parse(row.kql_keywords || '[]'),
|
|
254
|
+
sublime_attack_types: JSON.parse(row.sublime_attack_types || '[]'),
|
|
255
|
+
sublime_detection_methods: JSON.parse(row.sublime_detection_methods || '[]'),
|
|
256
|
+
sublime_tactics: JSON.parse(row.sublime_tactics || '[]'),
|
|
247
257
|
};
|
|
248
258
|
}
|
|
249
259
|
export function searchDetections(query, limit = 50) {
|
|
@@ -419,6 +429,7 @@ export function getStats() {
|
|
|
419
429
|
const splunk = database.prepare("SELECT COUNT(*) as count FROM detections WHERE source_type = 'splunk_escu'").get().count;
|
|
420
430
|
const elastic = database.prepare("SELECT COUNT(*) as count FROM detections WHERE source_type = 'elastic'").get().count;
|
|
421
431
|
const kql = database.prepare("SELECT COUNT(*) as count FROM detections WHERE source_type = 'kql'").get().count;
|
|
432
|
+
const sublime = database.prepare("SELECT COUNT(*) as count FROM detections WHERE source_type = 'sublime'").get().count;
|
|
422
433
|
// Count by severity
|
|
423
434
|
const severityRows = database.prepare(`
|
|
424
435
|
SELECT severity, COUNT(*) as count FROM detections
|
|
@@ -496,6 +507,7 @@ export function getStats() {
|
|
|
496
507
|
splunk_escu: splunk,
|
|
497
508
|
elastic,
|
|
498
509
|
kql,
|
|
510
|
+
sublime,
|
|
499
511
|
by_severity,
|
|
500
512
|
by_logsource_product,
|
|
501
513
|
mitre_coverage,
|
package/dist/index.js
CHANGED
|
@@ -27,17 +27,19 @@ const SPLUNK_PATHS = parsePaths(process.env.SPLUNK_PATHS);
|
|
|
27
27
|
const ELASTIC_PATHS = parsePaths(process.env.ELASTIC_PATHS);
|
|
28
28
|
const STORY_PATHS = parsePaths(process.env.STORY_PATHS);
|
|
29
29
|
const KQL_PATHS = parsePaths(process.env.KQL_PATHS);
|
|
30
|
+
const SUBLIME_PATHS = parsePaths(process.env.SUBLIME_PATHS);
|
|
31
|
+
const CQL_HUB_PATHS = parsePaths(process.env.CQL_HUB_PATHS);
|
|
30
32
|
// Auto-index on startup if paths are configured and DB is empty
|
|
31
33
|
function autoIndex() {
|
|
32
|
-
if (SIGMA_PATHS.length === 0 && SPLUNK_PATHS.length === 0 && ELASTIC_PATHS.length === 0 && KQL_PATHS.length === 0) {
|
|
34
|
+
if (SIGMA_PATHS.length === 0 && SPLUNK_PATHS.length === 0 && ELASTIC_PATHS.length === 0 && KQL_PATHS.length === 0 && SUBLIME_PATHS.length === 0 && CQL_HUB_PATHS.length === 0) {
|
|
33
35
|
return;
|
|
34
36
|
}
|
|
35
37
|
initDb();
|
|
36
38
|
if (needsIndexing()) {
|
|
37
39
|
console.error('[security-detections-mcp] Auto-indexing detections...');
|
|
38
|
-
const result = indexDetections(SIGMA_PATHS, SPLUNK_PATHS, STORY_PATHS, ELASTIC_PATHS, KQL_PATHS);
|
|
40
|
+
const result = indexDetections(SIGMA_PATHS, SPLUNK_PATHS, STORY_PATHS, ELASTIC_PATHS, KQL_PATHS, SUBLIME_PATHS, CQL_HUB_PATHS);
|
|
39
41
|
let msg = `[security-detections-mcp] Indexed ${result.total} detections`;
|
|
40
|
-
msg += ` (${result.sigma_indexed} Sigma, ${result.splunk_indexed} Splunk, ${result.elastic_indexed} Elastic, ${result.kql_indexed} KQL)`;
|
|
42
|
+
msg += ` (${result.sigma_indexed} Sigma, ${result.splunk_indexed} Splunk, ${result.elastic_indexed} Elastic, ${result.kql_indexed} KQL, ${result.sublime_indexed} Sublime, ${result.cql_hub_indexed} CrowdStrike CQL)`;
|
|
41
43
|
if (result.stories_indexed > 0) {
|
|
42
44
|
msg += `, ${result.stories_indexed} stories`;
|
|
43
45
|
}
|
package/dist/indexer.d.ts
CHANGED
|
@@ -7,9 +7,13 @@ export interface IndexResult {
|
|
|
7
7
|
elastic_failed: number;
|
|
8
8
|
kql_indexed: number;
|
|
9
9
|
kql_failed: number;
|
|
10
|
+
sublime_indexed: number;
|
|
11
|
+
sublime_failed: number;
|
|
12
|
+
cql_hub_indexed: number;
|
|
13
|
+
cql_hub_failed: number;
|
|
10
14
|
stories_indexed: number;
|
|
11
15
|
stories_failed: number;
|
|
12
16
|
total: number;
|
|
13
17
|
}
|
|
14
|
-
export declare function indexDetections(sigmaPaths: string[], splunkPaths: string[], storyPaths?: string[], elasticPaths?: string[], kqlPaths?: string[]): IndexResult;
|
|
18
|
+
export declare function indexDetections(sigmaPaths: string[], splunkPaths: string[], storyPaths?: string[], elasticPaths?: string[], kqlPaths?: string[], sublimePaths?: string[], cqlHubPaths?: string[]): IndexResult;
|
|
15
19
|
export declare function needsIndexing(): boolean;
|