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/dist/indexer.js CHANGED
@@ -5,6 +5,8 @@ import { parseSplunkFile } from './parsers/splunk.js';
5
5
  import { parseStoryFile } from './parsers/story.js';
6
6
  import { parseElasticFile } from './parsers/elastic.js';
7
7
  import { parseKqlFile, parseRawKqlFile } from './parsers/kql.js';
8
+ import { parseSublimeFile } from './parsers/sublime.js';
9
+ import { parseCqlHubFile } from './parsers/crowdstrike_cql.js';
8
10
  import { recreateDb, insertDetection, insertStory, getDetectionCount, initDb } from './db.js';
9
11
  // Recursively find all YAML files in a directory
10
12
  function findYamlFiles(dir) {
@@ -108,7 +110,7 @@ function findKqlFiles(dir) {
108
110
  }
109
111
  return files;
110
112
  }
111
- export function indexDetections(sigmaPaths, splunkPaths, storyPaths = [], elasticPaths = [], kqlPaths = []) {
113
+ export function indexDetections(sigmaPaths, splunkPaths, storyPaths = [], elasticPaths = [], kqlPaths = [], sublimePaths = [], cqlHubPaths = []) {
112
114
  // Recreate DB to ensure schema is up to date
113
115
  recreateDb();
114
116
  initDb();
@@ -120,6 +122,10 @@ export function indexDetections(sigmaPaths, splunkPaths, storyPaths = [], elasti
120
122
  let elastic_failed = 0;
121
123
  let kql_indexed = 0;
122
124
  let kql_failed = 0;
125
+ let sublime_indexed = 0;
126
+ let sublime_failed = 0;
127
+ let cql_hub_indexed = 0;
128
+ let cql_hub_failed = 0;
123
129
  let stories_indexed = 0;
124
130
  let stories_failed = 0;
125
131
  // Index Sigma rules
@@ -190,6 +196,34 @@ export function indexDetections(sigmaPaths, splunkPaths, storyPaths = [], elasti
190
196
  }
191
197
  }
192
198
  }
199
+ // Index Sublime Security rules (YAML with MQL source)
200
+ for (const basePath of sublimePaths) {
201
+ const files = findYamlFiles(basePath);
202
+ for (const file of files) {
203
+ const detection = parseSublimeFile(file);
204
+ if (detection) {
205
+ insertDetection(detection);
206
+ sublime_indexed++;
207
+ }
208
+ else {
209
+ sublime_failed++;
210
+ }
211
+ }
212
+ }
213
+ // Index CQL Hub queries (CrowdStrike Query Language)
214
+ for (const basePath of cqlHubPaths) {
215
+ const files = findYamlFiles(basePath);
216
+ for (const file of files) {
217
+ const detection = parseCqlHubFile(file);
218
+ if (detection) {
219
+ insertDetection(detection);
220
+ cql_hub_indexed++;
221
+ }
222
+ else {
223
+ cql_hub_failed++;
224
+ }
225
+ }
226
+ }
193
227
  // Index Splunk Analytic Stories (optional)
194
228
  for (const basePath of storyPaths) {
195
229
  const files = findYamlFiles(basePath);
@@ -213,9 +247,13 @@ export function indexDetections(sigmaPaths, splunkPaths, storyPaths = [], elasti
213
247
  elastic_failed,
214
248
  kql_indexed,
215
249
  kql_failed,
250
+ sublime_indexed,
251
+ sublime_failed,
252
+ cql_hub_indexed,
253
+ cql_hub_failed,
216
254
  stories_indexed,
217
255
  stories_failed,
218
- total: sigma_indexed + splunk_indexed + elastic_indexed + kql_indexed,
256
+ total: sigma_indexed + splunk_indexed + elastic_indexed + kql_indexed + sublime_indexed + cql_hub_indexed,
219
257
  };
220
258
  }
221
259
  export function needsIndexing() {
@@ -0,0 +1,2 @@
1
+ import type { Detection } from '../types.js';
2
+ export declare function parseCqlHubFile(filePath: string): Detection | null;
@@ -0,0 +1,302 @@
1
+ import { readFileSync } from 'fs';
2
+ import { parse as parseYaml } from 'yaml';
3
+ import { createHash } from 'crypto';
4
+ // MITRE technique ID to tactic(s) mapping (top-level techniques only)
5
+ // Some techniques belong to multiple tactics (e.g., T1078 -> initial-access & persistence)
6
+ const TECHNIQUE_TO_TACTICS = {
7
+ 'T1595': ['reconnaissance'], 'T1592': ['reconnaissance'], 'T1589': ['reconnaissance'],
8
+ 'T1590': ['reconnaissance'], 'T1591': ['reconnaissance'], 'T1598': ['reconnaissance'],
9
+ 'T1597': ['reconnaissance'], 'T1596': ['reconnaissance'], 'T1593': ['reconnaissance'],
10
+ 'T1594': ['reconnaissance'],
11
+ 'T1583': ['resource-development'], 'T1586': ['resource-development'], 'T1584': ['resource-development'],
12
+ 'T1587': ['resource-development'], 'T1585': ['resource-development'], 'T1588': ['resource-development'],
13
+ 'T1608': ['resource-development'],
14
+ 'T1189': ['initial-access'], 'T1190': ['initial-access'],
15
+ 'T1200': ['initial-access'], 'T1566': ['initial-access'],
16
+ 'T1195': ['initial-access'], 'T1199': ['initial-access'],
17
+ 'T1133': ['initial-access', 'persistence'],
18
+ 'T1078': ['initial-access', 'persistence', 'privilege-escalation', 'defense-evasion'],
19
+ 'T1091': ['initial-access', 'lateral-movement'],
20
+ 'T1059': ['execution'], 'T1203': ['execution'], 'T1559': ['execution'],
21
+ 'T1106': ['execution'], 'T1129': ['execution'],
22
+ 'T1204': ['execution'], 'T1047': ['execution'], 'T1569': ['execution'],
23
+ 'T1053': ['execution', 'persistence', 'privilege-escalation'],
24
+ 'T1098': ['persistence'], 'T1197': ['persistence'], 'T1547': ['persistence', 'privilege-escalation'],
25
+ 'T1037': ['persistence'], 'T1136': ['persistence'], 'T1543': ['persistence', 'privilege-escalation'],
26
+ 'T1546': ['persistence', 'privilege-escalation'], 'T1574': ['persistence', 'privilege-escalation'],
27
+ 'T1525': ['persistence'], 'T1556': ['persistence', 'credential-access', 'defense-evasion'],
28
+ 'T1137': ['persistence'], 'T1542': ['persistence', 'defense-evasion'],
29
+ 'T1505': ['persistence'], 'T1205': ['persistence', 'defense-evasion'],
30
+ 'T1548': ['privilege-escalation', 'defense-evasion'], 'T1134': ['privilege-escalation', 'defense-evasion'],
31
+ 'T1068': ['privilege-escalation'], 'T1484': ['privilege-escalation', 'defense-evasion'],
32
+ 'T1611': ['privilege-escalation'],
33
+ 'T1562': ['defense-evasion'], 'T1070': ['defense-evasion'], 'T1202': ['defense-evasion'],
34
+ 'T1036': ['defense-evasion'], 'T1055': ['defense-evasion', 'privilege-escalation'],
35
+ 'T1027': ['defense-evasion'], 'T1218': ['defense-evasion'], 'T1216': ['defense-evasion'],
36
+ 'T1220': ['defense-evasion'], 'T1140': ['defense-evasion'], 'T1112': ['defense-evasion'],
37
+ 'T1564': ['defense-evasion'],
38
+ 'T1003': ['credential-access'], 'T1110': ['credential-access'], 'T1555': ['credential-access'],
39
+ 'T1212': ['credential-access'], 'T1187': ['credential-access'], 'T1606': ['credential-access'],
40
+ 'T1056': ['credential-access', 'collection'], 'T1557': ['credential-access', 'collection'],
41
+ 'T1111': ['credential-access'], 'T1552': ['credential-access'], 'T1558': ['credential-access'],
42
+ 'T1539': ['credential-access'], 'T1528': ['credential-access'], 'T1649': ['credential-access'],
43
+ 'T1087': ['discovery'], 'T1010': ['discovery'], 'T1217': ['discovery'],
44
+ 'T1580': ['discovery'], 'T1538': ['discovery'], 'T1526': ['discovery'],
45
+ 'T1482': ['discovery'], 'T1083': ['discovery'], 'T1046': ['discovery'],
46
+ 'T1135': ['discovery'], 'T1040': ['discovery', 'credential-access'],
47
+ 'T1201': ['discovery'], 'T1120': ['discovery'], 'T1069': ['discovery'],
48
+ 'T1057': ['discovery'], 'T1012': ['discovery'], 'T1018': ['discovery'],
49
+ 'T1518': ['discovery'], 'T1082': ['discovery'], 'T1016': ['discovery'],
50
+ 'T1049': ['discovery'], 'T1033': ['discovery'], 'T1007': ['discovery'],
51
+ 'T1124': ['discovery'],
52
+ 'T1021': ['lateral-movement'], 'T1072': ['lateral-movement'],
53
+ 'T1080': ['lateral-movement'], 'T1550': ['lateral-movement', 'defense-evasion'],
54
+ 'T1563': ['lateral-movement'], 'T1570': ['lateral-movement'],
55
+ 'T1560': ['collection'], 'T1123': ['collection'], 'T1119': ['collection'],
56
+ 'T1115': ['collection'], 'T1530': ['collection'], 'T1602': ['collection'],
57
+ 'T1213': ['collection'], 'T1005': ['collection'], 'T1039': ['collection'],
58
+ 'T1025': ['collection'], 'T1074': ['collection'], 'T1114': ['collection'],
59
+ 'T1113': ['collection'], 'T1125': ['collection'],
60
+ 'T1071': ['command-and-control'], 'T1132': ['command-and-control'],
61
+ 'T1001': ['command-and-control'], 'T1568': ['command-and-control'],
62
+ 'T1573': ['command-and-control'], 'T1008': ['command-and-control'],
63
+ 'T1105': ['command-and-control'], 'T1104': ['command-and-control'],
64
+ 'T1095': ['command-and-control'], 'T1571': ['command-and-control'],
65
+ 'T1572': ['command-and-control'], 'T1090': ['command-and-control'],
66
+ 'T1219': ['command-and-control'], 'T1102': ['command-and-control'],
67
+ 'T1048': ['exfiltration'], 'T1041': ['exfiltration'], 'T1011': ['exfiltration'],
68
+ 'T1052': ['exfiltration'], 'T1567': ['exfiltration'], 'T1029': ['exfiltration'],
69
+ 'T1537': ['exfiltration'],
70
+ 'T1531': ['impact'], 'T1485': ['impact'], 'T1486': ['impact'],
71
+ 'T1565': ['impact'], 'T1491': ['impact'], 'T1561': ['impact'],
72
+ 'T1499': ['impact'], 'T1495': ['impact'], 'T1489': ['impact'],
73
+ 'T1490': ['impact'], 'T1498': ['impact'], 'T1496': ['impact'],
74
+ };
75
+ // Generate a stable ID from file path and rule name
76
+ function generateId(filePath, name) {
77
+ const hash = createHash('sha256')
78
+ .update(`${filePath}:${name}`)
79
+ .digest('hex')
80
+ .substring(0, 32);
81
+ return `crowdstrike-cql-${hash}`;
82
+ }
83
+ // Extract MITRE tactics from technique IDs
84
+ function extractMitreTactics(mitreIds) {
85
+ const tactics = new Set();
86
+ for (const id of mitreIds) {
87
+ // Get the parent technique ID (T1003.001 -> T1003)
88
+ const parentId = id.split('.')[0];
89
+ const mapped = TECHNIQUE_TO_TACTICS[parentId];
90
+ if (mapped) {
91
+ for (const tactic of mapped) {
92
+ tactics.add(tactic);
93
+ }
94
+ }
95
+ }
96
+ return [...tactics];
97
+ }
98
+ // Extract process names from CQL query text
99
+ function extractProcessNames(cql) {
100
+ const processNames = new Set();
101
+ // Match .exe references in CQL patterns like FileName=/cmd.exe/, ImageFileName=/\\mimikatz\.exe$/
102
+ const exeMatches = cql.match(/[\w.-]+\.exe/gi);
103
+ if (exeMatches) {
104
+ for (const match of exeMatches) {
105
+ const name = match.toLowerCase();
106
+ if (name.length > 4 && !name.includes('*')) {
107
+ processNames.add(name);
108
+ }
109
+ }
110
+ }
111
+ return [...processNames];
112
+ }
113
+ // Extract file paths from CQL query text
114
+ function extractFilePaths(cql) {
115
+ const filePaths = new Set();
116
+ const interestingPaths = [
117
+ 'C:\\Windows\\Temp', 'C:\\Windows\\System32', 'C:\\Windows\\SysWOW64',
118
+ 'C:\\ProgramData', 'C:\\Users\\Public', '\\AppData\\Local\\Temp',
119
+ '\\AppData\\Roaming',
120
+ ];
121
+ const cqlLower = cql.toLowerCase();
122
+ for (const path of interestingPaths) {
123
+ if (cqlLower.includes(path.toLowerCase())) {
124
+ filePaths.add(path);
125
+ }
126
+ }
127
+ if (cqlLower.includes('\\temp\\') || cqlLower.includes('\\tmp\\')) {
128
+ filePaths.add('Temp directory');
129
+ }
130
+ return [...filePaths];
131
+ }
132
+ // Extract registry paths from CQL query text
133
+ function extractRegistryPaths(cql) {
134
+ const registryPaths = new Set();
135
+ const cqlLower = cql.toLowerCase();
136
+ if (cqlLower.includes('hklm') || cqlLower.includes('hkey_local_machine')) {
137
+ registryPaths.add('HKLM registry');
138
+ }
139
+ if (cqlLower.includes('hkcu') || cqlLower.includes('hkey_current_user')) {
140
+ registryPaths.add('HKCU registry');
141
+ }
142
+ if (cqlLower.includes('\\run\\') || cqlLower.includes('\\runonce\\')) {
143
+ registryPaths.add('Run/RunOnce keys');
144
+ }
145
+ if (cqlLower.includes('\\services\\')) {
146
+ registryPaths.add('Services registry');
147
+ }
148
+ return [...registryPaths];
149
+ }
150
+ // Extract CVE IDs from name and description
151
+ function extractCves(text) {
152
+ const matches = text.match(/CVE-\d{4}-\d+/gi);
153
+ if (!matches)
154
+ return [];
155
+ return [...new Set(matches.map(m => m.toUpperCase()))];
156
+ }
157
+ // Infer platforms from CQL query and log_sources
158
+ function extractPlatforms(cql, logSources) {
159
+ const platforms = new Set();
160
+ // Check event_platform in CQL
161
+ if (/event_platform\s*=\s*"?Win/i.test(cql))
162
+ platforms.add('windows');
163
+ if (/event_platform\s*=\s*"?Mac/i.test(cql))
164
+ platforms.add('macos');
165
+ if (/event_platform\s*=\s*"?Lin/i.test(cql))
166
+ platforms.add('linux');
167
+ // Infer from log_sources
168
+ for (const src of logSources) {
169
+ const lower = src.toLowerCase();
170
+ if (lower === 'endpoint') {
171
+ // Endpoint could be any OS; only add if no specific platform found
172
+ if (platforms.size === 0) {
173
+ platforms.add('windows');
174
+ platforms.add('linux');
175
+ }
176
+ }
177
+ else if (lower === 'network') {
178
+ platforms.add('network');
179
+ }
180
+ else if (lower === 'cloud' || lower.includes('aws') || lower.includes('azure') || lower.includes('gcp')) {
181
+ platforms.add('cloud');
182
+ }
183
+ }
184
+ return [...platforms];
185
+ }
186
+ // Map log_sources to asset type
187
+ function deriveAssetType(logSources) {
188
+ if (!logSources || logSources.length === 0)
189
+ return null;
190
+ const first = logSources[0].toLowerCase();
191
+ if (first === 'endpoint')
192
+ return 'Endpoint';
193
+ if (first === 'network')
194
+ return 'Network';
195
+ if (first === 'cloud' || first.includes('aws') || first.includes('azure') || first.includes('gcp'))
196
+ return 'Cloud';
197
+ if (first === 'identity' || first === 'idp')
198
+ return 'Endpoint';
199
+ return null;
200
+ }
201
+ // Map log_sources to security domain
202
+ function deriveSecurityDomain(logSources) {
203
+ if (!logSources || logSources.length === 0)
204
+ return null;
205
+ const first = logSources[0].toLowerCase();
206
+ if (first === 'endpoint')
207
+ return 'endpoint';
208
+ if (first === 'network')
209
+ return 'network';
210
+ if (first === 'cloud' || first.includes('aws') || first.includes('azure') || first.includes('gcp'))
211
+ return 'cloud';
212
+ if (first === 'identity' || first === 'idp')
213
+ return 'access';
214
+ return null;
215
+ }
216
+ // Map tags to detection type
217
+ function deriveDetectionType(tags) {
218
+ for (const tag of tags) {
219
+ const lower = tag.toLowerCase();
220
+ if (lower === 'hunting')
221
+ return 'Hunting';
222
+ if (lower === 'detection')
223
+ return 'TTP';
224
+ if (lower === 'anomaly')
225
+ return 'Anomaly';
226
+ if (lower === 'correlation')
227
+ return 'Correlation';
228
+ }
229
+ return null;
230
+ }
231
+ export function parseCqlHubFile(filePath) {
232
+ try {
233
+ const content = readFileSync(filePath, 'utf-8');
234
+ const rule = parseYaml(content);
235
+ // name and cql are required
236
+ if (!rule.name || !rule.cql) {
237
+ return null;
238
+ }
239
+ const id = generateId(filePath, rule.name);
240
+ const mitreIds = rule.mitre_ids || [];
241
+ const logSources = rule.log_sources || [];
242
+ const tags = rule.tags || [];
243
+ const csModules = rule.cs_required_modules || [];
244
+ // Build description: include explanation if present
245
+ let description = rule.description || '';
246
+ if (rule.explanation) {
247
+ description = description
248
+ ? `${description}\n\n${rule.explanation}`
249
+ : rule.explanation;
250
+ }
251
+ // Combine text fields for CVE extraction
252
+ const combinedText = `${rule.name} ${rule.description || ''}`;
253
+ // Build enriched tags: include cs_required_modules as prefixed tags
254
+ const enrichedTags = [
255
+ ...tags,
256
+ ...csModules.map(m => `cs_module:${m}`),
257
+ ];
258
+ const detection = {
259
+ id,
260
+ name: rule.name,
261
+ description,
262
+ query: rule.cql,
263
+ source_type: 'crowdstrike_cql',
264
+ mitre_ids: mitreIds,
265
+ logsource_category: logSources.length > 0 ? logSources[0].toLowerCase() : null,
266
+ logsource_product: 'crowdstrike',
267
+ logsource_service: 'falcon_logscale',
268
+ severity: null,
269
+ status: null,
270
+ author: rule.author || null,
271
+ date_created: null,
272
+ date_modified: null,
273
+ references: [],
274
+ falsepositives: [],
275
+ tags: enrichedTags,
276
+ file_path: filePath,
277
+ raw_yaml: content,
278
+ cves: extractCves(combinedText),
279
+ analytic_stories: [],
280
+ data_sources: logSources,
281
+ detection_type: deriveDetectionType(tags),
282
+ asset_type: deriveAssetType(logSources),
283
+ security_domain: deriveSecurityDomain(logSources),
284
+ process_names: extractProcessNames(rule.cql),
285
+ file_paths: extractFilePaths(rule.cql),
286
+ registry_paths: extractRegistryPaths(rule.cql),
287
+ mitre_tactics: extractMitreTactics(mitreIds),
288
+ platforms: extractPlatforms(rule.cql, logSources),
289
+ kql_category: null,
290
+ kql_tags: [],
291
+ kql_keywords: [],
292
+ sublime_attack_types: [],
293
+ sublime_detection_methods: [],
294
+ sublime_tactics: [],
295
+ };
296
+ return detection;
297
+ }
298
+ catch {
299
+ // Skip files that can't be parsed
300
+ return null;
301
+ }
302
+ }
@@ -235,6 +235,9 @@ export function parseElasticFile(filePath) {
235
235
  kql_category: null,
236
236
  kql_tags: [],
237
237
  kql_keywords: [],
238
+ sublime_attack_types: [],
239
+ sublime_detection_methods: [],
240
+ sublime_tactics: [],
238
241
  };
239
242
  return detection;
240
243
  }
@@ -341,6 +341,9 @@ export function parseRawKqlFile(filePath, basePath) {
341
341
  kql_category: category,
342
342
  kql_tags: tags,
343
343
  kql_keywords: keywords,
344
+ sublime_attack_types: [],
345
+ sublime_detection_methods: [],
346
+ sublime_tactics: [],
344
347
  };
345
348
  return detection;
346
349
  }
@@ -415,6 +418,9 @@ export function parseKqlFile(filePath, basePath) {
415
418
  kql_category: category,
416
419
  kql_tags: tags,
417
420
  kql_keywords: keywords,
421
+ sublime_attack_types: [],
422
+ sublime_detection_methods: [],
423
+ sublime_tactics: [],
418
424
  };
419
425
  return detection;
420
426
  }
@@ -388,6 +388,9 @@ export function parseSigmaFile(filePath) {
388
388
  kql_category: null,
389
389
  kql_tags: [],
390
390
  kql_keywords: [],
391
+ sublime_attack_types: [],
392
+ sublime_detection_methods: [],
393
+ sublime_tactics: [],
391
394
  };
392
395
  return detection;
393
396
  }
@@ -182,6 +182,9 @@ export function parseSplunkFile(filePath) {
182
182
  kql_category: null,
183
183
  kql_tags: [],
184
184
  kql_keywords: [],
185
+ sublime_attack_types: [],
186
+ sublime_detection_methods: [],
187
+ sublime_tactics: [],
185
188
  };
186
189
  return detection;
187
190
  }
