security-detections-mcp 3.1.0 → 3.2.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.
@@ -175,10 +175,28 @@ export function listBySource(sourceType, limit = 100, offset = 0) {
175
175
  */
176
176
  export function listByMitre(techniqueId, limit = 100, offset = 0) {
177
177
  const database = getDb();
178
+ // Try junction table first (indexed join — faster)
179
+ try {
180
+ const check = database.prepare('SELECT 1 FROM detection_techniques LIMIT 1').get();
181
+ if (check) {
182
+ const rows = database.prepare(`
183
+ SELECT d.* FROM detections d
184
+ JOIN detection_techniques dt ON d.id = dt.detection_id
185
+ WHERE dt.technique_id = ?
186
+ ORDER BY d.name
187
+ LIMIT ? OFFSET ?
188
+ `).all(techniqueId, limit, offset);
189
+ return rows.map(rowToDetection);
190
+ }
191
+ }
192
+ catch {
193
+ // Junction table may not exist yet — fall through to LIKE query
194
+ }
195
+ // Fallback: original LIKE-based query
178
196
  const stmt = database.prepare(`
179
- SELECT * FROM detections
180
- WHERE mitre_ids LIKE ?
181
- ORDER BY name
197
+ SELECT * FROM detections
198
+ WHERE mitre_ids LIKE ?
199
+ ORDER BY name
182
200
  LIMIT ? OFFSET ?
183
201
  `);
184
202
  const rows = stmt.all(`%"${techniqueId}"%`, limit, offset);
@@ -624,13 +642,42 @@ export function analyzeCoverage(sourceType) {
624
642
  }
625
643
  }
626
644
  }
627
- const tacticTotals = {
628
- 'reconnaissance': 10, 'resource-development': 8, 'initial-access': 10,
629
- 'execution': 14, 'persistence': 20, 'privilege-escalation': 14,
630
- 'defense-evasion': 43, 'credential-access': 17, 'discovery': 31,
631
- 'lateral-movement': 9, 'collection': 17, 'command-and-control': 18,
632
- 'exfiltration': 9, 'impact': 14
633
- };
645
+ // Try dynamic tactic totals from technique_tactics table (populated from STIX or detections)
646
+ let tacticTotals;
647
+ try {
648
+ const ttRows = database.prepare('SELECT tactic_name, COUNT(DISTINCT technique_id) as count FROM technique_tactics GROUP BY tactic_name').all();
649
+ if (ttRows.length > 0) {
650
+ tacticTotals = {};
651
+ for (const row of ttRows) {
652
+ tacticTotals[row.tactic_name] = row.count;
653
+ }
654
+ // Ensure all 14 tactics are present
655
+ for (const t of allTactics) {
656
+ if (!tacticTotals[t])
657
+ tacticTotals[t] = 1;
658
+ }
659
+ }
660
+ else {
661
+ // Fallback to hardcoded if junction table is empty
662
+ tacticTotals = {
663
+ 'reconnaissance': 10, 'resource-development': 8, 'initial-access': 10,
664
+ 'execution': 14, 'persistence': 20, 'privilege-escalation': 14,
665
+ 'defense-evasion': 43, 'credential-access': 17, 'discovery': 31,
666
+ 'lateral-movement': 9, 'collection': 17, 'command-and-control': 18,
667
+ 'exfiltration': 9, 'impact': 14
668
+ };
669
+ }
670
+ }
671
+ catch {
672
+ // Table may not exist — fallback to hardcoded
673
+ tacticTotals = {
674
+ 'reconnaissance': 10, 'resource-development': 8, 'initial-access': 10,
675
+ 'execution': 14, 'persistence': 20, 'privilege-escalation': 14,
676
+ 'defense-evasion': 43, 'credential-access': 17, 'discovery': 31,
677
+ 'lateral-movement': 9, 'collection': 17, 'command-and-control': 18,
678
+ 'exfiltration': 9, 'impact': 14
679
+ };
680
+ }
634
681
  const coverageByTactic = {};
