codeslick-cli 1.0.3 → 1.1.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 (55) hide show
  1. package/README.md +73 -21
  2. package/bin/codeslick.cjs +21 -2
  3. package/dist/packages/cli/src/commands/scan.d.ts +3 -0
  4. package/dist/packages/cli/src/commands/scan.d.ts.map +1 -1
  5. package/dist/packages/cli/src/commands/scan.js +103 -24
  6. package/dist/packages/cli/src/commands/scan.js.map +1 -1
  7. package/dist/packages/cli/src/reporters/cli-reporter.d.ts +28 -2
  8. package/dist/packages/cli/src/reporters/cli-reporter.d.ts.map +1 -1
  9. package/dist/packages/cli/src/reporters/cli-reporter.js +393 -4
  10. package/dist/packages/cli/src/reporters/cli-reporter.js.map +1 -1
  11. package/dist/packages/cli/src/scanner/local-scanner.d.ts +5 -1
  12. package/dist/packages/cli/src/scanner/local-scanner.d.ts.map +1 -1
  13. package/dist/packages/cli/src/scanner/local-scanner.js +110 -16
  14. package/dist/packages/cli/src/scanner/local-scanner.js.map +1 -1
  15. package/dist/src/lib/analyzers/java/security-checks/hardcoded-credentials.d.ts.map +1 -1
  16. package/dist/src/lib/analyzers/java/security-checks/hardcoded-credentials.js +24 -16
  17. package/dist/src/lib/analyzers/java/security-checks/hardcoded-credentials.js.map +1 -1
  18. package/dist/src/lib/analyzers/javascript/security-checks/ai-generated-code.d.ts.map +1 -1
  19. package/dist/src/lib/analyzers/javascript/security-checks/ai-generated-code.js +4 -12
  20. package/dist/src/lib/analyzers/javascript/security-checks/ai-generated-code.js.map +1 -1
  21. package/dist/src/lib/analyzers/javascript/security-checks/credential-crypto.d.ts.map +1 -1
  22. package/dist/src/lib/analyzers/javascript/security-checks/credential-crypto.js +22 -9
  23. package/dist/src/lib/analyzers/javascript/security-checks/credential-crypto.js.map +1 -1
  24. package/dist/src/lib/analyzers/javascript-analyzer.d.ts.map +1 -1
  25. package/dist/src/lib/analyzers/javascript-analyzer.js +28 -13
  26. package/dist/src/lib/analyzers/javascript-analyzer.js.map +1 -1
  27. package/dist/src/lib/analyzers/python/security-checks/credentials-crypto.d.ts.map +1 -1
  28. package/dist/src/lib/analyzers/python/security-checks/credentials-crypto.js +44 -18
  29. package/dist/src/lib/analyzers/python/security-checks/credentials-crypto.js.map +1 -1
  30. package/dist/src/lib/analyzers/python-analyzer.d.ts.map +1 -1
  31. package/dist/src/lib/analyzers/python-analyzer.js +21 -13
  32. package/dist/src/lib/analyzers/python-analyzer.js.map +1 -1
  33. package/dist/src/lib/analyzers/secrets/validators/context-checker.d.ts.map +1 -1
  34. package/dist/src/lib/analyzers/secrets/validators/context-checker.js +21 -0
  35. package/dist/src/lib/analyzers/secrets/validators/context-checker.js.map +1 -1
  36. package/dist/src/lib/analyzers/typescript/security-checks/ai-generated-code.d.ts.map +1 -1
  37. package/dist/src/lib/analyzers/typescript/security-checks/ai-generated-code.js +4 -12
  38. package/dist/src/lib/analyzers/typescript/security-checks/ai-generated-code.js.map +1 -1
  39. package/dist/src/lib/analyzers/typescript/security-checks/credentials-crypto.d.ts.map +1 -1
  40. package/dist/src/lib/analyzers/typescript/security-checks/credentials-crypto.js +25 -9
  41. package/dist/src/lib/analyzers/typescript/security-checks/credentials-crypto.js.map +1 -1
  42. package/dist/src/lib/analyzers/typescript/security-checks/security-misconfiguration.d.ts.map +1 -1
  43. package/dist/src/lib/analyzers/typescript/security-checks/security-misconfiguration.js +14 -4
  44. package/dist/src/lib/analyzers/typescript/security-checks/security-misconfiguration.js.map +1 -1
  45. package/dist/src/lib/analyzers/typescript/type-checker.d.ts +32 -0
  46. package/dist/src/lib/analyzers/typescript/type-checker.d.ts.map +1 -1
  47. package/dist/src/lib/analyzers/typescript/type-checker.js +264 -22
  48. package/dist/src/lib/analyzers/typescript/type-checker.js.map +1 -1
  49. package/dist/src/lib/analyzers/typescript-analyzer.d.ts.map +1 -1
  50. package/dist/src/lib/analyzers/typescript-analyzer.js +27 -23
  51. package/dist/src/lib/analyzers/typescript-analyzer.js.map +1 -1
  52. package/package.json +1 -1
  53. package/src/commands/scan.ts +77 -25
  54. package/src/reporters/cli-reporter.ts +449 -4
  55. package/src/scanner/local-scanner.ts +132 -19
