driftdetect-core 0.4.1 → 0.4.2

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.
Files changed (237) hide show
  1. package/dist/boundaries/boundary-scanner.d.ts +76 -0
  2. package/dist/boundaries/boundary-scanner.d.ts.map +1 -0
  3. package/dist/boundaries/boundary-scanner.js +801 -0
  4. package/dist/boundaries/boundary-scanner.js.map +1 -0
  5. package/dist/boundaries/data-access-learner.d.ts +126 -0
  6. package/dist/boundaries/data-access-learner.d.ts.map +1 -0
  7. package/dist/boundaries/data-access-learner.js +486 -0
  8. package/dist/boundaries/data-access-learner.js.map +1 -0
  9. package/dist/boundaries/index.d.ts +6 -0
  10. package/dist/boundaries/index.d.ts.map +1 -1
  11. package/dist/boundaries/index.js +6 -0
  12. package/dist/boundaries/index.js.map +1 -1
  13. package/dist/boundaries/security-prioritizer.d.ts +118 -0
  14. package/dist/boundaries/security-prioritizer.d.ts.map +1 -0
  15. package/dist/boundaries/security-prioritizer.js +316 -0
  16. package/dist/boundaries/security-prioritizer.js.map +1 -0
  17. package/dist/call-graph/analysis/coverage-analyzer.d.ts +201 -0
  18. package/dist/call-graph/analysis/coverage-analyzer.d.ts.map +1 -0
  19. package/dist/call-graph/analysis/coverage-analyzer.js +553 -0
  20. package/dist/call-graph/analysis/coverage-analyzer.js.map +1 -0
  21. package/dist/call-graph/analysis/dead-code-detector.d.ts +145 -0
  22. package/dist/call-graph/analysis/dead-code-detector.d.ts.map +1 -0
  23. package/dist/call-graph/analysis/dead-code-detector.js +391 -0
  24. package/dist/call-graph/analysis/dead-code-detector.js.map +1 -0
  25. package/dist/call-graph/analysis/graph-builder.d.ts +142 -0
  26. package/dist/call-graph/analysis/graph-builder.d.ts.map +1 -0
  27. package/dist/call-graph/analysis/graph-builder.js +624 -0
  28. package/dist/call-graph/analysis/graph-builder.js.map +1 -0
  29. package/dist/call-graph/analysis/impact-analyzer.d.ts +150 -0
  30. package/dist/call-graph/analysis/impact-analyzer.d.ts.map +1 -0
  31. package/dist/call-graph/analysis/impact-analyzer.js +329 -0
  32. package/dist/call-graph/analysis/impact-analyzer.js.map +1 -0
  33. package/dist/call-graph/analysis/index.d.ts +11 -0
  34. package/dist/call-graph/analysis/index.d.ts.map +1 -0
  35. package/dist/call-graph/analysis/index.js +9 -0
  36. package/dist/call-graph/analysis/index.js.map +1 -0
  37. package/dist/call-graph/analysis/path-finder.d.ts +117 -0
  38. package/dist/call-graph/analysis/path-finder.d.ts.map +1 -0
  39. package/dist/call-graph/analysis/path-finder.js +360 -0
  40. package/dist/call-graph/analysis/path-finder.js.map +1 -0
  41. package/dist/call-graph/analysis/reachability.d.ts +56 -0
  42. package/dist/call-graph/analysis/reachability.d.ts.map +1 -0
  43. package/dist/call-graph/analysis/reachability.js +357 -0
  44. package/dist/call-graph/analysis/reachability.js.map +1 -0
  45. package/dist/call-graph/demo.d.ts +11 -0
  46. package/dist/call-graph/demo.d.ts.map +1 -0
  47. package/dist/call-graph/demo.js +339 -0
  48. package/dist/call-graph/demo.js.map +1 -0
  49. package/dist/call-graph/enrichment/enrichment-engine.d.ts +126 -0
  50. package/dist/call-graph/enrichment/enrichment-engine.d.ts.map +1 -0
  51. package/dist/call-graph/enrichment/enrichment-engine.js +760 -0
  52. package/dist/call-graph/enrichment/enrichment-engine.js.map +1 -0
  53. package/dist/call-graph/enrichment/impact-scorer.d.ts +59 -0
  54. package/dist/call-graph/enrichment/impact-scorer.d.ts.map +1 -0
  55. package/dist/call-graph/enrichment/impact-scorer.js +328 -0
  56. package/dist/call-graph/enrichment/impact-scorer.js.map +1 -0
  57. package/dist/call-graph/enrichment/index.d.ts +12 -0
  58. package/dist/call-graph/enrichment/index.d.ts.map +1 -0
  59. package/dist/call-graph/enrichment/index.js +15 -0
  60. package/dist/call-graph/enrichment/index.js.map +1 -0
  61. package/dist/call-graph/enrichment/remediation-generator.d.ts +41 -0
  62. package/dist/call-graph/enrichment/remediation-generator.d.ts.map +1 -0
  63. package/dist/call-graph/enrichment/remediation-generator.js +609 -0
  64. package/dist/call-graph/enrichment/remediation-generator.js.map +1 -0
  65. package/dist/call-graph/enrichment/sensitivity-classifier.d.ts +71 -0
  66. package/dist/call-graph/enrichment/sensitivity-classifier.d.ts.map +1 -0
  67. package/dist/call-graph/enrichment/sensitivity-classifier.js +454 -0
  68. package/dist/call-graph/enrichment/sensitivity-classifier.js.map +1 -0
  69. package/dist/call-graph/enrichment/types.d.ts +402 -0
  70. package/dist/call-graph/enrichment/types.d.ts.map +1 -0
  71. package/dist/call-graph/enrichment/types.js +9 -0
  72. package/dist/call-graph/enrichment/types.js.map +1 -0
  73. package/dist/call-graph/extractors/base-extractor.d.ts +112 -0
  74. package/dist/call-graph/extractors/base-extractor.d.ts.map +1 -0
  75. package/dist/call-graph/extractors/base-extractor.js +140 -0
  76. package/dist/call-graph/extractors/base-extractor.js.map +1 -0
  77. package/dist/call-graph/extractors/csharp-data-access-extractor.d.ts +76 -0
  78. package/dist/call-graph/extractors/csharp-data-access-extractor.d.ts.map +1 -0
  79. package/dist/call-graph/extractors/csharp-data-access-extractor.js +387 -0
  80. package/dist/call-graph/extractors/csharp-data-access-extractor.js.map +1 -0
  81. package/dist/call-graph/extractors/csharp-extractor.d.ts +87 -0
  82. package/dist/call-graph/extractors/csharp-extractor.d.ts.map +1 -0
  83. package/dist/call-graph/extractors/csharp-extractor.js +470 -0
  84. package/dist/call-graph/extractors/csharp-extractor.js.map +1 -0
  85. package/dist/call-graph/extractors/data-access-extractor.d.ts +76 -0
  86. package/dist/call-graph/extractors/data-access-extractor.d.ts.map +1 -0
  87. package/dist/call-graph/extractors/data-access-extractor.js +234 -0
  88. package/dist/call-graph/extractors/data-access-extractor.js.map +1 -0
  89. package/dist/call-graph/extractors/index.d.ts +26 -0
  90. package/dist/call-graph/extractors/index.d.ts.map +1 -0
  91. package/dist/call-graph/extractors/index.js +36 -0
  92. package/dist/call-graph/extractors/index.js.map +1 -0
  93. package/dist/call-graph/extractors/java-data-access-extractor.d.ts +101 -0
  94. package/dist/call-graph/extractors/java-data-access-extractor.d.ts.map +1 -0
  95. package/dist/call-graph/extractors/java-data-access-extractor.js +611 -0
  96. package/dist/call-graph/extractors/java-data-access-extractor.js.map +1 -0
  97. package/dist/call-graph/extractors/java-extractor.d.ts +87 -0
  98. package/dist/call-graph/extractors/java-extractor.d.ts.map +1 -0
  99. package/dist/call-graph/extractors/java-extractor.js +510 -0
  100. package/dist/call-graph/extractors/java-extractor.js.map +1 -0
  101. package/dist/call-graph/extractors/php-data-access-extractor.d.ts +93 -0
  102. package/dist/call-graph/extractors/php-data-access-extractor.d.ts.map +1 -0
  103. package/dist/call-graph/extractors/php-data-access-extractor.js +589 -0
  104. package/dist/call-graph/extractors/php-data-access-extractor.js.map +1 -0
  105. package/dist/call-graph/extractors/php-extractor.d.ts +104 -0
  106. package/dist/call-graph/extractors/php-extractor.d.ts.map +1 -0
  107. package/dist/call-graph/extractors/php-extractor.js +619 -0
  108. package/dist/call-graph/extractors/php-extractor.js.map +1 -0
  109. package/dist/call-graph/extractors/python-data-access-extractor.d.ts +90 -0
  110. package/dist/call-graph/extractors/python-data-access-extractor.d.ts.map +1 -0
  111. package/dist/call-graph/extractors/python-data-access-extractor.js +537 -0
  112. package/dist/call-graph/extractors/python-data-access-extractor.js.map +1 -0
  113. package/dist/call-graph/extractors/python-extractor.d.ts +98 -0
  114. package/dist/call-graph/extractors/python-extractor.d.ts.map +1 -0
  115. package/dist/call-graph/extractors/python-extractor.js +681 -0
  116. package/dist/call-graph/extractors/python-extractor.js.map +1 -0
  117. package/dist/call-graph/extractors/semantic-data-access-scanner.d.ts +91 -0
  118. package/dist/call-graph/extractors/semantic-data-access-scanner.d.ts.map +1 -0
  119. package/dist/call-graph/extractors/semantic-data-access-scanner.js +498 -0
  120. package/dist/call-graph/extractors/semantic-data-access-scanner.js.map +1 -0
  121. package/dist/call-graph/extractors/typescript-data-access-extractor.d.ts +122 -0
  122. package/dist/call-graph/extractors/typescript-data-access-extractor.d.ts.map +1 -0
  123. package/dist/call-graph/extractors/typescript-data-access-extractor.js +788 -0
  124. package/dist/call-graph/extractors/typescript-data-access-extractor.js.map +1 -0
  125. package/dist/call-graph/extractors/typescript-extractor.d.ts +145 -0
  126. package/dist/call-graph/extractors/typescript-extractor.d.ts.map +1 -0
  127. package/dist/call-graph/extractors/typescript-extractor.js +904 -0
  128. package/dist/call-graph/extractors/typescript-extractor.js.map +1 -0
  129. package/dist/call-graph/index.d.ts +127 -0
  130. package/dist/call-graph/index.d.ts.map +1 -0
  131. package/dist/call-graph/index.js +247 -0
  132. package/dist/call-graph/index.js.map +1 -0
  133. package/dist/call-graph/store/call-graph-store.d.ts +70 -0
  134. package/dist/call-graph/store/call-graph-store.d.ts.map +1 -0
  135. package/dist/call-graph/store/call-graph-store.js +210 -0
  136. package/dist/call-graph/store/call-graph-store.js.map +1 -0
  137. package/dist/call-graph/store/index.d.ts +7 -0
  138. package/dist/call-graph/store/index.d.ts.map +1 -0
  139. package/dist/call-graph/store/index.js +7 -0
  140. package/dist/call-graph/store/index.js.map +1 -0
  141. package/dist/call-graph/types.d.ts +376 -0
  142. package/dist/call-graph/types.d.ts.map +1 -0
  143. package/dist/call-graph/types.js +8 -0
  144. package/dist/call-graph/types.js.map +1 -0
  145. package/dist/index.d.ts +8 -0
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js +12 -0
  148. package/dist/index.js.map +1 -1
  149. package/dist/lake/callgraph-shard-store.d.ts +168 -0
  150. package/dist/lake/callgraph-shard-store.d.ts.map +1 -0
  151. package/dist/lake/callgraph-shard-store.js +466 -0
  152. package/dist/lake/callgraph-shard-store.js.map +1 -0
  153. package/dist/lake/examples-store.d.ts +127 -0
  154. package/dist/lake/examples-store.d.ts.map +1 -0
  155. package/dist/lake/examples-store.js +389 -0
  156. package/dist/lake/examples-store.js.map +1 -0
  157. package/dist/lake/index-store.d.ts +82 -0
  158. package/dist/lake/index-store.d.ts.map +1 -0
  159. package/dist/lake/index-store.js +359 -0
  160. package/dist/lake/index-store.js.map +1 -0
  161. package/dist/lake/index.d.ts +93 -0
  162. package/dist/lake/index.d.ts.map +1 -0
  163. package/dist/lake/index.js +138 -0
  164. package/dist/lake/index.js.map +1 -0
  165. package/dist/lake/lake.bak/index-store.d.ts +82 -0
  166. package/dist/lake/lake.bak/index-store.d.ts.map +1 -0
  167. package/dist/lake/lake.bak/index-store.js +357 -0
  168. package/dist/lake/lake.bak/index-store.js.map +1 -0
  169. package/dist/lake/lake.bak/index.d.ts +81 -0
  170. package/dist/lake/lake.bak/index.d.ts.map +1 -0
  171. package/dist/lake/lake.bak/index.js +114 -0
  172. package/dist/lake/lake.bak/index.js.map +1 -0
  173. package/dist/lake/lake.bak/manifest-store.d.ts +51 -0
  174. package/dist/lake/lake.bak/manifest-store.d.ts.map +1 -0
  175. package/dist/lake/lake.bak/manifest-store.js +347 -0
  176. package/dist/lake/lake.bak/manifest-store.js.map +1 -0
  177. package/dist/lake/lake.bak/query-engine.d.ts +112 -0
  178. package/dist/lake/lake.bak/query-engine.d.ts.map +1 -0
  179. package/dist/lake/lake.bak/query-engine.js +370 -0
  180. package/dist/lake/lake.bak/query-engine.js.map +1 -0
  181. package/dist/lake/lake.bak/types.d.ts +428 -0
  182. package/dist/lake/lake.bak/types.d.ts.map +1 -0
  183. package/dist/lake/lake.bak/types.js +46 -0
  184. package/dist/lake/lake.bak/types.js.map +1 -0
  185. package/dist/lake/lake.bak/view-materializer.d.ts +70 -0
  186. package/dist/lake/lake.bak/view-materializer.d.ts.map +1 -0
  187. package/dist/lake/lake.bak/view-materializer.js +314 -0
  188. package/dist/lake/lake.bak/view-materializer.js.map +1 -0
  189. package/dist/lake/lake.bak/view-store.d.ts +57 -0
  190. package/dist/lake/lake.bak/view-store.d.ts.map +1 -0
  191. package/dist/lake/lake.bak/view-store.js +348 -0
  192. package/dist/lake/lake.bak/view-store.js.map +1 -0
  193. package/dist/lake/manifest-store.d.ts +51 -0
  194. package/dist/lake/manifest-store.d.ts.map +1 -0
  195. package/dist/lake/manifest-store.js +348 -0
  196. package/dist/lake/manifest-store.js.map +1 -0
  197. package/dist/lake/pattern-shard-store.d.ts +87 -0
  198. package/dist/lake/pattern-shard-store.d.ts.map +1 -0
  199. package/dist/lake/pattern-shard-store.js +347 -0
  200. package/dist/lake/pattern-shard-store.js.map +1 -0
  201. package/dist/lake/query-engine.d.ts +124 -0
  202. package/dist/lake/query-engine.d.ts.map +1 -0
  203. package/dist/lake/query-engine.js +453 -0
  204. package/dist/lake/query-engine.js.map +1 -0
  205. package/dist/lake/security-shard-store.d.ts +156 -0
  206. package/dist/lake/security-shard-store.d.ts.map +1 -0
  207. package/dist/lake/security-shard-store.js +498 -0
  208. package/dist/lake/security-shard-store.js.map +1 -0
  209. package/dist/lake/types.d.ts +428 -0
  210. package/dist/lake/types.d.ts.map +1 -0
  211. package/dist/lake/types.js +46 -0
  212. package/dist/lake/types.js.map +1 -0
  213. package/dist/lake/view-materializer.d.ts +70 -0
  214. package/dist/lake/view-materializer.d.ts.map +1 -0
  215. package/dist/lake/view-materializer.js +314 -0
  216. package/dist/lake/view-materializer.js.map +1 -0
  217. package/dist/lake/view-store.d.ts +57 -0
  218. package/dist/lake/view-store.d.ts.map +1 -0
  219. package/dist/lake/view-store.js +348 -0
  220. package/dist/lake/view-store.js.map +1 -0
  221. package/dist/parsers/tree-sitter/index.d.ts +1 -0
  222. package/dist/parsers/tree-sitter/index.d.ts.map +1 -1
  223. package/dist/parsers/tree-sitter/index.js +4 -0
  224. package/dist/parsers/tree-sitter/index.js.map +1 -1
  225. package/dist/parsers/tree-sitter/typescript-loader.d.ts +58 -0
  226. package/dist/parsers/tree-sitter/typescript-loader.d.ts.map +1 -0
  227. package/dist/parsers/tree-sitter/typescript-loader.js +250 -0
  228. package/dist/parsers/tree-sitter/typescript-loader.js.map +1 -0
  229. package/dist/store/project-config.d.ts +154 -0
  230. package/dist/store/project-config.d.ts.map +1 -0
  231. package/dist/store/project-config.js +235 -0
  232. package/dist/store/project-config.js.map +1 -0
  233. package/dist/store/project-registry.d.ts +241 -0
  234. package/dist/store/project-registry.d.ts.map +1 -0
  235. package/dist/store/project-registry.js +557 -0
  236. package/dist/store/project-registry.js.map +1 -0
  237. package/package.json +4 -2
