driftdetect-core 0.4.1 → 0.4.3
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/LICENSE +21 -0
- package/dist/boundaries/boundary-scanner.d.ts +76 -0
- package/dist/boundaries/boundary-scanner.d.ts.map +1 -0
- package/dist/boundaries/boundary-scanner.js +801 -0
- package/dist/boundaries/boundary-scanner.js.map +1 -0
- package/dist/boundaries/data-access-learner.d.ts +126 -0
- package/dist/boundaries/data-access-learner.d.ts.map +1 -0
- package/dist/boundaries/data-access-learner.js +486 -0
- package/dist/boundaries/data-access-learner.js.map +1 -0
- package/dist/boundaries/index.d.ts +6 -0
- package/dist/boundaries/index.d.ts.map +1 -1
- package/dist/boundaries/index.js +6 -0
- package/dist/boundaries/index.js.map +1 -1
- package/dist/boundaries/security-prioritizer.d.ts +118 -0
- package/dist/boundaries/security-prioritizer.d.ts.map +1 -0
- package/dist/boundaries/security-prioritizer.js +316 -0
- package/dist/boundaries/security-prioritizer.js.map +1 -0
- package/dist/call-graph/analysis/coverage-analyzer.d.ts +201 -0
- package/dist/call-graph/analysis/coverage-analyzer.d.ts.map +1 -0
- package/dist/call-graph/analysis/coverage-analyzer.js +553 -0
- package/dist/call-graph/analysis/coverage-analyzer.js.map +1 -0
- package/dist/call-graph/analysis/dead-code-detector.d.ts +145 -0
- package/dist/call-graph/analysis/dead-code-detector.d.ts.map +1 -0
- package/dist/call-graph/analysis/dead-code-detector.js +391 -0
- package/dist/call-graph/analysis/dead-code-detector.js.map +1 -0
- package/dist/call-graph/analysis/graph-builder.d.ts +142 -0
- package/dist/call-graph/analysis/graph-builder.d.ts.map +1 -0
- package/dist/call-graph/analysis/graph-builder.js +624 -0
- package/dist/call-graph/analysis/graph-builder.js.map +1 -0
- package/dist/call-graph/analysis/impact-analyzer.d.ts +150 -0
- package/dist/call-graph/analysis/impact-analyzer.d.ts.map +1 -0
- package/dist/call-graph/analysis/impact-analyzer.js +329 -0
- package/dist/call-graph/analysis/impact-analyzer.js.map +1 -0
- package/dist/call-graph/analysis/index.d.ts +11 -0
- package/dist/call-graph/analysis/index.d.ts.map +1 -0
- package/dist/call-graph/analysis/index.js +9 -0
- package/dist/call-graph/analysis/index.js.map +1 -0
- package/dist/call-graph/analysis/path-finder.d.ts +117 -0
- package/dist/call-graph/analysis/path-finder.d.ts.map +1 -0
- package/dist/call-graph/analysis/path-finder.js +360 -0
- package/dist/call-graph/analysis/path-finder.js.map +1 -0
- package/dist/call-graph/analysis/reachability.d.ts +56 -0
- package/dist/call-graph/analysis/reachability.d.ts.map +1 -0
- package/dist/call-graph/analysis/reachability.js +357 -0
- package/dist/call-graph/analysis/reachability.js.map +1 -0
- package/dist/call-graph/demo.d.ts +11 -0
- package/dist/call-graph/demo.d.ts.map +1 -0
- package/dist/call-graph/demo.js +339 -0
- package/dist/call-graph/demo.js.map +1 -0
- package/dist/call-graph/enrichment/enrichment-engine.d.ts +126 -0
- package/dist/call-graph/enrichment/enrichment-engine.d.ts.map +1 -0
- package/dist/call-graph/enrichment/enrichment-engine.js +760 -0
- package/dist/call-graph/enrichment/enrichment-engine.js.map +1 -0
- package/dist/call-graph/enrichment/impact-scorer.d.ts +59 -0
- package/dist/call-graph/enrichment/impact-scorer.d.ts.map +1 -0
- package/dist/call-graph/enrichment/impact-scorer.js +328 -0
- package/dist/call-graph/enrichment/impact-scorer.js.map +1 -0
- package/dist/call-graph/enrichment/index.d.ts +12 -0
- package/dist/call-graph/enrichment/index.d.ts.map +1 -0
- package/dist/call-graph/enrichment/index.js +15 -0
- package/dist/call-graph/enrichment/index.js.map +1 -0
- package/dist/call-graph/enrichment/remediation-generator.d.ts +41 -0
- package/dist/call-graph/enrichment/remediation-generator.d.ts.map +1 -0
- package/dist/call-graph/enrichment/remediation-generator.js +609 -0
- package/dist/call-graph/enrichment/remediation-generator.js.map +1 -0
- package/dist/call-graph/enrichment/sensitivity-classifier.d.ts +71 -0
- package/dist/call-graph/enrichment/sensitivity-classifier.d.ts.map +1 -0
- package/dist/call-graph/enrichment/sensitivity-classifier.js +454 -0
- package/dist/call-graph/enrichment/sensitivity-classifier.js.map +1 -0
- package/dist/call-graph/enrichment/types.d.ts +402 -0
- package/dist/call-graph/enrichment/types.d.ts.map +1 -0
- package/dist/call-graph/enrichment/types.js +9 -0
- package/dist/call-graph/enrichment/types.js.map +1 -0
- package/dist/call-graph/extractors/base-extractor.d.ts +112 -0
- package/dist/call-graph/extractors/base-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/base-extractor.js +140 -0
- package/dist/call-graph/extractors/base-extractor.js.map +1 -0
- package/dist/call-graph/extractors/csharp-data-access-extractor.d.ts +76 -0
- package/dist/call-graph/extractors/csharp-data-access-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/csharp-data-access-extractor.js +387 -0
- package/dist/call-graph/extractors/csharp-data-access-extractor.js.map +1 -0
- package/dist/call-graph/extractors/csharp-extractor.d.ts +87 -0
- package/dist/call-graph/extractors/csharp-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/csharp-extractor.js +470 -0
- package/dist/call-graph/extractors/csharp-extractor.js.map +1 -0
- package/dist/call-graph/extractors/data-access-extractor.d.ts +76 -0
- package/dist/call-graph/extractors/data-access-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/data-access-extractor.js +234 -0
- package/dist/call-graph/extractors/data-access-extractor.js.map +1 -0
- package/dist/call-graph/extractors/index.d.ts +26 -0
- package/dist/call-graph/extractors/index.d.ts.map +1 -0
- package/dist/call-graph/extractors/index.js +36 -0
- package/dist/call-graph/extractors/index.js.map +1 -0
- package/dist/call-graph/extractors/java-data-access-extractor.d.ts +101 -0
- package/dist/call-graph/extractors/java-data-access-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/java-data-access-extractor.js +611 -0
- package/dist/call-graph/extractors/java-data-access-extractor.js.map +1 -0
- package/dist/call-graph/extractors/java-extractor.d.ts +87 -0
- package/dist/call-graph/extractors/java-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/java-extractor.js +510 -0
- package/dist/call-graph/extractors/java-extractor.js.map +1 -0
- package/dist/call-graph/extractors/php-data-access-extractor.d.ts +93 -0
- package/dist/call-graph/extractors/php-data-access-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/php-data-access-extractor.js +589 -0
- package/dist/call-graph/extractors/php-data-access-extractor.js.map +1 -0
- package/dist/call-graph/extractors/php-extractor.d.ts +104 -0
- package/dist/call-graph/extractors/php-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/php-extractor.js +619 -0
- package/dist/call-graph/extractors/php-extractor.js.map +1 -0
- package/dist/call-graph/extractors/python-data-access-extractor.d.ts +90 -0
- package/dist/call-graph/extractors/python-data-access-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/python-data-access-extractor.js +537 -0
- package/dist/call-graph/extractors/python-data-access-extractor.js.map +1 -0
- package/dist/call-graph/extractors/python-extractor.d.ts +98 -0
- package/dist/call-graph/extractors/python-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/python-extractor.js +681 -0
- package/dist/call-graph/extractors/python-extractor.js.map +1 -0
- package/dist/call-graph/extractors/semantic-data-access-scanner.d.ts +91 -0
- package/dist/call-graph/extractors/semantic-data-access-scanner.d.ts.map +1 -0
- package/dist/call-graph/extractors/semantic-data-access-scanner.js +498 -0
- package/dist/call-graph/extractors/semantic-data-access-scanner.js.map +1 -0
- package/dist/call-graph/extractors/typescript-data-access-extractor.d.ts +122 -0
- package/dist/call-graph/extractors/typescript-data-access-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/typescript-data-access-extractor.js +788 -0
- package/dist/call-graph/extractors/typescript-data-access-extractor.js.map +1 -0
- package/dist/call-graph/extractors/typescript-extractor.d.ts +145 -0
- package/dist/call-graph/extractors/typescript-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/typescript-extractor.js +904 -0
- package/dist/call-graph/extractors/typescript-extractor.js.map +1 -0
- package/dist/call-graph/index.d.ts +127 -0
- package/dist/call-graph/index.d.ts.map +1 -0
- package/dist/call-graph/index.js +247 -0
- package/dist/call-graph/index.js.map +1 -0
- package/dist/call-graph/store/call-graph-store.d.ts +70 -0
- package/dist/call-graph/store/call-graph-store.d.ts.map +1 -0
- package/dist/call-graph/store/call-graph-store.js +210 -0
- package/dist/call-graph/store/call-graph-store.js.map +1 -0
- package/dist/call-graph/store/index.d.ts +7 -0
- package/dist/call-graph/store/index.d.ts.map +1 -0
- package/dist/call-graph/store/index.js +7 -0
- package/dist/call-graph/store/index.js.map +1 -0
- package/dist/call-graph/types.d.ts +376 -0
- package/dist/call-graph/types.d.ts.map +1 -0
- package/dist/call-graph/types.js +8 -0
- package/dist/call-graph/types.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/lake/callgraph-shard-store.d.ts +168 -0
- package/dist/lake/callgraph-shard-store.d.ts.map +1 -0
- package/dist/lake/callgraph-shard-store.js +466 -0
- package/dist/lake/callgraph-shard-store.js.map +1 -0
- package/dist/lake/examples-store.d.ts +127 -0
- package/dist/lake/examples-store.d.ts.map +1 -0
- package/dist/lake/examples-store.js +389 -0
- package/dist/lake/examples-store.js.map +1 -0
- package/dist/lake/index-store.d.ts +82 -0
- package/dist/lake/index-store.d.ts.map +1 -0
- package/dist/lake/index-store.js +359 -0
- package/dist/lake/index-store.js.map +1 -0
- package/dist/lake/index.d.ts +93 -0
- package/dist/lake/index.d.ts.map +1 -0
- package/dist/lake/index.js +138 -0
- package/dist/lake/index.js.map +1 -0
- package/dist/lake/lake.bak/index-store.d.ts +82 -0
- package/dist/lake/lake.bak/index-store.d.ts.map +1 -0
- package/dist/lake/lake.bak/index-store.js +357 -0
- package/dist/lake/lake.bak/index-store.js.map +1 -0
- package/dist/lake/lake.bak/index.d.ts +81 -0
- package/dist/lake/lake.bak/index.d.ts.map +1 -0
- package/dist/lake/lake.bak/index.js +114 -0
- package/dist/lake/lake.bak/index.js.map +1 -0
- package/dist/lake/lake.bak/manifest-store.d.ts +51 -0
- package/dist/lake/lake.bak/manifest-store.d.ts.map +1 -0
- package/dist/lake/lake.bak/manifest-store.js +347 -0
- package/dist/lake/lake.bak/manifest-store.js.map +1 -0
- package/dist/lake/lake.bak/query-engine.d.ts +112 -0
- package/dist/lake/lake.bak/query-engine.d.ts.map +1 -0
- package/dist/lake/lake.bak/query-engine.js +370 -0
- package/dist/lake/lake.bak/query-engine.js.map +1 -0
- package/dist/lake/lake.bak/types.d.ts +428 -0
- package/dist/lake/lake.bak/types.d.ts.map +1 -0
- package/dist/lake/lake.bak/types.js +46 -0
- package/dist/lake/lake.bak/types.js.map +1 -0
- package/dist/lake/lake.bak/view-materializer.d.ts +70 -0
- package/dist/lake/lake.bak/view-materializer.d.ts.map +1 -0
- package/dist/lake/lake.bak/view-materializer.js +314 -0
- package/dist/lake/lake.bak/view-materializer.js.map +1 -0
- package/dist/lake/lake.bak/view-store.d.ts +57 -0
- package/dist/lake/lake.bak/view-store.d.ts.map +1 -0
- package/dist/lake/lake.bak/view-store.js +348 -0
- package/dist/lake/lake.bak/view-store.js.map +1 -0
- package/dist/lake/manifest-store.d.ts +51 -0
- package/dist/lake/manifest-store.d.ts.map +1 -0
- package/dist/lake/manifest-store.js +348 -0
- package/dist/lake/manifest-store.js.map +1 -0
- package/dist/lake/pattern-shard-store.d.ts +87 -0
- package/dist/lake/pattern-shard-store.d.ts.map +1 -0
- package/dist/lake/pattern-shard-store.js +347 -0
- package/dist/lake/pattern-shard-store.js.map +1 -0
- package/dist/lake/query-engine.d.ts +124 -0
- package/dist/lake/query-engine.d.ts.map +1 -0
- package/dist/lake/query-engine.js +453 -0
- package/dist/lake/query-engine.js.map +1 -0
- package/dist/lake/security-shard-store.d.ts +156 -0
- package/dist/lake/security-shard-store.d.ts.map +1 -0
- package/dist/lake/security-shard-store.js +498 -0
- package/dist/lake/security-shard-store.js.map +1 -0
- package/dist/lake/types.d.ts +428 -0
- package/dist/lake/types.d.ts.map +1 -0
- package/dist/lake/types.js +46 -0
- package/dist/lake/types.js.map +1 -0
- package/dist/lake/view-materializer.d.ts +70 -0
- package/dist/lake/view-materializer.d.ts.map +1 -0
- package/dist/lake/view-materializer.js +314 -0
- package/dist/lake/view-materializer.js.map +1 -0
- package/dist/lake/view-store.d.ts +57 -0
- package/dist/lake/view-store.d.ts.map +1 -0
- package/dist/lake/view-store.js +348 -0
- package/dist/lake/view-store.js.map +1 -0
- package/dist/parsers/tree-sitter/index.d.ts +1 -0
- package/dist/parsers/tree-sitter/index.d.ts.map +1 -1
- package/dist/parsers/tree-sitter/index.js +4 -0
- package/dist/parsers/tree-sitter/index.js.map +1 -1
- package/dist/parsers/tree-sitter/typescript-loader.d.ts +58 -0
- package/dist/parsers/tree-sitter/typescript-loader.d.ts.map +1 -0
- package/dist/parsers/tree-sitter/typescript-loader.js +250 -0
- package/dist/parsers/tree-sitter/typescript-loader.js.map +1 -0
- package/dist/store/project-config.d.ts +154 -0
- package/dist/store/project-config.d.ts.map +1 -0
- package/dist/store/project-config.js +235 -0
- package/dist/store/project-config.js.map +1 -0
- package/dist/store/project-registry.d.ts +241 -0
- package/dist/store/project-registry.d.ts.map +1 -0
- package/dist/store/project-registry.js +557 -0
- package/dist/store/project-registry.js.map +1 -0
- package/package.json +16 -14
|
@@ -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
|