code-auditor-mcp 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/LICENSE +21 -0
- package/README.md +362 -0
- package/configs/hhra-compat.json +168 -0
- package/dist/analyzers/analyzerUtils.d.ts +69 -0
- package/dist/analyzers/analyzerUtils.d.ts.map +1 -0
- package/dist/analyzers/analyzerUtils.js +188 -0
- package/dist/analyzers/analyzerUtils.js.map +1 -0
- package/dist/analyzers/dataAccessAnalyzer.d.ts +46 -0
- package/dist/analyzers/dataAccessAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/dataAccessAnalyzer.js +448 -0
- package/dist/analyzers/dataAccessAnalyzer.js.map +1 -0
- package/dist/analyzers/dryAnalyzer.d.ts +30 -0
- package/dist/analyzers/dryAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/dryAnalyzer.js +537 -0
- package/dist/analyzers/dryAnalyzer.js.map +1 -0
- package/dist/analyzers/solidAnalyzer.d.ts +23 -0
- package/dist/analyzers/solidAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/solidAnalyzer.js +355 -0
- package/dist/analyzers/solidAnalyzer.js.map +1 -0
- package/dist/auditRunner.d.ts +20 -0
- package/dist/auditRunner.d.ts.map +1 -0
- package/dist/auditRunner.js +214 -0
- package/dist/auditRunner.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +38 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/configLoader.d.ts +19 -0
- package/dist/config/configLoader.d.ts.map +1 -0
- package/dist/config/configLoader.js +124 -0
- package/dist/config/configLoader.js.map +1 -0
- package/dist/config/defaults.d.ts +69 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +142 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +66 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-standalone.d.ts +3 -0
- package/dist/mcp-standalone.d.ts.map +1 -0
- package/dist/mcp-standalone.js +171 -0
- package/dist/mcp-standalone.js.map +1 -0
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +340 -0
- package/dist/mcp.js.map +1 -0
- package/dist/reporting/csvReportGenerator.d.ts +29 -0
- package/dist/reporting/csvReportGenerator.d.ts.map +1 -0
- package/dist/reporting/csvReportGenerator.js +182 -0
- package/dist/reporting/csvReportGenerator.js.map +1 -0
- package/dist/reporting/htmlReportGenerator.d.ts +18 -0
- package/dist/reporting/htmlReportGenerator.d.ts.map +1 -0
- package/dist/reporting/htmlReportGenerator.js +352 -0
- package/dist/reporting/htmlReportGenerator.js.map +1 -0
- package/dist/reporting/jsonReportGenerator.d.ts +23 -0
- package/dist/reporting/jsonReportGenerator.d.ts.map +1 -0
- package/dist/reporting/jsonReportGenerator.js +103 -0
- package/dist/reporting/jsonReportGenerator.js.map +1 -0
- package/dist/reporting/reportGenerator.d.ts +21 -0
- package/dist/reporting/reportGenerator.d.ts.map +1 -0
- package/dist/reporting/reportGenerator.js +53 -0
- package/dist/reporting/reportGenerator.js.map +1 -0
- package/dist/types.d.ts +246 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/astParser.d.ts +71 -0
- package/dist/utils/astParser.d.ts.map +1 -0
- package/dist/utils/astParser.js +244 -0
- package/dist/utils/astParser.js.map +1 -0
- package/dist/utils/astUtils.d.ts +89 -0
- package/dist/utils/astUtils.d.ts.map +1 -0
- package/dist/utils/astUtils.js +289 -0
- package/dist/utils/astUtils.js.map +1 -0
- package/dist/utils/fileDiscovery.d.ts +58 -0
- package/dist/utils/fileDiscovery.d.ts.map +1 -0
- package/dist/utils/fileDiscovery.js +203 -0
- package/dist/utils/fileDiscovery.js.map +1 -0
- package/examples/.auditrc.example.json +61 -0
- package/examples/.auditrc.json +201 -0
- package/examples/nextjs.auditrc.json +65 -0
- package/examples/node-api.auditrc.json +82 -0
- package/examples/react.auditrc.json +59 -0
- package/package.json +62 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Functional utilities for analyzer development
|
|
3
|
+
* These replace the BaseAnalyzer class with composable functions
|
|
4
|
+
*/
|
|
5
|
+
import * as ts from 'typescript';
|
|
6
|
+
// Re-export utilities from other modules
|
|
7
|
+
export { parseTypeScriptFile } from '../utils/astParser.js';
|
|
8
|
+
export { getLineAndColumn, getNodeText, getImports, findNodesByKind } from '../utils/astUtils.js';
|
|
9
|
+
/**
|
|
10
|
+
* Standard file processing function
|
|
11
|
+
* Handles file reading, parsing, error handling, and progress reporting
|
|
12
|
+
*/
|
|
13
|
+
export async function processFiles(files, analyzeFile, analyzerName, config = {}, progressReporter) {
|
|
14
|
+
const violations = [];
|
|
15
|
+
const errors = [];
|
|
16
|
+
let processedFiles = 0;
|
|
17
|
+
const startTime = Date.now();
|
|
18
|
+
for (const file of files) {
|
|
19
|
+
try {
|
|
20
|
+
// Report progress
|
|
21
|
+
if (progressReporter) {
|
|
22
|
+
progressReporter(processedFiles, files.length, file);
|
|
23
|
+
}
|
|
24
|
+
// Read and parse file
|
|
25
|
+
const { parseTypeScriptFile: parse } = await import('../utils/astParser.js');
|
|
26
|
+
const { sourceFile, errors: parseErrors } = await parse(file);
|
|
27
|
+
if (parseErrors.length > 0) {
|
|
28
|
+
throw new Error(`Parse errors: ${parseErrors.map(e => e.messageText).join(', ')}`);
|
|
29
|
+
}
|
|
30
|
+
// Run analyzer-specific logic
|
|
31
|
+
const fileViolations = await analyzeFile(file, sourceFile, config);
|
|
32
|
+
violations.push(...fileViolations);
|
|
33
|
+
processedFiles++;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
37
|
+
errors.push({ file, error: errorMessage });
|
|
38
|
+
console.error(`Error analyzing ${file}:`, error);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const result = {
|
|
42
|
+
violations,
|
|
43
|
+
filesProcessed: processedFiles,
|
|
44
|
+
executionTime: Date.now() - startTime,
|
|
45
|
+
analyzerName
|
|
46
|
+
};
|
|
47
|
+
if (errors.length > 0) {
|
|
48
|
+
result.errors = errors;
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create a violation object with defaults
|
|
54
|
+
*/
|
|
55
|
+
export function createViolation(data) {
|
|
56
|
+
return data;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get line and column from a TypeScript node
|
|
60
|
+
*/
|
|
61
|
+
export function getNodePosition(sourceFile, node) {
|
|
62
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
63
|
+
return { line: line + 1, column: character + 1 };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if a node is exported
|
|
67
|
+
*/
|
|
68
|
+
export function isNodeExported(node) {
|
|
69
|
+
if (ts.canHaveModifiers(node)) {
|
|
70
|
+
const modifiers = ts.getModifiers(node);
|
|
71
|
+
return !!modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get the name of a node if it has one
|
|
77
|
+
*/
|
|
78
|
+
export function getNodeName(node) {
|
|
79
|
+
if ('name' in node && node.name) {
|
|
80
|
+
const name = node.name;
|
|
81
|
+
if (ts.isIdentifier(name)) {
|
|
82
|
+
return name.text;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Count specific node types in a subtree
|
|
89
|
+
*/
|
|
90
|
+
export function countNodesOfType(node, predicate) {
|
|
91
|
+
let count = 0;
|
|
92
|
+
const visit = (node) => {
|
|
93
|
+
if (predicate(node)) {
|
|
94
|
+
count++;
|
|
95
|
+
}
|
|
96
|
+
ts.forEachChild(node, visit);
|
|
97
|
+
};
|
|
98
|
+
visit(node);
|
|
99
|
+
return count;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Find all nodes of a specific type
|
|
103
|
+
*/
|
|
104
|
+
export function findNodesOfType(node, predicate) {
|
|
105
|
+
const nodes = [];
|
|
106
|
+
const visit = (node) => {
|
|
107
|
+
if (predicate(node)) {
|
|
108
|
+
nodes.push(node);
|
|
109
|
+
}
|
|
110
|
+
ts.forEachChild(node, visit);
|
|
111
|
+
};
|
|
112
|
+
visit(node);
|
|
113
|
+
return nodes;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Traverse AST with a visitor function
|
|
117
|
+
*/
|
|
118
|
+
export function traverseAST(node, visitor) {
|
|
119
|
+
visitor(node);
|
|
120
|
+
ts.forEachChild(node, child => traverseAST(child, visitor));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Filter violations by severity
|
|
124
|
+
*/
|
|
125
|
+
export function filterViolationsBySeverity(violations, minSeverity) {
|
|
126
|
+
if (!minSeverity) {
|
|
127
|
+
return violations;
|
|
128
|
+
}
|
|
129
|
+
const severityOrder = { critical: 3, warning: 2, suggestion: 1 };
|
|
130
|
+
const minLevel = severityOrder[minSeverity] || 0;
|
|
131
|
+
return violations.filter(v => severityOrder[v.severity] >= minLevel);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Sort violations by severity, file, and line
|
|
135
|
+
*/
|
|
136
|
+
export function sortViolations(violations) {
|
|
137
|
+
return violations.sort((a, b) => {
|
|
138
|
+
// Sort by severity first
|
|
139
|
+
const severityOrder = { critical: 3, warning: 2, suggestion: 1 };
|
|
140
|
+
const severityDiff = severityOrder[b.severity] - severityOrder[a.severity];
|
|
141
|
+
if (severityDiff !== 0)
|
|
142
|
+
return severityDiff;
|
|
143
|
+
// Then by file
|
|
144
|
+
const fileDiff = a.file.localeCompare(b.file);
|
|
145
|
+
if (fileDiff !== 0)
|
|
146
|
+
return fileDiff;
|
|
147
|
+
// Then by line
|
|
148
|
+
return (a.line || 0) - (b.line || 0);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Calculate cyclomatic complexity of a function
|
|
153
|
+
*/
|
|
154
|
+
export function calculateComplexity(node) {
|
|
155
|
+
let complexity = 1;
|
|
156
|
+
traverseAST(node, (child) => {
|
|
157
|
+
if (ts.isIfStatement(child) ||
|
|
158
|
+
ts.isConditionalExpression(child) ||
|
|
159
|
+
ts.isSwitchStatement(child) ||
|
|
160
|
+
ts.isForStatement(child) ||
|
|
161
|
+
ts.isWhileStatement(child) ||
|
|
162
|
+
ts.isDoStatement(child) ||
|
|
163
|
+
ts.isCaseClause(child)) {
|
|
164
|
+
complexity++;
|
|
165
|
+
}
|
|
166
|
+
if (ts.isBinaryExpression(child)) {
|
|
167
|
+
const operator = child.operatorToken.kind;
|
|
168
|
+
if (operator === ts.SyntaxKind.AmpersandAmpersandToken ||
|
|
169
|
+
operator === ts.SyntaxKind.BarBarToken) {
|
|
170
|
+
complexity++;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
return complexity;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Create a standard analyzer function
|
|
178
|
+
*/
|
|
179
|
+
export function createAnalyzer(name, fileAnalyzer, defaultConfig = {}) {
|
|
180
|
+
return async (files, config, options) => {
|
|
181
|
+
const mergedConfig = { ...defaultConfig, ...config };
|
|
182
|
+
const result = await processFiles(files, fileAnalyzer, name, mergedConfig);
|
|
183
|
+
// Apply filtering and sorting
|
|
184
|
+
result.violations = sortViolations(filterViolationsBySeverity(result.violations, options?.minSeverity));
|
|
185
|
+
return result;
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=analyzerUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzerUtils.js","sourceRoot":"","sources":["../../src/analyzers/analyzerUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAGjC,yCAAyC;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,eAAe,EAChB,MAAM,sBAAsB,CAAC;AAgB9B;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAAe,EACf,WAAiC,EACjC,YAAoB,EACpB,SAAc,EAAE,EAChB,gBAAmC;IAEnC,MAAM,UAAU,GAAgB,EAAE,CAAC;IACnC,MAAM,MAAM,GAA2C,EAAE,CAAC;IAC1D,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,kBAAkB;YAClB,IAAI,gBAAgB,EAAE,CAAC;gBACrB,gBAAgB,CAAC,cAAc,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACvD,CAAC;YAED,sBAAsB;YACtB,MAAM,EAAE,mBAAmB,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;YAC7E,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;YAE9D,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC3B,MAAM,IAAI,KAAK,CAAC,iBAAiB,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACrF,CAAC;YAED,8BAA8B;YAC9B,MAAM,cAAc,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;YACnE,UAAU,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,CAAC;YAEnC,cAAc,EAAE,CAAC;QACnB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5E,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;YAC3C,OAAO,CAAC,KAAK,CAAC,mBAAmB,IAAI,GAAG,EAAE,KAAK,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAmB;QAC7B,UAAU;QACV,cAAc,EAAE,cAAc;QAC9B,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;QACrC,YAAY;KACb,CAAC;IAEF,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;IACzB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAe;IAEf,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,UAAyB,EACzB,IAAa;IAEb,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,UAAU,CAAC,6BAA6B,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IACtF,OAAO,EAAE,IAAI,EAAE,IAAI,GAAG,CAAC,EAAE,MAAM,EAAE,SAAS,GAAG,CAAC,EAAE,CAAC;AACnD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,IAAa;IAC1C,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,MAAM,SAAS,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACxC,OAAO,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,IAAa;IACvC,IAAI,MAAM,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAuB,CAAC;QAC1C,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1B,OAAO,IAAI,CAAC,IAAI,CAAC;QACnB,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAa,EACb,SAAuC;IAEvC,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,MAAM,KAAK,GAAG,CAAC,IAAa,EAAE,EAAE;QAC9B,IAAI,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YACpB,KAAK,EAAE,CAAC;QACV,CAAC;QACD,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC/B,CAAC,CAAC;IAEF,KAAK,CAAC,IAAI,CAAC,CAAC;IACZ,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAa,EACb,SAAuC;IAEvC,MAAM,KAAK,GAAQ,EAAE,CAAC;IAEtB,MAAM,KAAK,GAAG,CAAC,IAAa,EAAE,EAAE;QAC9B,IAAI,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YACpB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;QACD,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC/B,CAAC,CAAC;IAEF,KAAK,CAAC,IAAI,CAAC,CAAC;IACZ,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CACzB,IAAa,EACb,OAAgC;IAEhC,OAAO,CAAC,IAAI,CAAC,CAAC;IACd,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,0BAA0B,CACxC,UAAuB,EACvB,WAA2B;IAE3B,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,MAAM,aAAa,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;IACjE,MAAM,QAAQ,GAAG,aAAa,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAEjD,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAC3B,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,QAAQ,CACtC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,UAAuB;IACpD,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC9B,yBAAyB;QACzB,MAAM,aAAa,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;QACjE,MAAM,YAAY,GAAG,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC3E,IAAI,YAAY,KAAK,CAAC;YAAE,OAAO,YAAY,CAAC;QAE5C,eAAe;QACf,MAAM,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,QAAQ,KAAK,CAAC;YAAE,OAAO,QAAQ,CAAC;QAEpC,eAAe;QACf,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAgC;IAClE,IAAI,UAAU,GAAG,CAAC,CAAC;IAEnB,WAAW,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE;QAC1B,IACE,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC;YACvB,EAAE,CAAC,uBAAuB,CAAC,KAAK,CAAC;YACjC,EAAE,CAAC,iBAAiB,CAAC,KAAK,CAAC;YAC3B,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC;YACxB,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC;YAC1B,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC;YACvB,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,EACtB,CAAC;YACD,UAAU,EAAE,CAAC;QACf,CAAC;QAED,IAAI,EAAE,CAAC,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC;YACjC,MAAM,QAAQ,GAAG,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC;YAC1C,IACE,QAAQ,KAAK,EAAE,CAAC,UAAU,CAAC,uBAAuB;gBAClD,QAAQ,KAAK,EAAE,CAAC,UAAU,CAAC,WAAW,EACtC,CAAC;gBACD,UAAU,EAAE,CAAC;YACf,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAY,EACZ,YAAkC,EAClC,gBAAqB,EAAE;IAEvB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE;QACtC,MAAM,YAAY,GAAG,EAAE,GAAG,aAAa,EAAE,GAAG,MAAM,EAAE,CAAC;QACrD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC;QAE3E,8BAA8B;QAC9B,MAAM,CAAC,UAAU,GAAG,cAAc,CAChC,0BAA0B,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,WAAW,CAAC,CACpE,CAAC;QAEF,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Access Analyzer (Functional)
|
|
3
|
+
* Analyzes database access patterns and data layer interactions
|
|
4
|
+
*
|
|
5
|
+
* Detects database usage, query patterns, performance risks,
|
|
6
|
+
* and security concerns in data access code
|
|
7
|
+
*/
|
|
8
|
+
import { AnalyzerDefinition } from '../types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Configuration for Data Access analyzer
|
|
11
|
+
*/
|
|
12
|
+
export interface DataAccessAnalyzerConfig {
|
|
13
|
+
databases?: {
|
|
14
|
+
[key: string]: {
|
|
15
|
+
name: string;
|
|
16
|
+
importPatterns: string[];
|
|
17
|
+
queryPatterns: string[];
|
|
18
|
+
ormPatterns?: string[];
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
organizationPatterns?: string[];
|
|
22
|
+
tablePatterns?: {
|
|
23
|
+
orm?: RegExp[];
|
|
24
|
+
sql?: RegExp[];
|
|
25
|
+
queryBuilder?: RegExp[];
|
|
26
|
+
};
|
|
27
|
+
performanceThresholds?: {
|
|
28
|
+
complexQueryCount?: number;
|
|
29
|
+
unfilteredQueryCount?: number;
|
|
30
|
+
joinedTableCount?: number;
|
|
31
|
+
};
|
|
32
|
+
securityPatterns?: {
|
|
33
|
+
sqlInjectionRisks?: string[];
|
|
34
|
+
parameterizedQueries?: string[];
|
|
35
|
+
};
|
|
36
|
+
sourcePatterns?: {
|
|
37
|
+
api?: string[];
|
|
38
|
+
page?: string[];
|
|
39
|
+
service?: string[];
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Data Access Analyzer definition
|
|
44
|
+
*/
|
|
45
|
+
export declare const dataAccessAnalyzer: AnalyzerDefinition;
|
|
46
|
+
//# sourceMappingURL=dataAccessAnalyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataAccessAnalyzer.d.ts","sourceRoot":"","sources":["../../src/analyzers/dataAccessAnalyzer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAIL,kBAAkB,EAEnB,MAAM,aAAa,CAAC;AAYrB;;GAEG;AACH,MAAM,WAAW,wBAAwB;IAEvC,SAAS,CAAC,EAAE;QACV,CAAC,GAAG,EAAE,MAAM,GAAG;YACb,IAAI,EAAE,MAAM,CAAC;YACb,cAAc,EAAE,MAAM,EAAE,CAAC;YACzB,aAAa,EAAE,MAAM,EAAE,CAAC;YACxB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;SACxB,CAAC;KACH,CAAC;IAGF,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAGhC,aAAa,CAAC,EAAE;QACd,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;QACf,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;QACf,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;KACzB,CAAC;IAGF,qBAAqB,CAAC,EAAE;QACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;QAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;IAGF,gBAAgB,CAAC,EAAE;QACjB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;QAC7B,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;KACjC,CAAC;IAGF,cAAc,CAAC,EAAE;QACf,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;CACH;AAygBD;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,kBA8BhC,CAAC"}
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Access Analyzer (Functional)
|
|
3
|
+
* Analyzes database access patterns and data layer interactions
|
|
4
|
+
*
|
|
5
|
+
* Detects database usage, query patterns, performance risks,
|
|
6
|
+
* and security concerns in data access code
|
|
7
|
+
*/
|
|
8
|
+
import { processFiles, createViolation, parseTypeScriptFile, getImports, findNodesByKind, getLineAndColumn, getNodeText } from './analyzerUtils.js';
|
|
9
|
+
import * as ts from 'typescript';
|
|
10
|
+
/**
|
|
11
|
+
* Default configuration
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_CONFIG = {
|
|
14
|
+
databases: {
|
|
15
|
+
'primary': {
|
|
16
|
+
name: 'Primary Database',
|
|
17
|
+
importPatterns: ['/database/', '/db/', 'drizzle', 'prisma', 'typeorm'],
|
|
18
|
+
queryPatterns: ['select', 'insert', 'update', 'delete', 'query'],
|
|
19
|
+
ormPatterns: ['from', 'where', 'join', 'orderBy', 'groupBy']
|
|
20
|
+
},
|
|
21
|
+
'secondary': {
|
|
22
|
+
name: 'Secondary Database',
|
|
23
|
+
importPatterns: ['/analytics/', '/reporting/'],
|
|
24
|
+
queryPatterns: ['query', 'execute', 'run'],
|
|
25
|
+
ormPatterns: []
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
organizationPatterns: [
|
|
29
|
+
'organizationId',
|
|
30
|
+
'organization_id',
|
|
31
|
+
'orgId',
|
|
32
|
+
'org_id',
|
|
33
|
+
'tenantId',
|
|
34
|
+
'tenant_id',
|
|
35
|
+
'companyId',
|
|
36
|
+
'company_id',
|
|
37
|
+
'filterByOrganization',
|
|
38
|
+
'whereOrganization',
|
|
39
|
+
'scopeToOrg'
|
|
40
|
+
],
|
|
41
|
+
tablePatterns: {
|
|
42
|
+
orm: [
|
|
43
|
+
/\.from\(['"`]?(\w+)['"`]?\)/g,
|
|
44
|
+
/\.table\(['"`]?(\w+)['"`]?\)/g,
|
|
45
|
+
/\.into\(['"`]?(\w+)['"`]?\)/g,
|
|
46
|
+
/\.update\(['"`]?(\w+)['"`]?\)/g
|
|
47
|
+
],
|
|
48
|
+
sql: [
|
|
49
|
+
/(?:FROM|JOIN|INTO|UPDATE)\s+['"`]?(\w+)['"`]?/gi,
|
|
50
|
+
/(?:INSERT\s+INTO)\s+['"`]?(\w+)['"`]?/gi,
|
|
51
|
+
/(?:DELETE\s+FROM)\s+['"`]?(\w+)['"`]?/gi
|
|
52
|
+
],
|
|
53
|
+
queryBuilder: [
|
|
54
|
+
/table:\s*['"`](\w+)['"`]/g,
|
|
55
|
+
/from:\s*['"`](\w+)['"`]/g
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
performanceThresholds: {
|
|
59
|
+
complexQueryCount: 3,
|
|
60
|
+
unfilteredQueryCount: 5,
|
|
61
|
+
joinedTableCount: 2
|
|
62
|
+
},
|
|
63
|
+
securityPatterns: {
|
|
64
|
+
sqlInjectionRisks: [
|
|
65
|
+
'concatenation',
|
|
66
|
+
'${',
|
|
67
|
+
'string interpolation',
|
|
68
|
+
'+ variable',
|
|
69
|
+
'raw(',
|
|
70
|
+
'unsafeRaw'
|
|
71
|
+
],
|
|
72
|
+
parameterizedQueries: [
|
|
73
|
+
'prepared',
|
|
74
|
+
'parameterized',
|
|
75
|
+
'bind',
|
|
76
|
+
'?',
|
|
77
|
+
'$1',
|
|
78
|
+
':param'
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
sourcePatterns: {
|
|
82
|
+
api: ['/api/', '/routes/', '/endpoints/'],
|
|
83
|
+
page: ['/pages/', '/app/', '/views/'],
|
|
84
|
+
service: ['/services/', '/lib/', '/utils/']
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Analyze a single file for data access patterns
|
|
89
|
+
*/
|
|
90
|
+
async function analyzeFile(filePath, config) {
|
|
91
|
+
const { sourceFile, errors } = await parseTypeScriptFile(filePath);
|
|
92
|
+
if (errors.length > 0) {
|
|
93
|
+
throw new Error(`Parse errors: ${errors.map(e => e.messageText).join(', ')}`);
|
|
94
|
+
}
|
|
95
|
+
const imports = getImports(sourceFile);
|
|
96
|
+
const patterns = [];
|
|
97
|
+
const violations = [];
|
|
98
|
+
// Extract database calls
|
|
99
|
+
const dbCalls = extractDatabaseCalls(sourceFile, imports, filePath, config);
|
|
100
|
+
// Group calls by database type
|
|
101
|
+
const callsByDb = new Map();
|
|
102
|
+
dbCalls.forEach(call => {
|
|
103
|
+
if (!callsByDb.has(call.type)) {
|
|
104
|
+
callsByDb.set(call.type, []);
|
|
105
|
+
}
|
|
106
|
+
callsByDb.get(call.type).push(call);
|
|
107
|
+
});
|
|
108
|
+
// Create patterns for each database type used
|
|
109
|
+
callsByDb.forEach((calls, dbType) => {
|
|
110
|
+
const allTables = new Set();
|
|
111
|
+
const queries = [];
|
|
112
|
+
let hasOrgFilter = false;
|
|
113
|
+
let hasSqlInjectionRisk = false;
|
|
114
|
+
calls.forEach(call => {
|
|
115
|
+
call.tables.forEach(table => allTables.add(table));
|
|
116
|
+
hasOrgFilter = hasOrgFilter || call.hasOrganizationFilter;
|
|
117
|
+
hasSqlInjectionRisk = hasSqlInjectionRisk || call.hasSqlInjectionRisk;
|
|
118
|
+
// Create query info with enhanced analysis
|
|
119
|
+
const queryInfo = extractQueryInfo(call, config);
|
|
120
|
+
queries.push(queryInfo);
|
|
121
|
+
});
|
|
122
|
+
const pattern = {
|
|
123
|
+
source: detectSourceType(filePath, config),
|
|
124
|
+
filePath,
|
|
125
|
+
database: dbType,
|
|
126
|
+
tables: Array.from(allTables),
|
|
127
|
+
queries,
|
|
128
|
+
hasOrganizationFilter: hasOrgFilter,
|
|
129
|
+
performanceRisk: assessPerformanceRisk(queries, config),
|
|
130
|
+
hasSqlInjectionRisk
|
|
131
|
+
};
|
|
132
|
+
patterns.push(pattern);
|
|
133
|
+
// Check for violations
|
|
134
|
+
violations.push(...checkDataAccessViolations(pattern));
|
|
135
|
+
});
|
|
136
|
+
return { patterns, violations };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Detect source type based on file path
|
|
140
|
+
*/
|
|
141
|
+
function detectSourceType(filePath, config) {
|
|
142
|
+
if (config.sourcePatterns.api.some(pattern => filePath.includes(pattern))) {
|
|
143
|
+
return 'api';
|
|
144
|
+
}
|
|
145
|
+
else if (config.sourcePatterns.page.some(pattern => filePath.includes(pattern))) {
|
|
146
|
+
return 'component';
|
|
147
|
+
}
|
|
148
|
+
else if (config.sourcePatterns.service.some(pattern => filePath.includes(pattern))) {
|
|
149
|
+
return 'service';
|
|
150
|
+
}
|
|
151
|
+
return 'service';
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Extract database calls from the AST
|
|
155
|
+
*/
|
|
156
|
+
function extractDatabaseCalls(sourceFile, imports, filePath, config) {
|
|
157
|
+
const calls = [];
|
|
158
|
+
// Map imports to database types
|
|
159
|
+
const dbImports = mapDatabaseImports(imports, config);
|
|
160
|
+
// Find all call expressions
|
|
161
|
+
const callExpressions = findNodesByKind(sourceFile, ts.SyntaxKind.CallExpression);
|
|
162
|
+
callExpressions.forEach(callExpr => {
|
|
163
|
+
const callText = getNodeText(callExpr, sourceFile);
|
|
164
|
+
const { line, column } = getLineAndColumn(sourceFile, callExpr.getStart());
|
|
165
|
+
// Check each configured database
|
|
166
|
+
Object.entries(dbImports).forEach(([dbType, importInfo]) => {
|
|
167
|
+
if (importInfo.hasImports && isDatabaseCall(callText, importInfo)) {
|
|
168
|
+
const tables = extractTablesFromCall(callText, config);
|
|
169
|
+
const hasOrgFilter = checkOrganizationFilter(callText, config);
|
|
170
|
+
const securityCheck = checkQuerySecurity(callText, config);
|
|
171
|
+
calls.push({
|
|
172
|
+
type: dbType,
|
|
173
|
+
method: extractMethodName(callExpr),
|
|
174
|
+
file: filePath,
|
|
175
|
+
line,
|
|
176
|
+
column,
|
|
177
|
+
tables,
|
|
178
|
+
hasOrganizationFilter: hasOrgFilter,
|
|
179
|
+
hasParameterizedQuery: securityCheck.parameterized,
|
|
180
|
+
hasSqlInjectionRisk: securityCheck.injectionRisk
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
return calls;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Map imports to database types
|
|
189
|
+
*/
|
|
190
|
+
function mapDatabaseImports(imports, config) {
|
|
191
|
+
const result = {};
|
|
192
|
+
Object.entries(config.databases).forEach(([key, dbConfig]) => {
|
|
193
|
+
const importNames = [];
|
|
194
|
+
let hasImports = false;
|
|
195
|
+
imports.forEach(imp => {
|
|
196
|
+
if (dbConfig.importPatterns.some((pattern) => imp.moduleSpecifier.includes(pattern))) {
|
|
197
|
+
hasImports = true;
|
|
198
|
+
importNames.push(...imp.importedNames);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
result[key] = {
|
|
202
|
+
hasImports,
|
|
203
|
+
importNames,
|
|
204
|
+
patterns: [...dbConfig.queryPatterns, ...importNames]
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Check if a call is a database call
|
|
211
|
+
*/
|
|
212
|
+
function isDatabaseCall(callText, importInfo) {
|
|
213
|
+
if (!importInfo.hasImports)
|
|
214
|
+
return false;
|
|
215
|
+
return importInfo.patterns.some(pattern => callText.includes(pattern));
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Extract method name from call expression
|
|
219
|
+
*/
|
|
220
|
+
function extractMethodName(callExpr) {
|
|
221
|
+
const expression = callExpr.expression;
|
|
222
|
+
if (ts.isPropertyAccessExpression(expression)) {
|
|
223
|
+
return expression.name.text;
|
|
224
|
+
}
|
|
225
|
+
else if (ts.isIdentifier(expression)) {
|
|
226
|
+
return expression.text;
|
|
227
|
+
}
|
|
228
|
+
return 'unknown';
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Extract table names from call
|
|
232
|
+
*/
|
|
233
|
+
function extractTablesFromCall(callText, config) {
|
|
234
|
+
const tables = new Set();
|
|
235
|
+
// Try all table extraction patterns
|
|
236
|
+
Object.values(config.tablePatterns).forEach(patterns => {
|
|
237
|
+
patterns.forEach(pattern => {
|
|
238
|
+
const matches = Array.from(callText.matchAll(pattern));
|
|
239
|
+
matches.forEach(match => {
|
|
240
|
+
if (match[1]) {
|
|
241
|
+
tables.add(match[1].toLowerCase());
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
return Array.from(tables);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Check for organization filtering
|
|
250
|
+
*/
|
|
251
|
+
function checkOrganizationFilter(callText, config) {
|
|
252
|
+
return config.organizationPatterns.some(pattern => callText.toLowerCase().includes(pattern.toLowerCase()));
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Check query security
|
|
256
|
+
*/
|
|
257
|
+
function checkQuerySecurity(callText, config) {
|
|
258
|
+
const hasParameterized = config.securityPatterns.parameterizedQueries.some(pattern => callText.includes(pattern));
|
|
259
|
+
const hasInjectionRisk = config.securityPatterns.sqlInjectionRisks.some(pattern => callText.includes(pattern));
|
|
260
|
+
return {
|
|
261
|
+
parameterized: hasParameterized,
|
|
262
|
+
injectionRisk: hasInjectionRisk && !hasParameterized
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Extract query info from database call
|
|
267
|
+
*/
|
|
268
|
+
function extractQueryInfo(call, config) {
|
|
269
|
+
return {
|
|
270
|
+
type: inferQueryType(call.method),
|
|
271
|
+
tables: call.tables,
|
|
272
|
+
line: call.line,
|
|
273
|
+
hasJoins: detectJoins(call.method),
|
|
274
|
+
hasOrganizationFilter: call.hasOrganizationFilter,
|
|
275
|
+
complexity: analyzeQueryComplexity(call, config)
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Infer query type from method name
|
|
280
|
+
*/
|
|
281
|
+
function inferQueryType(method) {
|
|
282
|
+
const methodLower = method.toLowerCase();
|
|
283
|
+
if (methodLower.includes('select') || methodLower.includes('find') || methodLower.includes('get')) {
|
|
284
|
+
return 'select';
|
|
285
|
+
}
|
|
286
|
+
else if (methodLower.includes('insert') || methodLower.includes('create')) {
|
|
287
|
+
return 'insert';
|
|
288
|
+
}
|
|
289
|
+
else if (methodLower.includes('update')) {
|
|
290
|
+
return 'update';
|
|
291
|
+
}
|
|
292
|
+
else if (methodLower.includes('delete') || methodLower.includes('remove')) {
|
|
293
|
+
return 'delete';
|
|
294
|
+
}
|
|
295
|
+
return 'other';
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Detect if a query has joins
|
|
299
|
+
*/
|
|
300
|
+
function detectJoins(method) {
|
|
301
|
+
const joinPatterns = [
|
|
302
|
+
'join',
|
|
303
|
+
'leftJoin',
|
|
304
|
+
'rightJoin',
|
|
305
|
+
'innerJoin',
|
|
306
|
+
'outerJoin',
|
|
307
|
+
'fullJoin'
|
|
308
|
+
];
|
|
309
|
+
return joinPatterns.some(pattern => method.toLowerCase().includes(pattern.toLowerCase()));
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Analyze query complexity
|
|
313
|
+
*/
|
|
314
|
+
function analyzeQueryComplexity(call, config) {
|
|
315
|
+
let complexityScore = 0;
|
|
316
|
+
// Table count factor
|
|
317
|
+
if (call.tables.length > config.performanceThresholds.joinedTableCount) {
|
|
318
|
+
complexityScore += 2;
|
|
319
|
+
}
|
|
320
|
+
else if (call.tables.length > 1) {
|
|
321
|
+
complexityScore += 1;
|
|
322
|
+
}
|
|
323
|
+
// Method complexity
|
|
324
|
+
if (detectJoins(call.method)) {
|
|
325
|
+
complexityScore += 2;
|
|
326
|
+
}
|
|
327
|
+
// Security risk adds complexity
|
|
328
|
+
if (call.hasSqlInjectionRisk) {
|
|
329
|
+
complexityScore += 3;
|
|
330
|
+
}
|
|
331
|
+
// Missing org filter in multi-tenant context
|
|
332
|
+
if (!call.hasOrganizationFilter && call.tables.length > 0) {
|
|
333
|
+
complexityScore += 1;
|
|
334
|
+
}
|
|
335
|
+
// Determine complexity level
|
|
336
|
+
if (complexityScore >= 5)
|
|
337
|
+
return 'complex';
|
|
338
|
+
if (complexityScore >= 2)
|
|
339
|
+
return 'moderate';
|
|
340
|
+
return 'simple';
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Assess performance risk based on queries
|
|
344
|
+
*/
|
|
345
|
+
function assessPerformanceRisk(queries, config) {
|
|
346
|
+
// Count complex queries
|
|
347
|
+
const complexQueries = queries.filter(q => q.hasJoins ||
|
|
348
|
+
q.tables.length > config.performanceThresholds.joinedTableCount ||
|
|
349
|
+
q.complexity === 'complex').length;
|
|
350
|
+
// Count queries without org filter
|
|
351
|
+
const unfiltered = queries.filter(q => !q.hasOrganizationFilter).length;
|
|
352
|
+
if (complexQueries > config.performanceThresholds.complexQueryCount ||
|
|
353
|
+
unfiltered > config.performanceThresholds.unfilteredQueryCount) {
|
|
354
|
+
return 'high';
|
|
355
|
+
}
|
|
356
|
+
else if (complexQueries > 1 || unfiltered > 2) {
|
|
357
|
+
return 'medium';
|
|
358
|
+
}
|
|
359
|
+
return 'low';
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Check for data access violations
|
|
363
|
+
*/
|
|
364
|
+
function checkDataAccessViolations(pattern) {
|
|
365
|
+
const violations = [];
|
|
366
|
+
// Check for missing organization filter in multi-tenant scenarios
|
|
367
|
+
if (!pattern.hasOrganizationFilter && pattern.tables.length > 0) {
|
|
368
|
+
violations.push(createViolation({
|
|
369
|
+
analyzer: 'data-access',
|
|
370
|
+
severity: 'warning',
|
|
371
|
+
type: 'data-access',
|
|
372
|
+
file: pattern.filePath,
|
|
373
|
+
line: 1,
|
|
374
|
+
column: 1,
|
|
375
|
+
message: 'Database queries may be missing tenant/organization filtering',
|
|
376
|
+
recommendation: 'Add appropriate filtering to ensure data isolation in multi-tenant environments',
|
|
377
|
+
estimatedEffort: 'small'
|
|
378
|
+
}));
|
|
379
|
+
}
|
|
380
|
+
// Check for SQL injection risks
|
|
381
|
+
if (pattern.hasSqlInjectionRisk) {
|
|
382
|
+
violations.push(createViolation({
|
|
383
|
+
analyzer: 'data-access',
|
|
384
|
+
severity: 'critical',
|
|
385
|
+
type: 'data-access',
|
|
386
|
+
file: pattern.filePath,
|
|
387
|
+
line: 1,
|
|
388
|
+
column: 1,
|
|
389
|
+
message: 'Potential SQL injection vulnerability detected',
|
|
390
|
+
recommendation: 'Use parameterized queries or prepared statements instead of string concatenation',
|
|
391
|
+
estimatedEffort: 'medium'
|
|
392
|
+
}));
|
|
393
|
+
}
|
|
394
|
+
// Check for performance risks
|
|
395
|
+
if (pattern.performanceRisk === 'high') {
|
|
396
|
+
violations.push(createViolation({
|
|
397
|
+
analyzer: 'data-access',
|
|
398
|
+
severity: 'warning',
|
|
399
|
+
type: 'data-access',
|
|
400
|
+
file: pattern.filePath,
|
|
401
|
+
line: 1,
|
|
402
|
+
column: 1,
|
|
403
|
+
message: 'High performance risk detected in data access patterns',
|
|
404
|
+
recommendation: 'Optimize queries, add indexes, implement caching, or paginate results',
|
|
405
|
+
estimatedEffort: 'medium'
|
|
406
|
+
}));
|
|
407
|
+
}
|
|
408
|
+
// Check for direct database access in presentation layer
|
|
409
|
+
if (pattern.source === 'component') {
|
|
410
|
+
violations.push(createViolation({
|
|
411
|
+
analyzer: 'data-access',
|
|
412
|
+
severity: 'suggestion',
|
|
413
|
+
type: 'data-access',
|
|
414
|
+
file: pattern.filePath,
|
|
415
|
+
line: 1,
|
|
416
|
+
column: 1,
|
|
417
|
+
message: 'Direct database access detected in presentation layer',
|
|
418
|
+
recommendation: 'Consider moving data access to service layer or API endpoints for better separation of concerns',
|
|
419
|
+
estimatedEffort: 'medium'
|
|
420
|
+
}));
|
|
421
|
+
}
|
|
422
|
+
return violations;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Data Access Analyzer definition
|
|
426
|
+
*/
|
|
427
|
+
export const dataAccessAnalyzer = {
|
|
428
|
+
name: 'data-access',
|
|
429
|
+
defaultConfig: DEFAULT_CONFIG,
|
|
430
|
+
analyze: async (files, config, options, progressCallback) => {
|
|
431
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
432
|
+
// Custom processor to handle patterns collection
|
|
433
|
+
const patterns = [];
|
|
434
|
+
const allViolations = [];
|
|
435
|
+
const result = await processFiles(files, async (filePath, sourceFile) => {
|
|
436
|
+
const fileResult = await analyzeFile(filePath, mergedConfig);
|
|
437
|
+
patterns.push(...fileResult.patterns);
|
|
438
|
+
allViolations.push(...fileResult.violations);
|
|
439
|
+
return fileResult.violations;
|
|
440
|
+
}, 'data-access', mergedConfig, progressCallback ?
|
|
441
|
+
(current, total, file) => progressCallback({ current, total, analyzer: 'data-access', file }) :
|
|
442
|
+
undefined);
|
|
443
|
+
// Override violations with our collected ones
|
|
444
|
+
result.violations = allViolations;
|
|
445
|
+
return result;
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
//# sourceMappingURL=dataAccessAnalyzer.js.map
|