@@ -0,0 +1,760 @@
1
+ /**
2
+ * Enrichment Engine
3
+ *
4
+ * Enterprise-grade security finding enrichment.
5
+ * Transforms raw vulnerability findings into actionable intelligence
6
+ * by connecting them to their actual data impact through call graph analysis.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const engine = new EnrichmentEngine(callGraph, boundaryStore);
11
+ *
12
+ * // Enrich a single finding
13
+ * const enriched = await engine.enrich(finding);
14
+ * console.log(enriched.dataImpact.sensitiveFields);
15
+ * console.log(enriched.priority.tier); // 'P0' | 'P1' | 'P2' | 'P3' | 'P4'
16
+ *
17
+ * // Batch enrich with summary
18
+ * const result = await engine.enrichBatch(findings);
19
+ * console.log(result.summary.byPriority);
20
+ * ```
21
+ */
22
+ import { ReachabilityEngine } from '../analysis/reachability.js';
23
+ import { SensitivityClassifier, createSensitivityClassifier } from './sensitivity-classifier.js';
24
+ import { ImpactScorer, createImpactScorer } from './impact-scorer.js';
25
+ import { RemediationGenerator, createRemediationGenerator } from './remediation-generator.js';
26
+ // ============================================================================
27
+ // Constants
28
+ // ============================================================================
29
+ const ENGINE_VERSION = '1.0.0';
30
+ const DEFAULT_MAX_DEPTH = 10;
31
+ const DEFAULT_MIN_CONFIDENCE = 0.5;
32
+ const DEFAULT_PARALLEL_LIMIT = 10;
33
+ /**
34
+ * Decorator patterns that indicate API entry points
35
+ */
36
+ const API_DECORATOR_PATTERNS = [
37
+ // Express/Node.js
38
+ /@(Get|Post|Put|Delete|Patch|Options|Head|All)\s*\(/i,
39
+ /app\.(get|post|put|delete|patch)\s*\(/i,
40
+ /router\.(get|post|put|delete|patch)\s*\(/i,
41
+ // NestJS
42
+ /@(Controller|Get|Post|Put|Delete|Patch)\s*\(/i,
43
+ // FastAPI/Flask
44
+ /@app\.(route|get|post|put|delete)\s*\(/i,
45
+ /@router\.(get|post|put|delete)\s*\(/i,
46
+ // Spring
47
+ /@(GetMapping|PostMapping|PutMapping|DeleteMapping|RequestMapping)\s*\(/i,
48
+ // ASP.NET
49
+ /\[(HttpGet|HttpPost|HttpPut|HttpDelete|Route)\s*\(/i,
50
+ // Laravel
51
+ /Route::(get|post|put|delete|patch)\s*\(/i,
52
+ ];
53
+ // ============================================================================
54
+ // Enrichment Engine
55
+ // ============================================================================
56
+ /**
57
+ * Enterprise-grade security finding enrichment engine
58
+ */
59
+ export class EnrichmentEngine {
60
+ graph;
61
+ reachability;
62
+ classifier;
63
+ scorer;
64
+ remediator;
65
+ dataAccessByFile;
66
+ sensitiveFields;
67
+ constructor(graph, dataAccessPoints, sensitiveFields) {
68
+ this.graph = graph;
69
+ this.reachability = new ReachabilityEngine(graph);
70
+ this.classifier = createSensitivityClassifier();
71
+ this.scorer = createImpactScorer();
72
+ this.remediator = createRemediationGenerator();
73
+ // Index data access points by file
74
+ this.dataAccessByFile = new Map();
75
+ if (dataAccessPoints) {
76
+ for (const access of dataAccessPoints) {
77
+ const existing = this.dataAccessByFile.get(access.file) ?? [];
78
+ existing.push(access);
79
+ this.dataAccessByFile.set(access.file, existing);
80
+ }
81
+ }
82
+ // Index sensitive fields
83
+ this.sensitiveFields = new Map();
84
+ if (sensitiveFields) {
85
+ for (const field of sensitiveFields) {
86
+ const key = field.table ? `${field.table}.${field.field}` : field.field;
87
+ this.sensitiveFields.set(key.toLowerCase(), field);
88
+ }
89
+ }
90
+ }
91
+ /**
92
+ * Enrich a single security finding
93
+ */
94
+ async enrich(finding, options = {}) {
95
+ const startTime = Date.now();
96
+ const warnings = [];
97
+ try {
98
+ // Analyze data impact
99
+ const dataImpact = this.analyzeDataImpact(finding, options, warnings);
100
+ // Analyze blast radius
101
+ const blastRadius = options.skipBlastRadius
102
+ ? this.createEmptyBlastRadius()
103
+ : this.analyzeBlastRadius(finding, warnings);
104
+ // Calculate priority score
105
+ const priority = this.scorer.calculatePriority(finding.severity, finding.category, dataImpact, blastRadius, finding.cvss);
106
+ // Generate remediation guidance
107
+ const remediation = options.skipRemediation
108
+ ? this.createEmptyRemediation()
109
+ : this.remediator.generate(finding, dataImpact);
110
+ // Build enrichment metadata
111
+ const enrichment = {
112
+ enrichedAt: new Date().toISOString(),
113
+ engineVersion: ENGINE_VERSION,
114
+ callGraphVersion: this.graph.version,
115
+ confidence: this.calculateConfidence(dataImpact, blastRadius),
116
+ warnings,
117
+ processingTimeMs: Date.now() - startTime,
118
+ };
119
+ return {
120
+ finding,
121
+ dataImpact,
122
+ blastRadius,
123
+ priority,
124
+ remediation,
125
+ enrichment,
126
+ };
127
+ }
128
+ catch (error) {
129
+ // Return a minimal enrichment on error
130
+ warnings.push(`Enrichment error: ${error instanceof Error ? error.message : 'Unknown error'}`);
131
+ return {
132
+ finding,
133
+ dataImpact: this.createEmptyDataImpact(),
134
+ blastRadius: this.createEmptyBlastRadius(),
135
+ priority: {
136
+ overall: this.getBaseSeverityScore(finding.severity),
137
+ severityScore: this.getBaseSeverityScore(finding.severity),
138
+ dataImpactScore: 0,
139
+ blastRadiusScore: 0,
140
+ exploitabilityScore: 50,
141
+ tier: this.getBaseTier(finding.severity),
142
+ increasingFactors: [],
143
+ decreasingFactors: ['Enrichment failed - using base severity only'],
144
+ },
145
+ remediation: this.createEmptyRemediation(),
146
+ enrichment: {
147
+ enrichedAt: new Date().toISOString(),
148
+ engineVersion: ENGINE_VERSION,
149
+ callGraphVersion: this.graph.version,
150
+ confidence: 0.1,
151
+ warnings,
152
+ processingTimeMs: Date.now() - startTime,
153
+ },
154
+ };
155
+ }
156
+ }
157
+ /**
158
+ * Enrich multiple findings with summary statistics
159
+ */
160
+ async enrichBatch(findings, options = {}) {
161
+ const startTime = Date.now();
162
+ const enrichedFindings = [];
163
+ const failures = [];
164
+ const parallelLimit = options.parallelLimit ?? DEFAULT_PARALLEL_LIMIT;
165
+ // Process in batches for controlled parallelism
166
+ for (let i = 0; i < findings.length; i += parallelLimit) {
167
+ const batch = findings.slice(i, i + parallelLimit);
168
+ const results = await Promise.all(batch.map(async (finding) => {
169
+ try {
170
+ return await this.enrich(finding, options);
171
+ }
172
+ catch (error) {
173
+ failures.push({
174
+ findingId: finding.id,
175
+ error: error instanceof Error ? error.message : 'Unknown error',
176
+ type: 'unknown',
177
+ });
178
+ return null;
179
+ }
180
+ }));
181
+ for (const result of results) {
182
+ if (result) {
183
+ enrichedFindings.push(result);
184
+ }
185
+ }
186
+ }
187
+ // Build summary
188
+ const summary = this.buildSummary(enrichedFindings);
189
+ // Build metadata
190
+ const metadata = {
191
+ startedAt: new Date(startTime).toISOString(),
192
+ completedAt: new Date().toISOString(),
193
+ totalTimeMs: Date.now() - startTime,
194
+ avgTimePerFindingMs: findings.length > 0
195
+ ? (Date.now() - startTime) / findings.length
196
+ : 0,
197
+ failures,
198
+ };
199
+ return {
200
+ findings: enrichedFindings,
201
+ summary,
202
+ metadata,
203
+ };
204
+ }
205
+ /**
206
+ * Analyze data impact for a finding
207
+ */
208
+ analyzeDataImpact(finding, options, warnings) {
209
+ const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
210
+ const minConfidence = options.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
211
+ // Get reachability from the finding location
212
+ const reachabilityResult = this.reachability.getReachableData(finding.file, finding.line, {
213
+ maxDepth,
214
+ includeUnresolved: options.includeUnresolved ?? false,
215
+ });
216
+ if (reachabilityResult.reachableAccess.length === 0) {
217
+ warnings.push('No data access detected from vulnerability location');
218
+ return this.createEmptyDataImpact();
219
+ }
220
+ // Build reachable data with impact analysis
221
+ const reachableData = [];
222
+ const sensitiveFieldsMap = new Map();
223
+ const tablesSet = new Set();
224
+ const regulationsSet = new Set();
225
+ for (const access of reachabilityResult.reachableAccess) {
226
+ if (access.access.confidence < minConfidence)
227
+ continue;
228
+ tablesSet.add(access.access.table);
229
+ // Classify sensitive fields
230
+ const accessSensitiveFields = [];
231
+ for (const field of access.access.fields) {
232
+ const profile = this.classifier.classify(field, access.access.table);
233
+ if (profile.type !== 'unknown') {
234
+ const sensitiveField = {
235
+ field,
236
+ table: access.access.table,
237
+ sensitivityType: profile.type,
238
+ file: access.access.file,
239
+ line: access.access.line,
240
+ confidence: access.access.confidence,
241
+ };
242
+ accessSensitiveFields.push(sensitiveField);
243
+ // Add to aggregated map
244
+ const key = `${access.access.table}.${field}`.toLowerCase();
245
+ const existing = sensitiveFieldsMap.get(key);
246
+ if (existing) {
247
+ existing.pathCount++;
248
+ existing.shortestPath = Math.min(existing.shortestPath, access.depth);
249
+ if (!existing.operations.includes(access.access.operation)) {
250
+ existing.operations.push(access.access.operation);
251
+ }
252
+ }
253
+ else {
254
+ sensitiveFieldsMap.set(key, {
255
+ field: sensitiveField,
256
+ pathCount: 1,
257
+ shortestPath: access.depth,
258
+ operations: [access.access.operation],
259
+ regulations: profile.regulations,
260
+ impactScore: profile.baseScore,
261
+ });
262
+ }
263
+ // Collect regulations
264
+ for (const reg of profile.regulations) {
265
+ regulationsSet.add(reg);
266
+ }
267
+ }
268
+ }
269
+ // Calculate impact for this access
270
+ const impactScore = this.calculateAccessImpactScore(access.access, accessSensitiveFields);
271
+ reachableData.push({
272
+ access: access.access,
273
+ callPath: access.path,
274
+ depth: access.depth,
275
+ sensitiveFields: accessSensitiveFields,
276
+ operations: [access.access.operation],
277
+ impactScore,
278
+ impactRationale: this.buildImpactRationale(access.access, accessSensitiveFields),
279
+ });
280
+ }
281
+ const sensitiveFields = Array.from(sensitiveFieldsMap.values());
282
+ const tables = Array.from(tablesSet);
283
+ const regulations = Array.from(regulationsSet);
284
+ // Calculate overall score
285
+ const { score, classification } = this.scorer.calculateDataImpactScore(sensitiveFields, tables, reachabilityResult.maxDepth, reachabilityResult.functionsTraversed);
286
+ return {
287
+ tables,
288
+ sensitiveFields,
289
+ reachableData,
290
+ maxDepth: reachabilityResult.maxDepth,
291
+ attackSurfaceSize: reachabilityResult.functionsTraversed,
292
+ regulations,
293
+ score,
294
+ classification,
295
+ };
296
+ }
297
+ /**
298
+ * Analyze blast radius for a finding
299
+ */
300
+ analyzeBlastRadius(finding, warnings) {
301
+ // Find the function containing the vulnerability
302
+ const vulnFunction = this.findContainingFunction(finding.file, finding.line);
303
+ if (!vulnFunction) {
304
+ warnings.push('Could not find function containing vulnerability');
305
+ return this.createEmptyBlastRadius();
306
+ }
307
+ // Find entry points that can reach this vulnerability
308
+ const entryPoints = this.findEntryPointsToFunction(vulnFunction.id);
309
+ // Find affected functions (callers and callees)
310
+ const affectedFunctions = this.findAffectedFunctions(vulnFunction);
311
+ // Calculate lines of code in blast radius
312
+ const linesOfCode = this.calculateLinesOfCode(affectedFunctions);
313
+ // Find related vulnerabilities (other findings in the same call paths)
314
+ // This would require access to all findings - simplified here
315
+ const relatedVulnerabilities = [];
316
+ // Calculate score
317
+ const { score, classification } = this.scorer.calculateBlastRadiusScore(entryPoints, affectedFunctions.length, linesOfCode);
318
+ return {
319
+ entryPoints,
320
+ relatedVulnerabilities,
321
+ affectedFunctions,
322
+ linesOfCode,
323
+ score,
324
+ classification,
325
+ };
326
+ }
327
+ /**
328
+ * Find entry points that can reach a function
329
+ */
330
+ findEntryPointsToFunction(targetId) {
331
+ const entryPoints = [];
332
+ for (const entryPointId of this.graph.entryPoints) {
333
+ const entryFunc = this.graph.functions.get(entryPointId);
334
+ if (!entryFunc)
335
+ continue;
336
+ // BFS to find path from entry point to target
337
+ const path = this.findPath(entryPointId, targetId);
338
+ if (path.length === 0)
339
+ continue;
340
+ entryPoints.push({
341
+ functionId: entryPointId,
342
+ name: entryFunc.qualifiedName,
343
+ file: entryFunc.file,
344
+ line: entryFunc.startLine,
345
+ type: this.classifyEntryPointType(entryFunc),
346
+ isPublic: this.isPublicEntryPoint(entryFunc),
347
+ requiresAuth: this.requiresAuthentication(entryFunc),
348
+ pathToVulnerability: path,
349
+ });
350
+ }
351
+ return entryPoints;
352
+ }
353
+ /**
354
+ * Find path between two functions using BFS
355
+ */
356
+ findPath(fromId, toId) {
357
+ if (fromId === toId) {
358
+ const func = this.graph.functions.get(fromId);
359
+ if (!func)
360
+ return [];
361
+ return [{
362
+ functionId: fromId,
363
+ functionName: func.qualifiedName,
364
+ file: func.file,
365
+ line: func.startLine,
366
+ }];
367
+ }
368
+ const visited = new Set();
369
+ const queue = [];
370
+ const fromFunc = this.graph.functions.get(fromId);
371
+ if (!fromFunc)
372
+ return [];
373
+ queue.push({
374
+ id: fromId,
375
+ path: [{
376
+ functionId: fromId,
377
+ functionName: fromFunc.qualifiedName,
378
+ file: fromFunc.file,
379
+ line: fromFunc.startLine,
380
+ }],
381
+ });
382
+ while (queue.length > 0) {
383
+ const { id, path } = queue.shift();
384
+ if (visited.has(id))
385
+ continue;
386
+ visited.add(id);
387
+ const func = this.graph.functions.get(id);
388
+ if (!func)
389
+ continue;
390
+ for (const call of func.calls) {
391
+ if (!call.resolved)
392
+ continue;
393
+ for (const candidateId of call.resolvedCandidates) {
394
+ if (candidateId === toId) {
395
+ const targetFunc = this.graph.functions.get(toId);
396
+ if (!targetFunc)
397
+ continue;
398
+ return [
399
+ ...path,
400
+ {
401
+ functionId: toId,
402
+ functionName: targetFunc.qualifiedName,
403
+ file: targetFunc.file,
404
+ line: targetFunc.startLine,
405
+ },
406
+ ];
407
+ }
408
+ if (!visited.has(candidateId)) {
409
+ const candidateFunc = this.graph.functions.get(candidateId);
410
+ if (candidateFunc) {
411
+ queue.push({
412
+ id: candidateId,
413
+ path: [
414
+ ...path,
415
+ {
416
+ functionId: candidateId,
417
+ functionName: candidateFunc.qualifiedName,
418
+ file: candidateFunc.file,
419
+ line: candidateFunc.startLine,
420
+ },
421
+ ],
422
+ });
423
+ }
424
+ }
425
+ }
426
+ }
427
+ }
428
+ return [];
429
+ }
430
+ /**
431
+ * Find functions affected by a vulnerability
432
+ */
433
+ findAffectedFunctions(vulnFunction) {
434
+ const affected = [];
435
+ const visited = new Set();
436
+ // Add the vulnerable function itself
437
+ affected.push({
438
+ functionId: vulnFunction.id,
439
+ name: vulnFunction.qualifiedName,
440
+ file: vulnFunction.file,
441
+ line: vulnFunction.startLine,
442
+ affectedBy: 'direct',
443
+ distance: 0,
444
+ });
445
+ visited.add(vulnFunction.id);
446
+ // Find callers (functions that call the vulnerable function)
447
+ const callerQueue = [];
448
+ for (const callSite of vulnFunction.calledBy) {
449
+ if (!visited.has(callSite.callerId)) {
450
+ callerQueue.push({ id: callSite.callerId, distance: 1 });
451
+ }
452
+ }
453
+ while (callerQueue.length > 0 && affected.length < 100) {
454
+ const { id, distance } = callerQueue.shift();
455
+ if (visited.has(id))
456
+ continue;
457
+ visited.add(id);
458
+ const func = this.graph.functions.get(id);
459
+ if (!func)
460
+ continue;
461
+ affected.push({
462
+ functionId: id,
463
+ name: func.qualifiedName,
464
+ file: func.file,
465
+ line: func.startLine,
466
+ affectedBy: 'caller',
467
+ distance,
468
+ });
469
+ // Add callers of this function (up to distance 3)
470
+ if (distance < 3) {
471
+ for (const callSite of func.calledBy) {
472
+ if (!visited.has(callSite.callerId)) {
473
+ callerQueue.push({ id: callSite.callerId, distance: distance + 1 });
474
+ }
475
+ }
476
+ }
477
+ }
478
+ // Find callees (functions called by the vulnerable function)
479
+ const calleeQueue = [];
480
+ for (const call of vulnFunction.calls) {
481
+ if (call.resolved) {
482
+ for (const candidateId of call.resolvedCandidates) {
483
+ if (!visited.has(candidateId)) {
484
+ calleeQueue.push({ id: candidateId, distance: 1 });
485
+ }
486
+ }
487
+ }
488
+ }
489
+ while (calleeQueue.length > 0 && affected.length < 100) {
490
+ const { id, distance } = calleeQueue.shift();
491
+ if (visited.has(id))
492
+ continue;
493
+ visited.add(id);
494
+ const func = this.graph.functions.get(id);
495
+ if (!func)
496
+ continue;
497
+ affected.push({
498
+ functionId: id,
499
+ name: func.qualifiedName,
500
+ file: func.file,
501
+ line: func.startLine,
502
+ affectedBy: 'callee',
503
+ distance,
504
+ });
505
+ // Add callees of this function (up to distance 3)
506
+ if (distance < 3) {
507
+ for (const call of func.calls) {
508
+ if (call.resolved) {
509
+ for (const candidateId of call.resolvedCandidates) {
510
+ if (!visited.has(candidateId)) {
511
+ calleeQueue.push({ id: candidateId, distance: distance + 1 });
512
+ }
513
+ }
514
+ }
515
+ }
516
+ }
517
+ }
518
+ return affected;
519
+ }
520
+ /**
521
+ * Find the function containing a location
522
+ */
523
+ findContainingFunction(file, line) {
524
+ let best = null;
525
+ let bestSize = Infinity;
526
+ for (const [, func] of this.graph.functions) {
527
+ if (func.file === file && line >= func.startLine && line <= func.endLine) {
528
+ const size = func.endLine - func.startLine;
529
+ if (size < bestSize) {
530
+ best = func;
531
+ bestSize = size;
532
+ }
533
+ }
534
+ }
535
+ return best;
536
+ }
537
+ /**
538
+ * Classify entry point type based on decorators and patterns
539
+ */
540
+ classifyEntryPointType(func) {
541
+ const decorators = func.decorators.join(' ');
542
+ for (const pattern of API_DECORATOR_PATTERNS) {
543
+ if (pattern.test(decorators)) {
544
+ return 'api-endpoint';
545
+ }
546
+ }
547
+ if (func.name === 'main' || func.name === '__main__') {
548
+ return 'main';
549
+ }
550
+ if (func.isExported) {
551
+ return 'exported-function';
552
+ }
553
+ return 'exported-function';
554
+ }
555
+ /**
556
+ * Check if entry point is publicly accessible
557
+ */
558
+ isPublicEntryPoint(func) {
559
+ const decorators = func.decorators.join(' ').toLowerCase();
560
+ // Check for public API indicators
561
+ if (decorators.includes('public') || decorators.includes('anonymous')) {
562
+ return true;
563
+ }
564
+ // API endpoints are typically public unless marked otherwise
565
+ for (const pattern of API_DECORATOR_PATTERNS) {
566
+ if (pattern.test(func.decorators.join(' '))) {
567
+ return true;
568
+ }
569
+ }
570
+ return func.isExported;
571
+ }
572
+ /**
573
+ * Check if entry point requires authentication
574
+ */
575
+ requiresAuthentication(func) {
576
+ const decorators = func.decorators.join(' ').toLowerCase();
577
+ // Check for auth decorators
578
+ const authPatterns = [
579
+ 'auth', 'authenticated', 'authorize', 'protected',
580
+ 'login_required', 'jwt', 'bearer', 'guard',
581
+ ];
582
+ return authPatterns.some((pattern) => decorators.includes(pattern));
583
+ }
584
+ /**
585
+ * Calculate lines of code in affected functions
586
+ */
587
+ calculateLinesOfCode(functions) {
588
+ let total = 0;
589
+ for (const affected of functions) {
590
+ const func = this.graph.functions.get(affected.functionId);
591
+ if (func) {
592
+ total += func.endLine - func.startLine + 1;
593
+ }
594
+ }
595
+ return total;
596
+ }
597
+ /**
598
+ * Calculate impact score for a single data access
599
+ */
600
+ calculateAccessImpactScore(access, sensitiveFields) {
601
+ if (sensitiveFields.length === 0) {
602
+ return 10; // Base score for non-sensitive data
603
+ }
604
+ // Use the highest sensitivity score
605
+ let maxScore = 0;
606
+ for (const field of sensitiveFields) {
607
+ const profile = this.classifier.classify(field.field, field.table ?? undefined);
608
+ maxScore = Math.max(maxScore, profile.baseScore);
609
+ }
610
+ // Adjust for operation type
611
+ const operationMultiplier = access.operation === 'write' || access.operation === 'delete'
612
+ ? 1.0
613
+ : 0.8;
614
+ return Math.round(maxScore * operationMultiplier);
615
+ }
616
+ /**
617
+ * Build impact rationale string
618
+ */
619
+ buildImpactRationale(access, sensitiveFields) {
620
+ if (sensitiveFields.length === 0) {
621
+ return `${access.operation} access to ${access.table}`;
622
+ }
623
+ const types = [...new Set(sensitiveFields.map((f) => f.sensitivityType))];
624
+ return `${access.operation} access to ${types.join(', ')} data in ${access.table}`;
625
+ }
626
+ /**
627
+ * Calculate overall confidence in enrichment
628
+ */
629
+ calculateConfidence(dataImpact, blastRadius) {
630
+ let confidence = 0.5; // Base confidence
631
+ // Higher confidence if we found data access
632
+ if (dataImpact.reachableData.length > 0) {
633
+ confidence += 0.2;
634
+ }
635
+ // Higher confidence if we found entry points
636
+ if (blastRadius.entryPoints.length > 0) {
637
+ confidence += 0.2;
638
+ }
639
+ // Higher confidence if call graph has good resolution
640
+ const resolutionRate = this.graph.stats.resolvedCallSites /
641
+ Math.max(1, this.graph.stats.totalCallSites);
642
+ confidence += resolutionRate * 0.1;
643
+ return Math.min(1, confidence);
644
+ }
645
+ /**
646
+ * Build summary statistics for batch enrichment
647
+ */
648
+ buildSummary(findings) {
649
+ const byPriority = {
650
+ P0: 0, P1: 0, P2: 0, P3: 0, P4: 0,
651
+ };
652
+ const byImpact = {
653
+ catastrophic: 0, severe: 0, significant: 0, moderate: 0, minimal: 0, none: 0,
654
+ };
655
+ const byCategory = {
656
+ injection: 0, 'broken-auth': 0, 'sensitive-exposure': 0, xxe: 0,
657
+ 'broken-access': 0, misconfig: 0, xss: 0, deserialization: 0,
658
+ components: 0, logging: 0, ssrf: 0, other: 0,
659
+ };
660
+ const sensitiveFieldsSet = new Set();
661
+ const tablesSet = new Set();
662
+ const regulationsSet = new Set();
663
+ for (const finding of findings) {
664
+ byPriority[finding.priority.tier]++;
665
+ byImpact[finding.dataImpact.classification]++;
666
+ byCategory[finding.finding.category]++;
667
+ for (const field of finding.dataImpact.sensitiveFields) {
668
+ sensitiveFieldsSet.add(`${field.field.table}.${field.field.field}`);
669
+ }
670
+ for (const table of finding.dataImpact.tables) {
671
+ tablesSet.add(table);
672
+ }
673
+ for (const reg of finding.dataImpact.regulations) {
674
+ regulationsSet.add(reg);
675
+ }
676
+ }
677
+ // Get top priority findings
678
+ const topPriority = findings
679
+ .filter((f) => f.priority.tier === 'P0' || f.priority.tier === 'P1')
680
+ .sort((a, b) => b.priority.overall - a.priority.overall)
681
+ .slice(0, 10);
682
+ return {
683
+ totalFindings: findings.length,
684
+ byPriority,
685
+ byImpact,
686
+ byCategory,
687
+ sensitiveFieldsAtRisk: sensitiveFieldsSet.size,
688
+ tablesAtRisk: tablesSet.size,
689
+ regulationsImplicated: Array.from(regulationsSet),
690
+ topPriority,
691
+ };
692
+ }
693
+ /**
694
+ * Get base severity score
695
+ */
696
+ getBaseSeverityScore(severity) {
697
+ const scores = { critical: 90, high: 70, medium: 50, low: 25, info: 10 };
698
+ return scores[severity];
699
+ }
700
+ /**
701
+ * Get base priority tier from severity
702
+ */
703
+ getBaseTier(severity) {
704
+ const tiers = {
705
+ critical: 'P1', high: 'P2', medium: 'P3', low: 'P4', info: 'P4',
706
+ };
707
+ return tiers[severity];
708
+ }
709
+ /**
710
+ * Create empty data impact
711
+ */
712
+ createEmptyDataImpact() {
713
+ return {
714
+ tables: [],
715
+ sensitiveFields: [],
716
+ reachableData: [],
717
+ maxDepth: 0,
718
+ attackSurfaceSize: 0,
719
+ regulations: [],
720
+ score: 0,
721
+ classification: 'none',
722
+ };
723
+ }
724
+ /**
725
+ * Create empty blast radius
726
+ */
727
+ createEmptyBlastRadius() {
728
+ return {
729
+ entryPoints: [],
730
+ relatedVulnerabilities: [],
731
+ affectedFunctions: [],
732
+ linesOfCode: 0,
733
+ score: 0,
734
+ classification: 'contained',
735
+ };
736
+ }
737
+ /**
738
+ * Create empty remediation
739
+ */
740
+ createEmptyRemediation() {
741
+ return {
742
+ summary: 'Review and address the security finding',
743
+ steps: [],
744
+ codeExamples: [],
745
+ effort: {
746
+ time: 'hours',
747
+ complexity: 'moderate',
748
+ regressionRisk: 'medium',
749
+ },
750
+ references: [],
751
+ };
752
+ }
753
+ }
754
+ /**
755
+ * Create a new enrichment engine
756
+ */
757
+ export function createEnrichmentEngine(graph, dataAccessPoints, sensitiveFields) {
758
+ return new EnrichmentEngine(graph, dataAccessPoints, sensitiveFields);
759
+ }
760
+ //# sourceMappingURL=enrichment-engine.js.map