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.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +362 -0
  3. package/configs/hhra-compat.json +168 -0
  4. package/dist/analyzers/analyzerUtils.d.ts +69 -0
  5. package/dist/analyzers/analyzerUtils.d.ts.map +1 -0
  6. package/dist/analyzers/analyzerUtils.js +188 -0
  7. package/dist/analyzers/analyzerUtils.js.map +1 -0
  8. package/dist/analyzers/dataAccessAnalyzer.d.ts +46 -0
  9. package/dist/analyzers/dataAccessAnalyzer.d.ts.map +1 -0
  10. package/dist/analyzers/dataAccessAnalyzer.js +448 -0
  11. package/dist/analyzers/dataAccessAnalyzer.js.map +1 -0
  12. package/dist/analyzers/dryAnalyzer.d.ts +30 -0
  13. package/dist/analyzers/dryAnalyzer.d.ts.map +1 -0
  14. package/dist/analyzers/dryAnalyzer.js +537 -0
  15. package/dist/analyzers/dryAnalyzer.js.map +1 -0
  16. package/dist/analyzers/solidAnalyzer.d.ts +23 -0
  17. package/dist/analyzers/solidAnalyzer.d.ts.map +1 -0
  18. package/dist/analyzers/solidAnalyzer.js +355 -0
  19. package/dist/analyzers/solidAnalyzer.js.map +1 -0
  20. package/dist/auditRunner.d.ts +20 -0
  21. package/dist/auditRunner.d.ts.map +1 -0
  22. package/dist/auditRunner.js +214 -0
  23. package/dist/auditRunner.js.map +1 -0
  24. package/dist/cli.d.ts +6 -0
  25. package/dist/cli.d.ts.map +1 -0
  26. package/dist/cli.js +38 -0
  27. package/dist/cli.js.map +1 -0
  28. package/dist/config/configLoader.d.ts +19 -0
  29. package/dist/config/configLoader.d.ts.map +1 -0
  30. package/dist/config/configLoader.js +124 -0
  31. package/dist/config/configLoader.js.map +1 -0
  32. package/dist/config/defaults.d.ts +69 -0
  33. package/dist/config/defaults.d.ts.map +1 -0
  34. package/dist/config/defaults.js +142 -0
  35. package/dist/config/defaults.js.map +1 -0
  36. package/dist/index.d.ts +44 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +66 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/mcp-standalone.d.ts +3 -0
  41. package/dist/mcp-standalone.d.ts.map +1 -0
  42. package/dist/mcp-standalone.js +171 -0
  43. package/dist/mcp-standalone.js.map +1 -0
  44. package/dist/mcp.d.ts +3 -0
  45. package/dist/mcp.d.ts.map +1 -0
  46. package/dist/mcp.js +340 -0
  47. package/dist/mcp.js.map +1 -0
  48. package/dist/reporting/csvReportGenerator.d.ts +29 -0
  49. package/dist/reporting/csvReportGenerator.d.ts.map +1 -0
  50. package/dist/reporting/csvReportGenerator.js +182 -0
  51. package/dist/reporting/csvReportGenerator.js.map +1 -0
  52. package/dist/reporting/htmlReportGenerator.d.ts +18 -0
  53. package/dist/reporting/htmlReportGenerator.d.ts.map +1 -0
  54. package/dist/reporting/htmlReportGenerator.js +352 -0
  55. package/dist/reporting/htmlReportGenerator.js.map +1 -0
  56. package/dist/reporting/jsonReportGenerator.d.ts +23 -0
  57. package/dist/reporting/jsonReportGenerator.d.ts.map +1 -0
  58. package/dist/reporting/jsonReportGenerator.js +103 -0
  59. package/dist/reporting/jsonReportGenerator.js.map +1 -0
  60. package/dist/reporting/reportGenerator.d.ts +21 -0
  61. package/dist/reporting/reportGenerator.d.ts.map +1 -0
  62. package/dist/reporting/reportGenerator.js +53 -0
  63. package/dist/reporting/reportGenerator.js.map +1 -0
  64. package/dist/types.d.ts +246 -0
  65. package/dist/types.d.ts.map +1 -0
  66. package/dist/types.js +6 -0
  67. package/dist/types.js.map +1 -0
  68. package/dist/utils/astParser.d.ts +71 -0
  69. package/dist/utils/astParser.d.ts.map +1 -0
  70. package/dist/utils/astParser.js +244 -0
  71. package/dist/utils/astParser.js.map +1 -0
  72. package/dist/utils/astUtils.d.ts +89 -0
  73. package/dist/utils/astUtils.d.ts.map +1 -0
  74. package/dist/utils/astUtils.js +289 -0
  75. package/dist/utils/astUtils.js.map +1 -0
  76. package/dist/utils/fileDiscovery.d.ts +58 -0
  77. package/dist/utils/fileDiscovery.d.ts.map +1 -0
  78. package/dist/utils/fileDiscovery.js +203 -0
  79. package/dist/utils/fileDiscovery.js.map +1 -0
  80. package/examples/.auditrc.example.json +61 -0
  81. package/examples/.auditrc.json +201 -0
  82. package/examples/nextjs.auditrc.json +65 -0
  83. package/examples/node-api.auditrc.json +82 -0
  84. package/examples/react.auditrc.json +59 -0
  85. 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":"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"}
@@ -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