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 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 **KQL** security detection rules.
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 KQL detections from a single interface
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 `kql` |
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 KQL
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 KQL) |
885
- | `source_type` | `sigma`, `splunk_escu`, `elastic`, or `kql` |
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** | **~7,200+** |
1129
+ | **Total Detections** | **~8,200+** |
1054
1130
  | **Indexed Patterns** | **10,235+** |
1055
1131
  | **Techniques with Patterns** | **528+** |
1056
- | **Detection Formats** | **4** (Sigma, Splunk, Elastic, KQL) |
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 7,200+ detection rules + 11 expert workflow prompts |
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": {
@@ -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;
@@ -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
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
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
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
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;