@@ -0,0 +1,2 @@
1
+ import type { Detection } from '../types.js';
2
+ export declare function parseSublimeFile(filePath: string): Detection | null;
@@ -0,0 +1,106 @@
1
+ import { readFileSync } from 'fs';
2
+ import { parse as parseYaml } from 'yaml';
3
+ import { createHash } from 'crypto';
4
+ // Best-effort mapping from Sublime tactics_and_techniques to MITRE ATT&CK tactics
5
+ const TACTICS_MAP = {
6
+ 'evasion': 'defense-evasion',
7
+ 'exploit': 'execution',
8
+ 'social engineering': 'initial-access',
9
+ 'impersonation: brand': 'initial-access',
10
+ 'impersonation: employee': 'initial-access',
11
+ 'impersonation: vip': 'initial-access',
12
+ 'lookalike domain': 'initial-access',
13
+ 'spoofing': 'initial-access',
14
+ 'scripting': 'execution',
15
+ 'macros': 'execution',
16
+ 'encryption': 'defense-evasion',
17
+ };
18
+ // Generate a stable ID from file path and rule name
19
+ function generateId(filePath, name) {
20
+ const hash = createHash('sha256')
21
+ .update(`${filePath}:${name}`)
22
+ .digest('hex')
23
+ .substring(0, 32);
24
+ return `sublime-${hash}`;
25
+ }
26
+ // Extract author names from the authors array
27
+ function extractAuthorNames(authors) {
28
+ if (!authors || authors.length === 0)
29
+ return null;
30
+ const names = authors
31
+ .map(a => a.name || a.twitter || a.github || 'Unknown')
32
+ .filter(Boolean);
33
+ return names.length > 0 ? names.join(', ') : null;
34
+ }
35
+ // Map Sublime tactics to MITRE ATT&CK tactics (best-effort)
36
+ function mapToMitreTactics(tactics) {
37
+ if (!tactics)
38
+ return [];
39
+ const mitreTactics = new Set();
40
+ for (const tactic of tactics) {
41
+ const mapped = TACTICS_MAP[tactic.toLowerCase()];
42
+ if (mapped) {
43
+ mitreTactics.add(mapped);
44
+ }
45
+ }
46
+ return [...mitreTactics];
47
+ }
48
+ export function parseSublimeFile(filePath) {
49
+ try {
50
+ const content = readFileSync(filePath, 'utf-8');
51
+ const rule = parseYaml(content);
52
+ // name and source are required
53
+ if (!rule.name || !rule.source) {
54
+ return null;
55
+ }
56
+ // type must be 'rule' or 'exclusion'
57
+ if (rule.type !== 'rule' && rule.type !== 'exclusion') {
58
+ return null;
59
+ }
60
+ const id = rule.id || generateId(filePath, rule.name);
61
+ const detection = {
62
+ id,
63
+ name: rule.name,
64
+ description: rule.description || '',
65
+ query: rule.source,
66
+ source_type: 'sublime',
67
+ mitre_ids: [],
68
+ logsource_category: 'email',
69
+ logsource_product: 'email',
70
+ logsource_service: null,
71
+ severity: rule.severity || null,
72
+ status: null,
73
+ author: extractAuthorNames(rule.authors),
74
+ date_created: null,
75
+ date_modified: null,
76
+ references: rule.references || [],
77
+ falsepositives: rule.false_positives || [],
78
+ tags: rule.tags || [],
79
+ file_path: filePath,
80
+ raw_yaml: content,
81
+ cves: [],
82
+ analytic_stories: [],
83
+ data_sources: ['Email Messages', 'Email Headers', 'Email Attachments'],
84
+ detection_type: rule.type === 'exclusion' ? 'Exclusion' : 'Rule',
85
+ asset_type: 'Email',
86
+ security_domain: 'access',
87
+ process_names: [],
88
+ file_paths: [],
89
+ registry_paths: [],
90
+ mitre_tactics: mapToMitreTactics(rule.tactics_and_techniques),
91
+ platforms: ['email'],
92
+ kql_category: null,
93
+ kql_tags: [],
94
+ kql_keywords: [],
95
+ // Sublime-specific fields
96
+ sublime_attack_types: rule.attack_types || [],
97
+ sublime_detection_methods: rule.detection_methods || [],
98
+ sublime_tactics: rule.tactics_and_techniques || [],
99
+ };
100
+ return detection;
101
+ }
102
+ catch {
103
+ // Skip files that can't be parsed
104
+ return null;
105
+ }
106
+ }
@@ -182,7 +182,7 @@ function getTacticResource(tactic) {
182
182
  * Get statistics and sample detections for a source type
183
183
  */
184
184
  function getSourceResource(sourceType) {
185
- const validSources = ['sigma', 'splunk_escu', 'elastic', 'kql'];
185
+ const validSources = ['sigma', 'splunk_escu', 'elastic', 'kql', 'sublime', 'crowdstrike_cql'];
186
186
  const normalizedSource = sourceType.toLowerCase();
187
187
  if (!validSources.includes(normalizedSource)) {
188
188
  return {
@@ -23,7 +23,7 @@ export const analysisTools = [
23
23
  properties: {
24
24
  source_type: {
25
25
  type: 'string',
26
- enum: ['sigma', 'splunk_escu', 'elastic', 'kql'],
26
+ enum: ['sigma', 'splunk_escu', 'elastic', 'kql', 'sublime', 'crowdstrike_cql'],
27
27
  description: 'Filter by source type',
28
28
  },
29
29
  tactic: {
@@ -66,7 +66,7 @@ export const analysisTools = [
66
66
  properties: {
67
67
  source_type: {
68
68
  type: 'string',
69
- enum: ['sigma', 'splunk_escu', 'elastic', 'kql'],
69
+ enum: ['sigma', 'splunk_escu', 'elastic', 'kql', 'sublime', 'crowdstrike_cql'],
70
70
  description: 'Filter by source type (optional - analyzes all if not specified)',
71
71
  },
72
72
  },
@@ -90,7 +90,7 @@ export const analysisTools = [
90
90
  },
91
91
  source_type: {
92
92
  type: 'string',
93
- enum: ['sigma', 'splunk_escu', 'elastic', 'kql'],
93
+ enum: ['sigma', 'splunk_escu', 'elastic', 'kql', 'sublime', 'crowdstrike_cql'],
94
94
  description: 'Filter by source type (optional)',
95
95
  },
96
96
  },
@@ -124,7 +124,7 @@ export const analysisTools = [
124
124
  },
125
125
  source_type: {
126
126
  type: 'string',
127
- enum: ['sigma', 'splunk_escu', 'elastic', 'kql'],
127
+ enum: ['sigma', 'splunk_escu', 'elastic', 'kql', 'sublime', 'crowdstrike_cql'],
128
128
  description: 'Filter by source type (optional)',
129
129
  },
130
130
  },
@@ -15,7 +15,7 @@ export const comparisonTools = [
15
15
  },
16
16
  source_type: {
17
17
  type: 'string',
18
- enum: ['sigma', 'splunk_escu', 'elastic', 'kql'],
18
+ enum: ['sigma', 'splunk_escu', 'elastic', 'kql', 'sublime', 'crowdstrike_cql'],
19
19
  description: 'Optional: filter by source type',
20
20
  },
21
21
  limit: {
@@ -147,7 +147,7 @@ export const comparisonTools = [
147
147
  },
148
148
  source_type: {
149
149
  type: 'string',
150
- enum: ['sigma', 'splunk_escu', 'elastic', 'kql'],
150
+ enum: ['sigma', 'splunk_escu', 'elastic', 'kql', 'sublime', 'crowdstrike_cql'],
151
151
  description: 'Optional: filter to specific source',
152
152
  },
153
153
  },
@@ -235,7 +235,7 @@ export const comparisonTools = [
235
235
  properties: {
236
236
  source_type: {
237
237
  type: 'string',
238
- enum: ['sigma', 'splunk_escu', 'elastic', 'kql'],
238
+ enum: ['sigma', 'splunk_escu', 'elastic', 'kql', 'sublime', 'crowdstrike_cql'],
239
239
  description: 'Filter by source type (optional)',
240
240
  },
241
241
  },
@@ -10,7 +10,7 @@ export const filterTools = [
10
10
  properties: {
11
11
  source_type: {
12
12
  type: 'string',
13
- enum: ['sigma', 'splunk_escu', 'elastic', 'kql'],
13
+ enum: ['sigma', 'splunk_escu', 'elastic', 'kql', 'sublime', 'crowdstrike_cql'],
14
14
  description: 'Source type to filter by',
15
15
  },
16
16
  limit: {
@@ -22,7 +22,7 @@ export const searchTools = [
22
22
  },
23
23
  source_type: {
24
24
  type: 'string',
25
- enum: ['sigma', 'splunk_escu', 'elastic', 'kql'],
25
+ enum: ['sigma', 'splunk_escu', 'elastic', 'kql', 'sublime', 'crowdstrike_cql'],
26
26
  description: 'Filter results by detection source type',
27
27
  },
28
28
  },
@@ -27,7 +27,7 @@ const getQueryPatternsTool = defineTool({
27
27
  },
28
28
  source_type: {
29
29
  type: 'string',
30
- enum: ['sigma', 'splunk_escu', 'elastic', 'kql'],
30
+ enum: ['sigma', 'splunk_escu', 'elastic', 'kql', 'sublime', 'crowdstrike_cql'],
31
31
  description: 'Filter patterns by source type (optional)',
32
32
  },
33
33
  },
@@ -175,7 +175,7 @@ const findSimilarDetectionsTool = defineTool({
175
175
  },
176
176
  source_type: {
177
177
  type: 'string',
178
- enum: ['sigma', 'splunk_escu', 'elastic', 'kql'],
178
+ enum: ['sigma', 'splunk_escu', 'elastic', 'kql', 'sublime', 'crowdstrike_cql'],
179
179
  description: 'Filter by source type (optional)',
180
180
  },
181
181
  limit: {