@@ -43,6 +43,7 @@ export interface ScannerConfig {
43
43
  severityThreshold?: 'critical' | 'high' | 'medium' | 'low';
44
44
  exclude?: string[];
45
45
  autofix?: boolean;
46
+ quickMode?: boolean; // Skip deep TypeScript type checking for speed
46
47
  }
47
48
 
48
49
  /**
@@ -72,21 +73,27 @@ export function detectLanguage(filePath: string): SupportedLanguage | null {
72
73
 
73
74
  /**
74
75
  * Check if file should be excluded based on patterns
76
+ * Uses fast regex-based pattern matching (no filesystem scanning)
75
77
  */
76
78
  export function shouldExclude(filePath: string, excludePatterns: string[]): boolean {
77
79
  const relativePath = relative(process.cwd(), filePath);
80
+ // Also check with forward slashes for cross-platform compatibility
81
+ const normalizedPath = relativePath.replace(/\\/g, '/');
78
82
 
79
83
  for (const pattern of excludePatterns) {
80
- // Convert glob pattern to regex
81
- const regex = new RegExp(
82
- pattern
83
- .replace(/\./g, '\\.')
84
- .replace(/\*\*/g, '.*')
85
- .replace(/\*/g, '[^/]*')
86
- .replace(/\?/g, '.')
87
- );
88
-
89
- if (regex.test(relativePath)) {
84
+ // Convert glob pattern to regex for fast matching
85
+ // Order matters: escape dots first, then handle glob patterns
86
+ const regexPattern = pattern
87
+ .replace(/\./g, '\\.') // Escape dots
88
+ .replace(/\*\*/g, '<<<GLOBSTAR>>>') // Temp placeholder for **
89
+ .replace(/\*/g, '[^/]*') // * matches anything except /
90
+ .replace(/<<<GLOBSTAR>>>/g, '.*') // ** matches anything including /
91
+ .replace(/\?/g, '.') // ? matches single char
92
+ .replace(/\{([^}]+)\}/g, (_, p1) => `(${p1.split(',').join('|')})`); // {a,b} -> (a|b)
93
+
94
+ const regex = new RegExp('^' + regexPattern + '$');
95
+
96
+ if (regex.test(normalizedPath) || regex.test(relativePath)) {
90
97
  return true;
91
98
  }
92
99
  }
@@ -141,6 +148,8 @@ export async function scanFile(
141
148
  const code = await readFile(filePath, 'utf-8');
142
149
 
143
150
  // Import analyzer dynamically based on language
151
+ // Pass quickMode option to skip expensive type checking
152
+ const analyzerOptions = { quickMode: config.quickMode || false };
144
153
  let result: AnalyzerResult;
145
154
 
146
155
  switch (language) {
@@ -149,7 +158,7 @@ export async function scanFile(
149
158
  '../../../../src/lib/analyzers/javascript-analyzer'
150
159
  );
151
160
  const analyzer = new JavaScriptAnalyzer();
152
- result = await analyzer.analyze({ code, filename: filePath });
161
+ result = await analyzer.analyze({ code, filename: filePath, options: analyzerOptions });
153
162
  break;
154
163
  }
155
164
 
@@ -158,21 +167,21 @@ export async function scanFile(
158
167
  '../../../../src/lib/analyzers/typescript-analyzer'
159
168
  );
160
169
  const analyzer = new TypeScriptAnalyzer();
161
- result = await analyzer.analyze({ code, filename: filePath });
170
+ result = await analyzer.analyze({ code, filename: filePath, options: analyzerOptions });
162
171
  break;
163
172
  }
164
173
 
165
174
  case 'python': {
166
175
  const { PythonAnalyzer } = await import('../../../../src/lib/analyzers/python-analyzer');
167
176
  const analyzer = new PythonAnalyzer();
168
- result = await analyzer.analyze({ code, filename: filePath });
177
+ result = await analyzer.analyze({ code, filename: filePath, options: analyzerOptions });
169
178
  break;
170
179
  }
171
180
 
172
181
  case 'java': {
173
182
  const { JavaAnalyzer } = await import('../../../../src/lib/analyzers/java-analyzer');
174
183
  const analyzer = new JavaAnalyzer();
175
- result = await analyzer.analyze({ code, filename: filePath });
184
+ result = await analyzer.analyze({ code, filename: filePath, options: analyzerOptions });
176
185
  break;
177
186
  }
178
187
 
@@ -200,7 +209,9 @@ export async function scanFile(
200
209
  /**
201
210
  * Scan multiple files for security vulnerabilities
202
211
  *
203
- * This function scans multiple files in parallel for better performance.
212
+ * OPTIMIZED (Jan 15, 2026): Uses batch TypeScript compilation for 17x speedup
213
+ * - TypeScript files: Batch processed together (single ts.createProgram)
214
+ * - Other files: Processed in parallel as before
204
215
  *
205
216
  * @param filePaths - Array of absolute file paths
206
217
  * @param config - Scanner configuration
@@ -210,11 +221,113 @@ export async function scanFiles(
210
221
  filePaths: string[],
211
222
  config: ScannerConfig = {}
212
223
  ): Promise<FileScanResult[]> {
213
- // Scan all files in parallel
214
- const results = await Promise.all(filePaths.map((path) => scanFile(path, config)));
224
+ // Separate TypeScript files from others for batch processing
225
+ const tsFiles: string[] = [];
226
+ const otherFiles: string[] = [];
227
+
228
+ for (const filePath of filePaths) {
229
+ const language = detectLanguage(filePath);
230
+ if (language === 'typescript') {
231
+ // Check exclusions before adding to batch
232
+ if (!config.exclude || !shouldExclude(filePath, config.exclude)) {
233
+ tsFiles.push(filePath);
234
+ }
235
+ } else if (language) {
236
+ otherFiles.push(filePath);
237
+ }
238
+ }
239
+
240
+ const results: FileScanResult[] = [];
241
+
242
+ // Batch process TypeScript files (17x faster)
243
+ if (tsFiles.length > 0 && !config.quickMode) {
244
+ const batchResults = await scanTypeScriptBatch(tsFiles, config);
245
+ results.push(...batchResults);
246
+ } else if (tsFiles.length > 0 && config.quickMode) {
247
+ // Quick mode: skip type checking, use parallel processing
248
+ const tsResults = await Promise.all(tsFiles.map((path) => scanFile(path, config)));
249
+ results.push(...tsResults.filter((r): r is FileScanResult => r !== null));
250
+ }
251
+
252
+ // Process other files in parallel (JS, Python, Java)
253
+ if (otherFiles.length > 0) {
254
+ const otherResults = await Promise.all(otherFiles.map((path) => scanFile(path, config)));
255
+ results.push(...otherResults.filter((r): r is FileScanResult => r !== null));
256
+ }
257
+
258
+ return results;
259
+ }
260
+
261
+ /**
262
+ * Batch scan TypeScript files using single ts.createProgram
263
+ * This is 17x faster than scanning each file individually
264
+ */
265
+ async function scanTypeScriptBatch(
266
+ filePaths: string[],
267
+ _config: ScannerConfig = {}
268
+ ): Promise<FileScanResult[]> {
269
+ const { readFile } = await import('fs/promises');
270
+ const { relative } = await import('path');
271
+
272
+ // Import batch diagnostics function
273
+ const { getBatchTypeScriptDiagnostics, convertDiagnosticsToIssues } = await import(
274
+ '../../../../src/lib/analyzers/typescript/type-checker'
275
+ );
276
+
277
+ // Get batch diagnostics for all TypeScript files at once
278
+ const batchResult = getBatchTypeScriptDiagnostics(filePaths);
279
+
280
+ // Import TypeScript analyzer for security checks (runs separately)
281
+ const { TypeScriptAnalyzer } = await import(
282
+ '../../../../src/lib/analyzers/typescript-analyzer'
283
+ );
284
+
285
+ const results: FileScanResult[] = [];
286
+
287
+ for (const filePath of filePaths) {
288
+ try {
289
+ const code = await readFile(filePath, 'utf-8');
290
+
291
+ // Run security analysis (regex-based, fast)
292
+ const analyzer = new TypeScriptAnalyzer();
293
+ // Use quickMode to skip the per-file type checking (we already did batch)
294
+ const result = await analyzer.analyze({ code, filename: filePath, options: { quickMode: true } });
295
+
296
+ // Add batch type diagnostics to the result
297
+ const fileDiagnostics = batchResult.diagnostics.get(filePath) || [];
298
+ if (fileDiagnostics.length > 0) {
299
+ const typeIssues = convertDiagnosticsToIssues(fileDiagnostics);
300
+ const typeVulnerabilities = typeIssues.map((issue: any) => ({
301
+ severity: issue.severity,
302
+ message: issue.message,
303
+ line: issue.line,
304
+ suggestion: issue.suggestion,
305
+ category: 'type-checking',
306
+ cvssScore: issue.cvssScore,
307
+ exploitLikelihood: issue.exploitLikelihood,
308
+ impact: issue.impact,
309
+ owasp: issue.owasp,
310
+ cwe: issue.cwe
311
+ }));
312
+ result.security.vulnerabilities.push(...typeVulnerabilities);
313
+ }
314
+
315
+ // Count vulnerabilities
316
+ const counts = countVulnerabilities(result);
317
+
318
+ results.push({
319
+ filePath,
320
+ relativePath: relative(process.cwd(), filePath),
321
+ language: 'typescript',
322
+ result,
323
+ ...counts,
324
+ });
325
+ } catch (error) {
326
+ console.error(`Error scanning ${filePath}:`, error);
327
+ }
328
+ }
215
329
 
216
- // Filter out null results (skipped files)
217
- return results.filter((r): r is FileScanResult => r !== null);
330
+ return results;
218
331
  }
219
332
 
220
333
  /**