635
682
  for (const tactic of allTactics) {
636
683
  const covered = tacticCounts[tactic].size;
@@ -745,12 +792,47 @@ export function suggestDetections(techniqueId, sourceType) {
745
792
  * Generate an ATT&CK Navigator layer from detection coverage.
746
793
  */
747
794
  export function generateNavigatorLayer(options) {
748
- const techniqueIds = getTechniqueIds({
795
+ const database = getDb();
796
+ // If actor_name is specified, filter to only that actor's techniques
797
+ let actorTechniqueFilter = null;
798
+ if (options.actor_name) {
799
+ try {
800
+ // Dynamic import would be circular — query directly
801
+ const actorRow = database.prepare('SELECT actor_id FROM attack_actors WHERE name = ? COLLATE NOCASE').get(options.actor_name);
802
+ if (!actorRow) {
803
+ // Try alias match
804
+ const aliasRow = database.prepare("SELECT actor_id FROM attack_actors WHERE aliases LIKE ? COLLATE NOCASE").get(`%"${options.actor_name}"%`);
805
+ if (aliasRow) {
806
+ const actorTechs = database.prepare('SELECT technique_id FROM actor_techniques WHERE actor_id = ?').all(aliasRow.actor_id);
807
+ actorTechniqueFilter = new Set(actorTechs.map(r => r.technique_id));
808
+ }
809
+ }
810
+ else {
811
+ const actorTechs = database.prepare('SELECT technique_id FROM actor_techniques WHERE actor_id = ?').all(actorRow.actor_id);
812
+ actorTechniqueFilter = new Set(actorTechs.map(r => r.technique_id));
813
+ }
814
+ }
815
+ catch {
816
+ // STIX tables may not exist — ignore actor filter
817
+ }
818
+ }
819
+ let techniqueIds = getTechniqueIds({
749
820
  source_type: options.source_type,
750
821
  tactic: options.tactic,
751
822
  severity: options.severity,
752
823
  });
753
- const database = getDb();
824
+ // For actor layers, include ALL actor techniques (even uncovered ones)
825
+ if (actorTechniqueFilter) {
826
+ const coveredSet = new Set(techniqueIds);
827
+ // Merge: keep covered ones + add uncovered actor techniques
828
+ for (const actorTech of actorTechniqueFilter) {
829
+ if (!coveredSet.has(actorTech)) {
830
+ techniqueIds.push(actorTech);
831
+ }
832
+ }
833
+ // Filter to only actor techniques
834
+ techniqueIds = techniqueIds.filter(t => actorTechniqueFilter.has(t));
835
+ }
754
836
  const techniques = [];
755
837
  function getColorForScore(score) {
756
838
  if (score >= 80)
@@ -776,11 +858,13 @@ export function generateNavigatorLayer(options) {
776
858
  }
777
859
  const count = database.prepare(countSql).get(...countParams).count;
778
860
  const score = Math.min(count * 20, 100);
861
+ // For actor layers, uncovered techniques show as red with score 0
862
+ const isActorGap = actorTechniqueFilter && count === 0;
779
863
  techniques.push({
780
864
  techniqueID: techId,
781
865
  score,
782
- comment: `${count} detection(s)`,
783
- color: getColorForScore(score),
866
+ comment: isActorGap ? 'GAP - no detections' : `${count} detection(s)`,
867
+ color: isActorGap ? '#ff6666' : getColorForScore(score),
784
868
  enabled: true,
785
869
  showSubtechniques: false,
786
870
  });
@@ -788,8 +872,8 @@ export function generateNavigatorLayer(options) {
788
872
  return {
789
873
  name: options.name,
790
874
  versions: {
791
- attack: '18',
792
- navigator: '5.1.0',
875
+ attack: '18.1',
876
+ navigator: '5.3.1',
793
877
  layer: '4.5',
794
878
  },
795
879
  domain: 'enterprise-attack',
@@ -815,6 +899,243 @@ export function generateNavigatorLayer(options) {
815
899
  };
816
900
  }
817
901
  // =============================================================================
902
+ // PROCEDURE AUTO-EXTRACTION
903
+ // =============================================================================
904
+ const BEHAVIOR_KEYWORDS = [
905
+ 'encoded command', 'base64', 'download', 'credential', 'dump', 'inject', 'hollow',
906
+ 'obfuscat', 'bypass', 'evasion', 'persist', 'lateral', 'remote', 'scheduled task',
907
+ 'service', 'registry', 'startup', 'script block', 'amsi', 'wmi', 'powershell',
908
+ 'mimikatz', 'lsass', 'shadow cop', 'ransomware', 'encrypt', 'exfiltrat', 'tunnel',
909
+ 'beacon', 'c2', 'command and control', 'brute force', 'spray', 'kerbero',
910
+ 'pass the hash', 'pass the ticket', 'golden ticket', 'dcsync', 'ntds',
911
+ 'dll side', 'dll hijack', 'process access', 'remote thread', 'token', 'impersonat',
912
+ 'privilege', 'parent process', 'child process', 'masquerad', 'macro', 'office',
913
+ 'phish', 'attachment', 'email', 'dns', 'http', 'clear log', 'event log', 'tamper',
914
+ 'suspicious', 'anomal', 'unusual', 'cloud', 'aws', 'azure', 'container', 'kubernetes',
915
+ 'api', 'oauth', 'saml', 'driver', 'kernel', 'named pipe', 'shellcode', 'reflection',
916
+ ];
917
+ function categorizeDetection(text, logsourceCategory) {
918
+ if (logsourceCategory === 'process_creation' || (text.includes('process') && text.includes('creat')))
919
+ return 'process_creation_monitoring';
920
+ if (logsourceCategory === 'process_access' || text.includes('process access'))
921
+ return 'process_access_monitoring';
922
+ if (logsourceCategory.includes('registry') || text.includes('registry'))
923
+ return 'registry_monitoring';
924
+ if (logsourceCategory.includes('file') || text.includes('file creat') || text.includes('file modif'))
925
+ return 'file_monitoring';
926
+ if (logsourceCategory === 'network_connection' || text.includes('network') || text.includes('connection'))
927
+ return 'network_connection_monitoring';
928
+ if (logsourceCategory === 'image_load' || text.includes('image load') || text.includes('dll load'))
929
+ return 'module_load_monitoring';
930
+ if (text.includes('commandline') || text.includes('command line'))
931
+ return 'command_line_monitoring';
932
+ if (text.includes('script'))
933
+ return 'script_execution_monitoring';
934
+ if (text.includes('authenti') || text.includes('logon') || text.includes('login'))
935
+ return 'authentication_monitoring';
936
+ if (text.includes('service') && (text.includes('creat') || text.includes('install')))
937
+ return 'service_monitoring';
938
+ if (text.includes('email') || text.includes('phish') || text.includes('spam'))
939
+ return 'email_security';
940
+ if (text.includes('cloud') || text.includes('aws') || text.includes('azure'))
941
+ return 'cloud_monitoring';
942
+ if (text.includes('driver') || text.includes('kernel'))
943
+ return 'kernel_monitoring';
944
+ return 'general_monitoring';
945
+ }
946
+ /**
947
+ * Auto-extract procedure reference data from detections for a technique.
948
+ * Clusters detections by behavioral category and generates procedure entries.
949
+ */
950
+ export function autoExtractProcedures(techniqueId) {
951
+ const database = getDb();
952
+ const detections = listByMitre(techniqueId, 1000);
953
+ if (detections.length === 0)
954
+ return { technique_id: techniqueId, procedures_generated: 0, detection_count: 0 };
955
+ // Extract keyword frequencies
956
+ const kwFreq = new Map();
957
+ for (const d of detections) {
958
+ const desc = (d.description || '').toLowerCase();
959
+ for (const kw of BEHAVIOR_KEYWORDS) {
960
+ if (desc.includes(kw))
961
+ kwFreq.set(kw, (kwFreq.get(kw) || 0) + 1);
962
+ }
963
+ }
964
+ // Group detections by category + dominant keyword
965
+ const groups = {};
966
+ for (const d of detections) {
967
+ const text = ((d.description || '') + ' ' + (d.query || '') + ' ' + (d.name || '')).toLowerCase();
968
+ const category = categorizeDetection(text, d.logsource_category || '');
969
+ let bestKw = null;
970
+ const desc = (d.description || '').toLowerCase();
971
+ for (const [kw, count] of kwFreq.entries()) {
972
+ if (desc.includes(kw) && count >= 2 && count < detections.length * 0.8) {
973
+ if (!bestKw || (kwFreq.get(bestKw) || 0) > count)
974
+ bestKw = kw;
975
+ }
976
+ }
977
+ const key = `${category}:${bestKw || 'general'}`;
978
+ if (!groups[key])
979
+ groups[key] = { category, keyword: bestKw, names: [], processNames: new Set(), keywords: new Set() };
980
+ const g = groups[key];
981
+ g.names.push(d.name);
982
+ for (const pn of d.process_names || [])
983
+ g.processNames.add(pn.toLowerCase());
984
+ if (bestKw)
985
+ g.keywords.add(bestKw);
986
+ }
987
+ // Convert to procedure entries and store
988
+ database.exec(`DELETE FROM procedure_reference WHERE technique_id = '${techniqueId.replace(/'/g, "''")}' AND source = 'auto'`);
989
+ const insertStmt = database.prepare(`INSERT OR REPLACE INTO procedure_reference (id, technique_id, name, category, description, source, indicators, detection_count, confidence)
990
+ VALUES (?, ?, ?, ?, ?, 'auto', ?, ?, ?)`);
991
+ let idx = 0;
992
+ for (const [, g] of Object.entries(groups)) {
993
+ const name = g.keyword
994
+ ? g.keyword.split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')
995
+ : g.category.replace(/_/g, ' ').split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
996
+ const indicators = {};
997
+ if (g.processNames.size > 0)
998
+ indicators.process_names = [...g.processNames].slice(0, 10);
999
+ if (g.keywords.size > 0)
1000
+ indicators.description_keywords = [...g.keywords].slice(0, 10);
1001
+ // Extract command patterns from queries
1002
+ const cmdPats = new Set();
1003
+ const groupDets = detections.filter(det => g.names.includes(det.name));
1004
+ for (const d of groupDets) {
1005
+ if (d.query) {
1006
+ const literals = d.query.toLowerCase().match(/['"]([^'"]{3,40})['"]/g);
1007
+ if (literals) {
1008
+ for (const lit of literals.slice(0, 10)) {
1009
+ const val = lit.replace(/['"]/g, '').trim();
1010
+ if (val.length >= 3 && !/^(and|or|not|true|false|null|none|string|type|object|select|from|where|index|name|value|count|table|status|data|endpoint|query|result|search|action|field|source|level|any|all|list|set|process|rule|detection|sigma)$/i.test(val)) {
1011
+ cmdPats.add(val);
1012
+ }
1013
+ }
1014
+ }
1015
+ }
1016
+ }
1017
+ if (cmdPats.size > 0)
1018
+ indicators.command_patterns = [...cmdPats].slice(0, 15);
1019
+ const confidence = g.names.length >= 10 ? 0.9 : g.names.length >= 5 ? 0.7 : g.names.length >= 3 ? 0.5 : 0.3;
1020
+ insertStmt.run(`${techniqueId}-auto-${idx++}`, techniqueId, name, g.category, `Auto-extracted: ${g.names.length} detections for ${g.keyword || g.category.replace(/_/g, ' ')}`, JSON.stringify(indicators), g.names.length, confidence);
1021
+ }
1022
+ // Fallback: if clustering produced nothing, create a single procedure from all detections
1023
+ if (idx === 0 && detections.length > 0) {
1024
+ const allProcessNames = new Set();
1025
+ const allKeywords = new Set();
1026
+ for (const d of detections) {
1027
+ for (const pn of d.process_names || [])
1028
+ allProcessNames.add(pn.toLowerCase());
1029
+ const desc = (d.description || '').toLowerCase();
1030
+ for (const kw of BEHAVIOR_KEYWORDS) {
1031
+ if (desc.includes(kw))
1032
+ allKeywords.add(kw);
1033
+ }
1034
+ }
1035
+ const fallbackIndicators = {};
1036
+ if (allProcessNames.size > 0)
1037
+ fallbackIndicators.process_names = [...allProcessNames].slice(0, 10);
1038
+ if (allKeywords.size > 0)
1039
+ fallbackIndicators.description_keywords = [...allKeywords].slice(0, 10);
1040
+ insertStmt.run(`${techniqueId}-auto-0`, techniqueId, `${techniqueId} Detection`, detections[0]?.logsource_category || 'general_monitoring', `Auto-extracted: ${detections.length} detection(s)`, JSON.stringify(fallbackIndicators), detections.length, detections.length >= 3 ? 0.5 : 0.2);
1041
+ idx = 1;
1042
+ }
1043
+ return { technique_id: techniqueId, procedures_generated: idx, detection_count: detections.length };
1044
+ }
1045
+ /**
1046
+ * Extract procedures for ALL techniques in the database.
1047
+ * Loads hand-curated procedures first, then auto-extracts for the rest.
1048
+ * Called after indexing completes.
1049
+ */
1050
+ export function extractAllProcedures() {
1051
+ const database = getDb();
1052
+ // Ensure table exists
1053
+ database.exec(`
1054
+ CREATE TABLE IF NOT EXISTS procedure_reference (
1055
+ id TEXT PRIMARY KEY, technique_id TEXT NOT NULL, name TEXT NOT NULL,
1056
+ category TEXT NOT NULL, description TEXT NOT NULL, source TEXT NOT NULL DEFAULT 'auto',
1057
+ indicators TEXT NOT NULL, detection_count INTEGER DEFAULT 0,
1058
+ confidence REAL DEFAULT 1.0, created_at TEXT DEFAULT CURRENT_TIMESTAMP
1059
+ )
1060
+ `);
1061
+ database.exec(`CREATE INDEX IF NOT EXISTS idx_proc_ref_technique ON procedure_reference(technique_id)`);
1062
+ // Load hand-curated procedures
1063
+ let handCuratedCount = 0;
1064
+ try {
1065
+ // Dynamic import isn't available in this context, so we use require-style
1066
+ // The hand-curated data is imported statically by the tools layer
1067
+ // Here we just ensure we don't overwrite hand-curated entries
1068
+ database.exec(`DELETE FROM procedure_reference WHERE source = 'auto'`);
1069
+ }
1070
+ catch {
1071
+ // Table may not exist yet, that's fine
1072
+ }
1073
+ // Get all technique IDs from the database
1074
+ const allMitreRows = database.prepare(`SELECT DISTINCT mitre_ids FROM detections WHERE mitre_ids IS NOT NULL AND mitre_ids != '[]'`).all();
1075
+ const allTechIds = new Set();
1076
+ for (const row of allMitreRows) {
1077
+ try {
1078
+ const ids = JSON.parse(row.mitre_ids);
1079
+ for (const id of ids)
1080
+ allTechIds.add(id);
1081
+ }
1082
+ catch { /* skip */ }
1083
+ }
1084
+ // Check which techniques already have hand-curated entries
1085
+ const handCuratedTechIds = new Set();
1086
+ try {
1087
+ const hcRows = database.prepare(`SELECT DISTINCT technique_id FROM procedure_reference WHERE source = 'hand_curated'`).all();
1088
+ for (const row of hcRows)
1089
+ handCuratedTechIds.add(row.technique_id);
1090
+ handCuratedCount = hcRows.length;
1091
+ }
1092
+ catch { /* table might not have hand_curated entries yet */ }
1093
+ let processed = 0;
1094
+ let totalProcs = 0;
1095
+ for (const techId of allTechIds) {
1096
+ if (handCuratedTechIds.has(techId))
1097
+ continue; // skip hand-curated techniques
1098
+ const result = autoExtractProcedures(techId);
1099
+ if (result.procedures_generated > 0) {
1100
+ processed++;
1101
+ totalProcs += result.procedures_generated;
1102
+ }
1103
+ }
1104
+ return { techniques_processed: processed, procedures_generated: totalProcs, hand_curated_loaded: handCuratedCount };
1105
+ }
1106
+ // =============================================================================
1107
+ // JUNCTION TABLE POPULATION
1108
+ // =============================================================================
1109
+ /**
1110
+ * Populate detection_techniques and technique_tactics junction tables
1111
+ * from existing detection data. Runs as a post-indexing bulk operation
1112
+ * in a single transaction for performance.
1113
+ */
1114
+ export function populateJunctionTables() {
1115
+ const database = getDb();
1116
+ const rows = database.prepare("SELECT id, mitre_ids, mitre_tactics FROM detections WHERE mitre_ids IS NOT NULL AND mitre_ids != '[]'").all();
1117
+ const dtStmt = database.prepare('INSERT OR IGNORE INTO detection_techniques (detection_id, technique_id) VALUES (?, ?)');
1118
+ const ttStmt = database.prepare("INSERT OR IGNORE INTO technique_tactics (technique_id, tactic_name, source) VALUES (?, ?, 'detection')");
1119
+ let dtCount = 0;
1120
+ let ttCount = 0;
1121
+ const batch = database.transaction(() => {
1122
+ for (const row of rows) {
1123
+ const ids = safeJsonParse(row.mitre_ids, []);
1124
+ const tactics = safeJsonParse(row.mitre_tactics, []);
1125
+ for (const techId of ids) {
1126
+ dtStmt.run(row.id, techId);
1127
+ dtCount++;
1128
+ for (const tactic of tactics) {
1129
+ ttStmt.run(techId, tactic);
1130
+ ttCount++;
1131
+ }
1132
+ }
1133
+ }
1134
+ });
1135
+ batch();
1136
+ return { detection_techniques: dtCount, technique_tactics: ttCount };
1137
+ }
1138
+ // =============================================================================
818
1139
  // LIGHTWEIGHT LIST FUNCTIONS
819
1140
  // =============================================================================
820
1141
  /**
@@ -6,8 +6,9 @@
6
6
  */
7
7
  export { getDbPath, getCacheDir, initDb, getDb, clearDb, recreateDb, dbExists, closeDb, } from './connection.js';
8
8
  export { createSchema, createSavedQueriesTable, } from './schema.js';
9
- export { type ValidationResult, type TechniqueIdFilters, type CoverageReport, type GapAnalysis, type DetectionSuggestion, type NavigatorLayerOptions, type DetectionListItem, type SourceComparisonResult, insertDetection, getDetectionById, getRawYaml, getDetectionCount, searchDetections, listDetections, listBySource, listByMitre, listByLogsource, listBySeverity, listByCve, listByAnalyticStory, listByProcessName, listByDetectionType, listByDataSource, listByKqlCategory, listByKqlTag, listByKqlDatasource, listByMitreTactic, getStats, getDistinctTechniqueIds, getDistinctCves, getDistinctProcessNames, validateTechniqueId, getTechniqueIds, analyzeCoverage, identifyGaps, suggestDetections, generateNavigatorLayer, searchDetectionList, listDetectionsBySourceLight, compareDetectionsBySource, getDetectionNamesByPattern, countDetectionsBySource, } from './detections.js';
9
+ export { type ValidationResult, type TechniqueIdFilters, type CoverageReport, type GapAnalysis, type DetectionSuggestion, type NavigatorLayerOptions, type DetectionListItem, type SourceComparisonResult, insertDetection, getDetectionById, getRawYaml, getDetectionCount, searchDetections, listDetections, listBySource, listByMitre, listByLogsource, listBySeverity, listByCve, listByAnalyticStory, listByProcessName, listByDetectionType, listByDataSource, listByKqlCategory, listByKqlTag, listByKqlDatasource, listByMitreTactic, getStats, getDistinctTechniqueIds, getDistinctCves, getDistinctProcessNames, validateTechniqueId, getTechniqueIds, analyzeCoverage, identifyGaps, suggestDetections, generateNavigatorLayer, autoExtractProcedures, extractAllProcedures, populateJunctionTables, searchDetectionList, listDetectionsBySourceLight, compareDetectionsBySource, getDetectionNamesByPattern, countDetectionsBySource, } from './detections.js';
10
10
  export { insertStory, getStoryByName, getStoryById, getStoryCount, searchStories, listStories, listStoriesByCategory, } from './stories.js';
11
11
  export { initSavedQueriesTable, saveQueryResult, getSavedQuery, listSavedQueries, deleteSavedQuery, cleanupExpiredQueries, getSavedQueryById, } from './cache.js';
12
12
  export { initDynamicSchema, createDynamicTable, getTableMetadata, listDynamicTables, dropDynamicTable, insertDynamicRow, insertDynamicRows, queryDynamicTable, deleteDynamicRows, getDynamicRow, PREBUILT_TABLES, } from './dynamic.js';
13
13
  export { type PatternData, type FieldReference, type StyleConvention, type TechniquePatterns, type ExtractionResult, initPatternsSchema, storePattern, storeFieldReference, storeStyleConvention, getPatternsByTechnique, getFieldReference, getStyleConventions, getMacroReference, extractSPLPatterns, extractSigmaPatterns, extractKQLPatterns, extractElasticPatterns, extractFieldUsage, extractMacroUsage, extractNamingConventions, extractAllPatterns, getPatternStats, } from './patterns.js';
14
+ export { type AttackActor, type AttackTechnique, type AttackSoftware, type ActorTechnique, type ActorCoverageResult, type ActorListItem, isStixLoaded, getActorByName, listActors, getActorTechniques, getActorCoverage, getSoftwareForActor, getTechniqueActors, getAttackTechnique, getAttackStats, } from './attack.js';
package/dist/db/index.js CHANGED
@@ -28,6 +28,10 @@ getDistinctTechniqueIds, getDistinctCves, getDistinctProcessNames,
28
28
  validateTechniqueId,
29
29
  // Coverage Analysis
30
30
  getTechniqueIds, analyzeCoverage, identifyGaps, suggestDetections, generateNavigatorLayer,
31
+ // Procedure Extraction
32
+ autoExtractProcedures, extractAllProcedures,
33
+ // Junction Tables
34
+ populateJunctionTables,
31
35
  // Lightweight Lists
32
36
  searchDetectionList, listDetectionsBySourceLight, compareDetectionsBySource, getDetectionNamesByPattern, countDetectionsBySource, } from './detections.js';
33
37
  // =============================================================================
@@ -56,3 +60,9 @@ getPatternsByTechnique, getFieldReference, getStyleConventions, getMacroReferenc
56
60
  extractSPLPatterns, extractSigmaPatterns, extractKQLPatterns, extractElasticPatterns, extractFieldUsage, extractMacroUsage, extractNamingConventions, extractAllPatterns,
57
61
  // Stats
58
62
  getPatternStats, } from './patterns.js';
63
+ // =============================================================================
64
+ // ATT&CK DATA (STIX-sourced)
65
+ // =============================================================================
66
+ export {
67
+ // Queries
68
+ isStixLoaded, getActorByName, listActors, getActorTechniques, getActorCoverage, getSoftwareForActor, getTechniqueActors, getAttackTechnique, getAttackStats, } from './attack.js';
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Procedure-Level Reference Data for MITRE ATT&CK Techniques
3
+ *
4
+ * Hand-curated: 16 techniques (high-confidence indicators)
5
+ * Auto-extracted: 472 techniques (behavioral clustering)
6
+ * Total: 488 techniques, 2374 procedures
7
+ *
8
+ * Sources: 8,295 community detection rules (Sigma, Splunk ESCU, Elastic, KQL, Sublime, CQL)
9
+ */
10
+ export interface TechniqueProcedure {
11
+ id: string;
12
+ name: string;
13
+ category: string;
14
+ description: string;
15
+ indicators: {
16
+ process_names?: string[];
17
+ command_patterns?: string[];
18
+ registry_paths?: string[];
19
+ file_paths?: string[];
20
+ event_ids?: string[];
21
+ description_keywords?: string[];
22
+ field_patterns?: string[];
23
+ };
24
+ }
25
+ export declare const PROCEDURE_REFERENCE: Record<string, TechniqueProcedure[]>;
26
+ export declare function getTechniquesWithProcedures(): string[];
27
+ export declare function getProceduresForTechnique(techniqueId: string): TechniqueProcedure[] | null;
28
+ export declare const HAND_CURATED_TECHNIQUES: string[];