ferret-scan 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/LICENSE +21 -0
- package/README.md +416 -0
- package/bin/ferret.js +822 -0
- package/dist/__tests__/basic.test.d.ts +6 -0
- package/dist/__tests__/basic.test.js +80 -0
- package/dist/analyzers/AstAnalyzer.d.ts +30 -0
- package/dist/analyzers/AstAnalyzer.js +332 -0
- package/dist/analyzers/CorrelationAnalyzer.d.ts +21 -0
- package/dist/analyzers/CorrelationAnalyzer.js +288 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +22 -0
- package/dist/intelligence/IndicatorMatcher.d.ts +50 -0
- package/dist/intelligence/IndicatorMatcher.js +285 -0
- package/dist/intelligence/ThreatFeed.d.ts +99 -0
- package/dist/intelligence/ThreatFeed.js +296 -0
- package/dist/remediation/Fixer.d.ts +71 -0
- package/dist/remediation/Fixer.js +391 -0
- package/dist/remediation/Quarantine.d.ts +102 -0
- package/dist/remediation/Quarantine.js +329 -0
- package/dist/reporters/ConsoleReporter.d.ts +13 -0
- package/dist/reporters/ConsoleReporter.js +185 -0
- package/dist/reporters/HtmlReporter.d.ts +25 -0
- package/dist/reporters/HtmlReporter.js +604 -0
- package/dist/reporters/SarifReporter.d.ts +86 -0
- package/dist/reporters/SarifReporter.js +117 -0
- package/dist/rules/ai-specific.d.ts +8 -0
- package/dist/rules/ai-specific.js +221 -0
- package/dist/rules/backdoors.d.ts +8 -0
- package/dist/rules/backdoors.js +134 -0
- package/dist/rules/correlationRules.d.ts +8 -0
- package/dist/rules/correlationRules.js +227 -0
- package/dist/rules/credentials.d.ts +8 -0
- package/dist/rules/credentials.js +194 -0
- package/dist/rules/exfiltration.d.ts +8 -0
- package/dist/rules/exfiltration.js +139 -0
- package/dist/rules/index.d.ts +51 -0
- package/dist/rules/index.js +97 -0
- package/dist/rules/injection.d.ts +8 -0
- package/dist/rules/injection.js +136 -0
- package/dist/rules/obfuscation.d.ts +8 -0
- package/dist/rules/obfuscation.js +159 -0
- package/dist/rules/permissions.d.ts +8 -0
- package/dist/rules/permissions.js +129 -0
- package/dist/rules/persistence.d.ts +8 -0
- package/dist/rules/persistence.js +117 -0
- package/dist/rules/semanticRules.d.ts +10 -0
- package/dist/rules/semanticRules.js +212 -0
- package/dist/rules/supply-chain.d.ts +8 -0
- package/dist/rules/supply-chain.js +148 -0
- package/dist/scanner/FileDiscovery.d.ts +24 -0
- package/dist/scanner/FileDiscovery.js +282 -0
- package/dist/scanner/PatternMatcher.d.ts +25 -0
- package/dist/scanner/PatternMatcher.js +206 -0
- package/dist/scanner/Scanner.d.ts +14 -0
- package/dist/scanner/Scanner.js +266 -0
- package/dist/scanner/WatchMode.d.ts +29 -0
- package/dist/scanner/WatchMode.js +195 -0
- package/dist/types.d.ts +332 -0
- package/dist/types.js +53 -0
- package/dist/utils/baseline.d.ts +80 -0
- package/dist/utils/baseline.js +276 -0
- package/dist/utils/config.d.ts +21 -0
- package/dist/utils/config.js +247 -0
- package/dist/utils/ignore.d.ts +18 -0
- package/dist/utils/ignore.js +82 -0
- package/dist/utils/logger.d.ts +32 -0
- package/dist/utils/logger.js +75 -0
- package/package.json +119 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Basic Tests
|
|
3
|
+
* Simple tests to verify core functionality
|
|
4
|
+
*/
|
|
5
|
+
import { getAllRules } from '../rules/index.js';
|
|
6
|
+
import { formatSarifReport } from '../reporters/SarifReporter.js';
|
|
7
|
+
describe('Basic Functionality', () => {
|
|
8
|
+
describe('Rules', () => {
|
|
9
|
+
it('should load all rules', () => {
|
|
10
|
+
const rules = getAllRules();
|
|
11
|
+
expect(rules.length).toBeGreaterThan(0);
|
|
12
|
+
expect(rules.every(rule => rule.id && rule.name)).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
it('should have valid patterns', () => {
|
|
15
|
+
const rules = getAllRules();
|
|
16
|
+
for (const rule of rules) {
|
|
17
|
+
// Rules may have patterns, semanticPatterns, or correlationRules
|
|
18
|
+
const hasPatterns = rule.patterns.length > 0;
|
|
19
|
+
const hasSemanticPatterns = (rule.semanticPatterns?.length ?? 0) > 0;
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
21
|
+
const hasCorrelationRules = (rule.correlationRules?.length ?? 0) > 0;
|
|
22
|
+
expect(hasPatterns || hasSemanticPatterns || hasCorrelationRules).toBe(true);
|
|
23
|
+
for (const pattern of rule.patterns) {
|
|
24
|
+
expect(pattern).toBeInstanceOf(RegExp);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe('SARIF Reporter', () => {
|
|
30
|
+
it('should generate valid SARIF for empty results', () => {
|
|
31
|
+
const mockResult = {
|
|
32
|
+
success: true,
|
|
33
|
+
startTime: new Date(),
|
|
34
|
+
endTime: new Date(),
|
|
35
|
+
duration: 100,
|
|
36
|
+
scannedPaths: [],
|
|
37
|
+
totalFiles: 0,
|
|
38
|
+
analyzedFiles: 0,
|
|
39
|
+
skippedFiles: 0,
|
|
40
|
+
findings: [],
|
|
41
|
+
findingsBySeverity: {
|
|
42
|
+
CRITICAL: [],
|
|
43
|
+
HIGH: [],
|
|
44
|
+
MEDIUM: [],
|
|
45
|
+
LOW: [],
|
|
46
|
+
INFO: [],
|
|
47
|
+
},
|
|
48
|
+
findingsByCategory: {
|
|
49
|
+
injection: [],
|
|
50
|
+
credentials: [],
|
|
51
|
+
backdoors: [],
|
|
52
|
+
'supply-chain': [],
|
|
53
|
+
permissions: [],
|
|
54
|
+
persistence: [],
|
|
55
|
+
obfuscation: [],
|
|
56
|
+
'ai-specific': [],
|
|
57
|
+
'advanced-hiding': [],
|
|
58
|
+
behavioral: [],
|
|
59
|
+
exfiltration: [],
|
|
60
|
+
},
|
|
61
|
+
overallRiskScore: 0,
|
|
62
|
+
summary: {
|
|
63
|
+
critical: 0,
|
|
64
|
+
high: 0,
|
|
65
|
+
medium: 0,
|
|
66
|
+
low: 0,
|
|
67
|
+
info: 0,
|
|
68
|
+
total: 0,
|
|
69
|
+
},
|
|
70
|
+
errors: [],
|
|
71
|
+
};
|
|
72
|
+
const sarifOutput = formatSarifReport(mockResult);
|
|
73
|
+
const parsed = JSON.parse(sarifOutput);
|
|
74
|
+
expect(parsed.version).toBe('2.1.0');
|
|
75
|
+
expect(parsed.runs).toHaveLength(1);
|
|
76
|
+
expect(parsed.runs[0]?.tool.driver.name).toBe('ferret-scan');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
//# sourceMappingURL=basic.test.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST Analyzer - TypeScript/JavaScript semantic analysis for security scanning
|
|
3
|
+
* Analyzes code blocks in markdown and TypeScript configurations for complex patterns
|
|
4
|
+
*/
|
|
5
|
+
import type { SemanticFinding, DiscoveredFile, Rule } from '../types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Analyze a single file for semantic patterns
|
|
8
|
+
*/
|
|
9
|
+
export declare function analyzeFile(file: DiscoveredFile, content: string, rules: Rule[]): SemanticFinding[];
|
|
10
|
+
/**
|
|
11
|
+
* Check if semantic analysis should be performed
|
|
12
|
+
*/
|
|
13
|
+
export declare function shouldAnalyze(file: DiscoveredFile, config: {
|
|
14
|
+
semanticAnalysis: boolean;
|
|
15
|
+
maxFileSize: number;
|
|
16
|
+
}): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Get memory usage for monitoring
|
|
19
|
+
*/
|
|
20
|
+
export declare function getMemoryUsage(): {
|
|
21
|
+
used: number;
|
|
22
|
+
total: number;
|
|
23
|
+
};
|
|
24
|
+
declare const _default: {
|
|
25
|
+
analyzeFile: typeof analyzeFile;
|
|
26
|
+
shouldAnalyze: typeof shouldAnalyze;
|
|
27
|
+
getMemoryUsage: typeof getMemoryUsage;
|
|
28
|
+
};
|
|
29
|
+
export default _default;
|
|
30
|
+
//# sourceMappingURL=AstAnalyzer.d.ts.map
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST Analyzer - TypeScript/JavaScript semantic analysis for security scanning
|
|
3
|
+
* Analyzes code blocks in markdown and TypeScript configurations for complex patterns
|
|
4
|
+
*/
|
|
5
|
+
import * as ts from 'typescript';
|
|
6
|
+
import logger from '../utils/logger.js';
|
|
7
|
+
/**
|
|
8
|
+
* Extract code blocks from markdown content
|
|
9
|
+
*/
|
|
10
|
+
function extractCodeBlocks(content) {
|
|
11
|
+
const codeBlocks = [];
|
|
12
|
+
const lines = content.split('\n');
|
|
13
|
+
let inCodeBlock = false;
|
|
14
|
+
let currentBlock = [];
|
|
15
|
+
let currentLanguage = '';
|
|
16
|
+
let blockStartLine = 0;
|
|
17
|
+
for (let i = 0; i < lines.length; i++) {
|
|
18
|
+
const line = lines[i];
|
|
19
|
+
const trimmedLine = line?.trim() ?? '';
|
|
20
|
+
if (trimmedLine.startsWith('```')) {
|
|
21
|
+
if (!inCodeBlock) {
|
|
22
|
+
// Starting a code block
|
|
23
|
+
inCodeBlock = true;
|
|
24
|
+
currentLanguage = trimmedLine.slice(3).trim().toLowerCase();
|
|
25
|
+
blockStartLine = i + 2; // +2 because we want the first line of actual code
|
|
26
|
+
currentBlock = [];
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// Ending a code block
|
|
30
|
+
inCodeBlock = false;
|
|
31
|
+
if (currentBlock.length > 0 && isAnalyzableLanguage(currentLanguage)) {
|
|
32
|
+
codeBlocks.push({
|
|
33
|
+
code: currentBlock.join('\n'),
|
|
34
|
+
language: currentLanguage,
|
|
35
|
+
line: blockStartLine || 1 // Ensure we have a valid line number
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
currentBlock = [];
|
|
39
|
+
currentLanguage = '';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (inCodeBlock) {
|
|
43
|
+
currentBlock.push(line ?? '');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return codeBlocks;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if language can be analyzed
|
|
50
|
+
*/
|
|
51
|
+
function isAnalyzableLanguage(language) {
|
|
52
|
+
const supportedLanguages = ['typescript', 'ts', 'javascript', 'js', 'jsx', 'tsx'];
|
|
53
|
+
return supportedLanguages.includes(language);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create TypeScript AST from code
|
|
57
|
+
*/
|
|
58
|
+
function createAST(code, fileName = 'analysis.ts') {
|
|
59
|
+
return ts.createSourceFile(fileName, code, ts.ScriptTarget.Latest, true, fileName?.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Extract semantic context from AST
|
|
63
|
+
*/
|
|
64
|
+
function extractSemanticContext(sourceFile) {
|
|
65
|
+
const context = {
|
|
66
|
+
variables: [],
|
|
67
|
+
imports: [],
|
|
68
|
+
callChain: []
|
|
69
|
+
};
|
|
70
|
+
function visit(node) {
|
|
71
|
+
switch (node.kind) {
|
|
72
|
+
case ts.SyntaxKind.ImportDeclaration: {
|
|
73
|
+
const importDecl = node;
|
|
74
|
+
if (importDecl.moduleSpecifier && ts.isStringLiteral(importDecl.moduleSpecifier)) {
|
|
75
|
+
context.imports.push(importDecl.moduleSpecifier.text);
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
case ts.SyntaxKind.VariableDeclaration: {
|
|
80
|
+
const varDecl = node;
|
|
81
|
+
if (varDecl.name && ts.isIdentifier(varDecl.name)) {
|
|
82
|
+
context.variables.push(varDecl.name.text);
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case ts.SyntaxKind.CallExpression: {
|
|
87
|
+
const callExpr = node;
|
|
88
|
+
const callText = callExpr.expression.getText(sourceFile);
|
|
89
|
+
context.callChain.push(callText);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
ts.forEachChild(node, visit);
|
|
94
|
+
}
|
|
95
|
+
visit(sourceFile);
|
|
96
|
+
return context;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Find security patterns in AST
|
|
100
|
+
*/
|
|
101
|
+
function findSecurityPatterns(sourceFile, patterns) {
|
|
102
|
+
const matches = [];
|
|
103
|
+
function visit(node) {
|
|
104
|
+
for (const pattern of patterns) {
|
|
105
|
+
const match = matchSemanticPattern(node, pattern, sourceFile);
|
|
106
|
+
if (match) {
|
|
107
|
+
matches.push({
|
|
108
|
+
pattern,
|
|
109
|
+
node,
|
|
110
|
+
confidence: match.confidence
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
ts.forEachChild(node, visit);
|
|
115
|
+
}
|
|
116
|
+
visit(sourceFile);
|
|
117
|
+
return matches;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Match a semantic pattern against an AST node
|
|
121
|
+
*/
|
|
122
|
+
function matchSemanticPattern(node, pattern, sourceFile) {
|
|
123
|
+
const nodeText = node.getText(sourceFile);
|
|
124
|
+
let confidence = pattern.confidence ?? 0.8;
|
|
125
|
+
switch (pattern.type) {
|
|
126
|
+
case 'function-call':
|
|
127
|
+
if (ts.isCallExpression(node)) {
|
|
128
|
+
const functionName = node.expression.getText(sourceFile);
|
|
129
|
+
if (functionName.includes(pattern.pattern)) {
|
|
130
|
+
return { confidence };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
case 'property-access':
|
|
135
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
136
|
+
const fullAccess = node.getText(sourceFile);
|
|
137
|
+
if (fullAccess.includes(pattern.pattern)) {
|
|
138
|
+
return { confidence };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
case 'dynamic-import':
|
|
143
|
+
if (ts.isCallExpression(node)) {
|
|
144
|
+
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
145
|
+
// Dynamic import detected - pattern for security analysis only
|
|
146
|
+
if (pattern.pattern === 'dynamic-import' || nodeText.includes(pattern.pattern)) {
|
|
147
|
+
confidence += 0.1; // Higher confidence for dynamic imports
|
|
148
|
+
return { confidence };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
case 'eval-chain':
|
|
154
|
+
if (nodeText.toLowerCase().includes('eval') ||
|
|
155
|
+
nodeText.includes('Function(')) {
|
|
156
|
+
// Detecting eval patterns for security analysis - not executing
|
|
157
|
+
if (pattern.pattern === 'eval' || nodeText.includes(pattern.pattern)) {
|
|
158
|
+
confidence += 0.2; // Higher confidence for eval usage
|
|
159
|
+
return { confidence };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
case 'object-structure':
|
|
164
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
165
|
+
if (nodeText.includes(pattern.pattern)) {
|
|
166
|
+
return { confidence };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Create AST node info
|
|
175
|
+
*/
|
|
176
|
+
function createASTNodeInfo(node, sourceFile) {
|
|
177
|
+
const nodeName = getNodeName(node);
|
|
178
|
+
return {
|
|
179
|
+
nodeType: ts.SyntaxKind[node.kind],
|
|
180
|
+
...(nodeName && { name: nodeName }),
|
|
181
|
+
...(node.parent && { parent: ts.SyntaxKind[node.parent.kind] }),
|
|
182
|
+
children: node.getChildren(sourceFile).map(child => ts.SyntaxKind[child.kind])
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get node name/identifier
|
|
187
|
+
*/
|
|
188
|
+
function getNodeName(node) {
|
|
189
|
+
if (ts.isIdentifier(node)) {
|
|
190
|
+
return node.text;
|
|
191
|
+
}
|
|
192
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
193
|
+
return node.name.text;
|
|
194
|
+
}
|
|
195
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {
|
|
196
|
+
return node.name.text;
|
|
197
|
+
}
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Get line and column from AST node
|
|
202
|
+
*/
|
|
203
|
+
function getPositionFromNode(node, sourceFile) {
|
|
204
|
+
const pos = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
205
|
+
return {
|
|
206
|
+
line: pos.line + 1, // Convert to 1-based
|
|
207
|
+
column: pos.character + 1
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Create context lines for semantic finding
|
|
212
|
+
*/
|
|
213
|
+
function createContextLines(sourceFile, node, contextLines = 3) {
|
|
214
|
+
const text = sourceFile.getText();
|
|
215
|
+
const lines = text.split('\n');
|
|
216
|
+
const pos = getPositionFromNode(node, sourceFile);
|
|
217
|
+
const matchLine = pos.line - 1; // Convert back to 0-based
|
|
218
|
+
const start = Math.max(0, matchLine - contextLines);
|
|
219
|
+
const end = Math.min(lines.length, matchLine + contextLines + 1);
|
|
220
|
+
const context = [];
|
|
221
|
+
for (let i = start; i < end; i++) {
|
|
222
|
+
context.push({
|
|
223
|
+
lineNumber: i + 1,
|
|
224
|
+
content: lines[i] ?? '',
|
|
225
|
+
isMatch: i === matchLine
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return context;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Analyze a single file for semantic patterns
|
|
232
|
+
*/
|
|
233
|
+
export function analyzeFile(file, content, rules) {
|
|
234
|
+
const findings = [];
|
|
235
|
+
try {
|
|
236
|
+
// Get rules with semantic patterns
|
|
237
|
+
const semanticRules = rules.filter(rule => rule.semanticPatterns && rule.semanticPatterns.length > 0);
|
|
238
|
+
if (semanticRules.length === 0) {
|
|
239
|
+
return findings;
|
|
240
|
+
}
|
|
241
|
+
logger.debug(`AST analysis for ${file.relativePath} with ${semanticRules.length} semantic rules`);
|
|
242
|
+
let codeBlocksToAnalyze = [];
|
|
243
|
+
// Extract code blocks from markdown files
|
|
244
|
+
if (file.type === 'md') {
|
|
245
|
+
codeBlocksToAnalyze = extractCodeBlocks(content);
|
|
246
|
+
}
|
|
247
|
+
else if (['ts', 'js', 'tsx', 'jsx'].includes(file.type)) {
|
|
248
|
+
// Analyze the entire file for TypeScript/JavaScript files
|
|
249
|
+
codeBlocksToAnalyze = [{ code: content, language: file.type, line: 1 }];
|
|
250
|
+
}
|
|
251
|
+
// Analyze each code block
|
|
252
|
+
for (const codeBlock of codeBlocksToAnalyze) {
|
|
253
|
+
try {
|
|
254
|
+
const sourceFile = createAST(codeBlock.code, `${file.relativePath}_block_${codeBlock.line}.${codeBlock.language}`);
|
|
255
|
+
const semanticContext = extractSemanticContext(sourceFile);
|
|
256
|
+
// Check each semantic rule
|
|
257
|
+
for (const rule of semanticRules) {
|
|
258
|
+
if (!rule.semanticPatterns)
|
|
259
|
+
continue;
|
|
260
|
+
const patternMatches = findSecurityPatterns(sourceFile, rule.semanticPatterns);
|
|
261
|
+
for (const match of patternMatches) {
|
|
262
|
+
const position = getPositionFromNode(match.node, sourceFile);
|
|
263
|
+
const astNodeInfo = createASTNodeInfo(match.node, sourceFile);
|
|
264
|
+
const contextLines = createContextLines(sourceFile, match.node, 3);
|
|
265
|
+
const finding = {
|
|
266
|
+
ruleId: rule.id,
|
|
267
|
+
ruleName: rule.name,
|
|
268
|
+
severity: rule.severity,
|
|
269
|
+
category: rule.category,
|
|
270
|
+
file: file.path,
|
|
271
|
+
relativePath: file.relativePath,
|
|
272
|
+
line: (codeBlock.line || 1) + position.line - 1, // Adjust for code block position
|
|
273
|
+
column: position.column,
|
|
274
|
+
match: match.node.getText(sourceFile).substring(0, 100), // Limit match length
|
|
275
|
+
context: contextLines,
|
|
276
|
+
remediation: rule.remediation,
|
|
277
|
+
metadata: {
|
|
278
|
+
semanticPattern: match.pattern,
|
|
279
|
+
codeBlock: codeBlock.line,
|
|
280
|
+
language: codeBlock.language
|
|
281
|
+
},
|
|
282
|
+
timestamp: new Date(),
|
|
283
|
+
riskScore: Math.round(match.confidence * 100),
|
|
284
|
+
astNode: astNodeInfo,
|
|
285
|
+
semanticContext,
|
|
286
|
+
confidence: match.confidence
|
|
287
|
+
};
|
|
288
|
+
findings.push(finding);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
logger.warn(`Error analyzing code block at line ${codeBlock.line} in ${file.relativePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
logger.error(`Error in semantic analysis for ${file.relativePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
299
|
+
}
|
|
300
|
+
return findings;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Check if semantic analysis should be performed
|
|
304
|
+
*/
|
|
305
|
+
export function shouldAnalyze(file, config) {
|
|
306
|
+
if (!config.semanticAnalysis) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
// Skip files that are too large
|
|
310
|
+
if (file.size > config.maxFileSize) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
// Only analyze markdown and TypeScript/JavaScript files
|
|
314
|
+
const supportedTypes = ['md', 'ts', 'js', 'tsx', 'jsx'];
|
|
315
|
+
return supportedTypes.includes(file.type);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Get memory usage for monitoring
|
|
319
|
+
*/
|
|
320
|
+
export function getMemoryUsage() {
|
|
321
|
+
const memUsage = process.memoryUsage();
|
|
322
|
+
return {
|
|
323
|
+
used: Math.round(memUsage.heapUsed / 1024 / 1024), // MB
|
|
324
|
+
total: Math.round(memUsage.heapTotal / 1024 / 1024) // MB
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
export default {
|
|
328
|
+
analyzeFile,
|
|
329
|
+
shouldAnalyze,
|
|
330
|
+
getMemoryUsage
|
|
331
|
+
};
|
|
332
|
+
//# sourceMappingURL=AstAnalyzer.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Correlation Analyzer - Cross-file attack pattern detection
|
|
3
|
+
* Detects sophisticated attack patterns that span multiple configuration files
|
|
4
|
+
*/
|
|
5
|
+
import type { CorrelationFinding, DiscoveredFile, Rule } from '../types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Analyze files for cross-file correlation patterns
|
|
8
|
+
*/
|
|
9
|
+
export declare function analyzeCorrelations(files: DiscoveredFile[], rules: Rule[]): CorrelationFinding[];
|
|
10
|
+
/**
|
|
11
|
+
* Check if correlation analysis should be performed
|
|
12
|
+
*/
|
|
13
|
+
export declare function shouldAnalyzeCorrelations(files: DiscoveredFile[], config: {
|
|
14
|
+
correlationAnalysis: boolean;
|
|
15
|
+
}): boolean;
|
|
16
|
+
declare const _default: {
|
|
17
|
+
analyzeCorrelations: typeof analyzeCorrelations;
|
|
18
|
+
shouldAnalyzeCorrelations: typeof shouldAnalyzeCorrelations;
|
|
19
|
+
};
|
|
20
|
+
export default _default;
|
|
21
|
+
//# sourceMappingURL=CorrelationAnalyzer.d.ts.map
|