codeslick-cli 1.3.0 → 1.4.1

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 (157) hide show
  1. package/README.md +50 -11
  2. package/dist/packages/cli/src/commands/scan.d.ts.map +1 -1
  3. package/dist/packages/cli/src/commands/scan.js +7 -3
  4. package/dist/packages/cli/src/commands/scan.js.map +1 -1
  5. package/dist/packages/cli/src/reporters/cli-reporter.d.ts +11 -0
  6. package/dist/packages/cli/src/reporters/cli-reporter.d.ts.map +1 -1
  7. package/dist/packages/cli/src/reporters/cli-reporter.js +150 -45
  8. package/dist/packages/cli/src/reporters/cli-reporter.js.map +1 -1
  9. package/dist/packages/cli/src/scanner/local-scanner.d.ts +2 -2
  10. package/dist/packages/cli/src/scanner/local-scanner.d.ts.map +1 -1
  11. package/dist/packages/cli/src/scanner/local-scanner.js +49 -9
  12. package/dist/packages/cli/src/scanner/local-scanner.js.map +1 -1
  13. package/dist/src/lib/analyzers/go-analyzer.d.ts +12 -0
  14. package/dist/src/lib/analyzers/go-analyzer.d.ts.map +1 -1
  15. package/dist/src/lib/analyzers/go-analyzer.js +113 -0
  16. package/dist/src/lib/analyzers/go-analyzer.js.map +1 -1
  17. package/dist/src/lib/analyzers/iac/pii-detector.d.ts +27 -0
  18. package/dist/src/lib/analyzers/iac/pii-detector.d.ts.map +1 -0
  19. package/dist/src/lib/analyzers/iac/pii-detector.js +199 -0
  20. package/dist/src/lib/analyzers/iac/pii-detector.js.map +1 -0
  21. package/dist/src/lib/analyzers/iac/pii-patterns.d.ts +43 -0
  22. package/dist/src/lib/analyzers/iac/pii-patterns.d.ts.map +1 -0
  23. package/dist/src/lib/analyzers/iac/pii-patterns.js +228 -0
  24. package/dist/src/lib/analyzers/iac/pii-patterns.js.map +1 -0
  25. package/dist/src/lib/analyzers/java-analyzer.d.ts +5 -0
  26. package/dist/src/lib/analyzers/java-analyzer.d.ts.map +1 -1
  27. package/dist/src/lib/analyzers/java-analyzer.js +51 -0
  28. package/dist/src/lib/analyzers/java-analyzer.js.map +1 -1
  29. package/dist/src/lib/analyzers/javascript/quality-checks/ai-hallucinations.d.ts +8 -4
  30. package/dist/src/lib/analyzers/javascript/quality-checks/ai-hallucinations.d.ts.map +1 -1
  31. package/dist/src/lib/analyzers/javascript/quality-checks/ai-hallucinations.js +109 -13
  32. package/dist/src/lib/analyzers/javascript/quality-checks/ai-hallucinations.js.map +1 -1
  33. package/dist/src/lib/analyzers/javascript/quality-checks/reference-errors.d.ts.map +1 -1
  34. package/dist/src/lib/analyzers/javascript/quality-checks/reference-errors.js +7 -8
  35. package/dist/src/lib/analyzers/javascript/quality-checks/reference-errors.js.map +1 -1
  36. package/dist/src/lib/analyzers/javascript-analyzer.d.ts.map +1 -1
  37. package/dist/src/lib/analyzers/javascript-analyzer.js +16 -12
  38. package/dist/src/lib/analyzers/javascript-analyzer.js.map +1 -1
  39. package/dist/src/lib/analyzers/kubernetes/checks/network-security.d.ts +33 -0
  40. package/dist/src/lib/analyzers/kubernetes/checks/network-security.d.ts.map +1 -0
  41. package/dist/src/lib/analyzers/kubernetes/checks/network-security.js +184 -0
  42. package/dist/src/lib/analyzers/kubernetes/checks/network-security.js.map +1 -0
  43. package/dist/src/lib/analyzers/kubernetes/checks/pod-security.d.ts +60 -0
  44. package/dist/src/lib/analyzers/kubernetes/checks/pod-security.d.ts.map +1 -0
  45. package/dist/src/lib/analyzers/kubernetes/checks/pod-security.js +418 -0
  46. package/dist/src/lib/analyzers/kubernetes/checks/pod-security.js.map +1 -0
  47. package/dist/src/lib/analyzers/kubernetes/checks/rbac-security.d.ts +44 -0
  48. package/dist/src/lib/analyzers/kubernetes/checks/rbac-security.d.ts.map +1 -0
  49. package/dist/src/lib/analyzers/kubernetes/checks/rbac-security.js +275 -0
  50. package/dist/src/lib/analyzers/kubernetes/checks/rbac-security.js.map +1 -0
  51. package/dist/src/lib/analyzers/kubernetes/checks/resource-management.d.ts +32 -0
  52. package/dist/src/lib/analyzers/kubernetes/checks/resource-management.d.ts.map +1 -0
  53. package/dist/src/lib/analyzers/kubernetes/checks/resource-management.js +176 -0
  54. package/dist/src/lib/analyzers/kubernetes/checks/resource-management.js.map +1 -0
  55. package/dist/src/lib/analyzers/kubernetes/checks/secrets-management.d.ts +38 -0
  56. package/dist/src/lib/analyzers/kubernetes/checks/secrets-management.d.ts.map +1 -0
  57. package/dist/src/lib/analyzers/kubernetes/checks/secrets-management.js +266 -0
  58. package/dist/src/lib/analyzers/kubernetes/checks/secrets-management.js.map +1 -0
  59. package/dist/src/lib/analyzers/kubernetes/checks/service-security.d.ts +26 -0
  60. package/dist/src/lib/analyzers/kubernetes/checks/service-security.d.ts.map +1 -0
  61. package/dist/src/lib/analyzers/kubernetes/checks/service-security.js +120 -0
  62. package/dist/src/lib/analyzers/kubernetes/checks/service-security.js.map +1 -0
  63. package/dist/src/lib/analyzers/kubernetes/parser.d.ts +74 -0
  64. package/dist/src/lib/analyzers/kubernetes/parser.d.ts.map +1 -0
  65. package/dist/src/lib/analyzers/kubernetes/parser.js +233 -0
  66. package/dist/src/lib/analyzers/kubernetes/parser.js.map +1 -0
  67. package/dist/src/lib/analyzers/kubernetes/pii-detector.d.ts +34 -0
  68. package/dist/src/lib/analyzers/kubernetes/pii-detector.d.ts.map +1 -0
  69. package/dist/src/lib/analyzers/kubernetes/pii-detector.js +182 -0
  70. package/dist/src/lib/analyzers/kubernetes/pii-detector.js.map +1 -0
  71. package/dist/src/lib/analyzers/kubernetes/types.d.ts +266 -0
  72. package/dist/src/lib/analyzers/kubernetes/types.d.ts.map +1 -0
  73. package/dist/src/lib/analyzers/kubernetes/types.js +77 -0
  74. package/dist/src/lib/analyzers/kubernetes/types.js.map +1 -0
  75. package/dist/src/lib/analyzers/kubernetes-analyzer.d.ts +93 -0
  76. package/dist/src/lib/analyzers/kubernetes-analyzer.d.ts.map +1 -0
  77. package/dist/src/lib/analyzers/kubernetes-analyzer.js +215 -0
  78. package/dist/src/lib/analyzers/kubernetes-analyzer.js.map +1 -0
  79. package/dist/src/lib/analyzers/python-analyzer.d.ts.map +1 -1
  80. package/dist/src/lib/analyzers/python-analyzer.js +32 -48
  81. package/dist/src/lib/analyzers/python-analyzer.js.map +1 -1
  82. package/dist/src/lib/analyzers/secrets/patterns/api-keys/ai-providers.d.ts +1 -1
  83. package/dist/src/lib/analyzers/secrets/patterns/api-keys/ai-providers.d.ts.map +1 -1
  84. package/dist/src/lib/analyzers/secrets/patterns/api-keys/aws.d.ts +1 -1
  85. package/dist/src/lib/analyzers/secrets/patterns/api-keys/aws.d.ts.map +1 -1
  86. package/dist/src/lib/analyzers/secrets/patterns/api-keys/cloud-providers.d.ts +1 -1
  87. package/dist/src/lib/analyzers/secrets/patterns/api-keys/cloud-providers.d.ts.map +1 -1
  88. package/dist/src/lib/analyzers/secrets/patterns/api-keys/communication.d.ts +1 -1
  89. package/dist/src/lib/analyzers/secrets/patterns/api-keys/communication.d.ts.map +1 -1
  90. package/dist/src/lib/analyzers/secrets/patterns/api-keys/generic.d.ts +1 -1
  91. package/dist/src/lib/analyzers/secrets/patterns/api-keys/generic.d.ts.map +1 -1
  92. package/dist/src/lib/analyzers/secrets/patterns/api-keys/github.d.ts +1 -1
  93. package/dist/src/lib/analyzers/secrets/patterns/api-keys/github.d.ts.map +1 -1
  94. package/dist/src/lib/analyzers/secrets/patterns/api-keys/stripe.d.ts +1 -1
  95. package/dist/src/lib/analyzers/secrets/patterns/api-keys/stripe.d.ts.map +1 -1
  96. package/dist/src/lib/analyzers/secrets/patterns/api-keys.d.ts +1 -1
  97. package/dist/src/lib/analyzers/secrets/patterns/api-keys.d.ts.map +1 -1
  98. package/dist/src/lib/analyzers/secrets/patterns/credentials.d.ts +1 -1
  99. package/dist/src/lib/analyzers/secrets/patterns/credentials.d.ts.map +1 -1
  100. package/dist/src/lib/analyzers/secrets/patterns/credentials.js +1 -1
  101. package/dist/src/lib/analyzers/secrets/patterns/credentials.js.map +1 -1
  102. package/dist/src/lib/analyzers/secrets/patterns/private-keys.d.ts +1 -1
  103. package/dist/src/lib/analyzers/secrets/patterns/private-keys.d.ts.map +1 -1
  104. package/dist/src/lib/analyzers/secrets/patterns/tokens.d.ts +1 -1
  105. package/dist/src/lib/analyzers/secrets/patterns/tokens.d.ts.map +1 -1
  106. package/dist/src/lib/analyzers/secrets/secrets-analyzer.d.ts +6 -32
  107. package/dist/src/lib/analyzers/secrets/secrets-analyzer.d.ts.map +1 -1
  108. package/dist/src/lib/analyzers/secrets/secrets-analyzer.js +48 -4
  109. package/dist/src/lib/analyzers/secrets/secrets-analyzer.js.map +1 -1
  110. package/dist/src/lib/analyzers/secrets/types.d.ts +40 -0
  111. package/dist/src/lib/analyzers/secrets/types.d.ts.map +1 -0
  112. package/dist/src/lib/analyzers/secrets/types.js +10 -0
  113. package/dist/src/lib/analyzers/secrets/types.js.map +1 -0
  114. package/dist/src/lib/analyzers/terraform/aws-checks.d.ts +71 -0
  115. package/dist/src/lib/analyzers/terraform/aws-checks.d.ts.map +1 -0
  116. package/dist/src/lib/analyzers/terraform/aws-checks.js +538 -0
  117. package/dist/src/lib/analyzers/terraform/aws-checks.js.map +1 -0
  118. package/dist/src/lib/analyzers/terraform/parser.d.ts +14 -0
  119. package/dist/src/lib/analyzers/terraform/parser.d.ts.map +1 -0
  120. package/dist/src/lib/analyzers/terraform/parser.js +237 -0
  121. package/dist/src/lib/analyzers/terraform/parser.js.map +1 -0
  122. package/dist/src/lib/analyzers/terraform/types.d.ts +70 -0
  123. package/dist/src/lib/analyzers/terraform/types.d.ts.map +1 -0
  124. package/dist/src/lib/analyzers/terraform/types.js +9 -0
  125. package/dist/src/lib/analyzers/terraform/types.js.map +1 -0
  126. package/dist/src/lib/analyzers/terraform-analyzer.d.ts +50 -0
  127. package/dist/src/lib/analyzers/terraform-analyzer.d.ts.map +1 -0
  128. package/dist/src/lib/analyzers/terraform-analyzer.js +168 -0
  129. package/dist/src/lib/analyzers/terraform-analyzer.js.map +1 -0
  130. package/dist/src/lib/analyzers/typescript/security-checks/type-security.d.ts.map +1 -1
  131. package/dist/src/lib/analyzers/typescript/security-checks/type-security.js +23 -8
  132. package/dist/src/lib/analyzers/typescript/security-checks/type-security.js.map +1 -1
  133. package/dist/src/lib/analyzers/typescript-analyzer.d.ts +5 -0
  134. package/dist/src/lib/analyzers/typescript-analyzer.d.ts.map +1 -1
  135. package/dist/src/lib/analyzers/typescript-analyzer.js +76 -0
  136. package/dist/src/lib/analyzers/typescript-analyzer.js.map +1 -1
  137. package/dist/src/lib/analyzers/utils/false-positive-filter.d.ts +27 -0
  138. package/dist/src/lib/analyzers/utils/false-positive-filter.d.ts.map +1 -0
  139. package/dist/src/lib/analyzers/utils/false-positive-filter.js +176 -0
  140. package/dist/src/lib/analyzers/utils/false-positive-filter.js.map +1 -0
  141. package/dist/src/lib/security/epss-service.d.ts.map +1 -1
  142. package/dist/src/lib/security/epss-service.js +83 -50
  143. package/dist/src/lib/security/epss-service.js.map +1 -1
  144. package/dist/src/lib/security/severity-scoring.d.ts.map +1 -1
  145. package/dist/src/lib/security/severity-scoring.js +140 -0
  146. package/dist/src/lib/security/severity-scoring.js.map +1 -1
  147. package/dist/src/lib/types/index.d.ts +3 -3
  148. package/dist/src/lib/types/index.d.ts.map +1 -1
  149. package/dist/src/lib/utils/ignore-patterns.d.ts +60 -0
  150. package/dist/src/lib/utils/ignore-patterns.d.ts.map +1 -0
  151. package/dist/src/lib/utils/ignore-patterns.js +212 -0
  152. package/dist/src/lib/utils/ignore-patterns.js.map +1 -0
  153. package/package.json +10 -7
  154. package/src/commands/scan.ts +7 -3
  155. package/src/reporters/cli-reporter.ts +174 -48
  156. package/src/scanner/local-scanner.ts +65 -10
  157. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ /**
3
+ * CodeSlick Ignore Patterns Utility
4
+ *
5
+ * Handles .codeslickignore file processing and pattern matching
6
+ * to exclude files from security analysis (analyzers, tests, examples, etc.)
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.loadIgnorePatterns = loadIgnorePatterns;
13
+ exports.shouldIgnoreFile = shouldIgnoreFile;
14
+ exports.hasInlineSuppression = hasInlineSuppression;
15
+ exports.isAnalyzerImplementation = isAnalyzerImplementation;
16
+ exports.isEducationalContent = isEducationalContent;
17
+ exports.isTestFile = isTestFile;
18
+ exports.isLegitimateAPI = isLegitimateAPI;
19
+ exports.filterSuppressedVulnerabilities = filterSuppressedVulnerabilities;
20
+ exports.clearCache = clearCache;
21
+ const minimatch_1 = require("minimatch");
22
+ const fs_1 = __importDefault(require("fs"));
23
+ const path_1 = __importDefault(require("path"));
24
+ let cachedPatterns = null;
25
+ /**
26
+ * Load patterns from .codeslickignore file
27
+ */
28
+ function loadIgnorePatterns(projectRoot = process.cwd()) {
29
+ // Return cached patterns if already loaded
30
+ if (cachedPatterns?.loaded && cachedPatterns.ignoreFile === projectRoot) {
31
+ return cachedPatterns.patterns;
32
+ }
33
+ const ignoreFilePath = path_1.default.join(projectRoot, '.codeslickignore');
34
+ const patterns = [];
35
+ try {
36
+ if (fs_1.default.existsSync(ignoreFilePath)) {
37
+ const content = fs_1.default.readFileSync(ignoreFilePath, 'utf-8');
38
+ const lines = content.split('\n');
39
+ for (const line of lines) {
40
+ const trimmed = line.trim();
41
+ // Skip empty lines and comments
42
+ if (trimmed === '' || trimmed.startsWith('#')) {
43
+ continue;
44
+ }
45
+ patterns.push(trimmed);
46
+ }
47
+ }
48
+ }
49
+ catch (error) {
50
+ console.warn(`Warning: Could not read .codeslickignore file: ${error}`);
51
+ }
52
+ // Cache the patterns
53
+ cachedPatterns = {
54
+ patterns,
55
+ loaded: true,
56
+ ignoreFile: projectRoot,
57
+ };
58
+ return patterns;
59
+ }
60
+ /**
61
+ * Check if a file should be ignored based on .codeslickignore patterns
62
+ */
63
+ function shouldIgnoreFile(filePath, projectRoot = process.cwd()) {
64
+ const patterns = loadIgnorePatterns(projectRoot);
65
+ if (patterns.length === 0) {
66
+ return false;
67
+ }
68
+ // Normalize path to use forward slashes (works on both Windows and Unix)
69
+ const normalizedPath = filePath.replace(/\\/g, '/');
70
+ // Get relative path from project root
71
+ const relativePath = path_1.default.relative(projectRoot, filePath).replace(/\\/g, '/');
72
+ // Check against each pattern
73
+ for (const pattern of patterns) {
74
+ // Test both absolute and relative paths
75
+ if ((0, minimatch_1.minimatch)(normalizedPath, pattern, { dot: true, matchBase: true }) ||
76
+ (0, minimatch_1.minimatch)(relativePath, pattern, { dot: true, matchBase: true })) {
77
+ return true;
78
+ }
79
+ }
80
+ return false;
81
+ }
82
+ /**
83
+ * Check if code contains an inline suppression comment
84
+ * Supports:
85
+ * // codeslick-ignore-next-line: reason (JavaScript, TypeScript, Java, Go, Terraform)
86
+ * # codeslick-ignore-next-line: reason (Python)
87
+ *
88
+ * @param code - The full source code
89
+ * @param lineNumber - 1-indexed line number where the issue was detected
90
+ */
91
+ function hasInlineSuppression(code, lineNumber) {
92
+ const lines = code.split('\n');
93
+ // Check the line BEFORE the issue
94
+ // lineNumber is 1-indexed, so line 11 means we want to check line 10
95
+ // Array is 0-indexed, so line 10 is at index 9, which is lineNumber - 2
96
+ if (lineNumber > 1 && lineNumber <= lines.length) {
97
+ const previousLine = lines[lineNumber - 2]; // Fixed: was lineNumber - 1
98
+ // Match: // codeslick-ignore-next-line or // codeslick-ignore-next-line: reason
99
+ const slashMatch = previousLine.match(/\/\/\s*codeslick-ignore-next-line(?::\s*(.+))?/);
100
+ if (slashMatch) {
101
+ return {
102
+ suppressed: true,
103
+ reason: slashMatch[1]?.trim() || 'Inline suppression',
104
+ };
105
+ }
106
+ // Match: # codeslick-ignore-next-line or # codeslick-ignore-next-line: reason (Python)
107
+ const hashMatch = previousLine.match(/#\s*codeslick-ignore-next-line(?::\s*(.+))?/);
108
+ if (hashMatch) {
109
+ return {
110
+ suppressed: true,
111
+ reason: hashMatch[1]?.trim() || 'Inline suppression',
112
+ };
113
+ }
114
+ }
115
+ return { suppressed: false };
116
+ }
117
+ /**
118
+ * Check if code is in an analyzer implementation file
119
+ * (contains detection patterns, not actual vulnerabilities)
120
+ */
121
+ function isAnalyzerImplementation(filePath) {
122
+ const normalizedPath = filePath.replace(/\\/g, '/');
123
+ // Analyzer implementation patterns
124
+ const analyzerPatterns = [
125
+ /\/analyzers\/.*-analyzer\.ts$/,
126
+ /\/analyzers\/.*\/checks\//,
127
+ /\/analyzers\/.*\/parser\.ts$/,
128
+ ];
129
+ return analyzerPatterns.some(pattern => pattern.test(normalizedPath));
130
+ }
131
+ /**
132
+ * Check if code is educational/example content
133
+ * (may contain intentional vulnerabilities for demonstration)
134
+ */
135
+ function isEducationalContent(filePath) {
136
+ const normalizedPath = filePath.replace(/\\/g, '/').toLowerCase();
137
+ const educationalPatterns = [
138
+ /ai-examples\//,
139
+ /examples\//,
140
+ /\/security-fixer\.ts$/,
141
+ /\/pattern-fixer\.ts$/,
142
+ /docs\/.*\.md$/,
143
+ ];
144
+ return educationalPatterns.some(pattern => pattern.test(normalizedPath));
145
+ }
146
+ /**
147
+ * Check if file is a test file
148
+ */
149
+ function isTestFile(filePath) {
150
+ const normalizedPath = filePath.replace(/\\/g, '/');
151
+ const testPatterns = [
152
+ /\.test\.(ts|js|tsx|jsx|py|go|java)$/,
153
+ /\/__tests__\//,
154
+ /\/__mocks__\//,
155
+ /\/tests?\//,
156
+ /^test-.*\.(tf|ts|js|py|go|java)$/,
157
+ ];
158
+ return testPatterns.some(pattern => pattern.test(normalizedPath));
159
+ }
160
+ /**
161
+ * Check if a URL is a known legitimate API endpoint
162
+ * (whitelisted APIs that should not be flagged as SSRF)
163
+ */
164
+ function isLegitimateAPI(url) {
165
+ const legitimateAPIs = [
166
+ 'openrouter.ai',
167
+ 'api.openai.com',
168
+ 'anthropic.com',
169
+ 'api.anthropic.com',
170
+ 'api.deepseek.com',
171
+ 'together.xyz',
172
+ 'api.together.xyz',
173
+ 'replicate.com',
174
+ 'vercel.com',
175
+ 'github.com',
176
+ 'api.github.com',
177
+ ];
178
+ try {
179
+ const parsedUrl = new URL(url);
180
+ return legitimateAPIs.some(domain => parsedUrl.hostname.includes(domain));
181
+ }
182
+ catch {
183
+ // If URL parsing fails, don't whitelist
184
+ return false;
185
+ }
186
+ }
187
+ /**
188
+ * Filter vulnerabilities based on inline suppression comments
189
+ * Removes vulnerabilities that have a codeslick-ignore-next-line comment above them
190
+ */
191
+ function filterSuppressedVulnerabilities(code, vulnerabilities) {
192
+ return vulnerabilities.filter(vuln => {
193
+ // Skip if no line number (can't check suppression)
194
+ if (!vuln.line) {
195
+ return true;
196
+ }
197
+ const { suppressed, reason } = hasInlineSuppression(code, vuln.line);
198
+ if (suppressed) {
199
+ // Log suppression for debugging (can be removed in production)
200
+ console.log(`[CodeSlick] Suppressed vulnerability at line ${vuln.line}: ${reason}`);
201
+ return false;
202
+ }
203
+ return true;
204
+ });
205
+ }
206
+ /**
207
+ * Clear cached patterns (useful for testing)
208
+ */
209
+ function clearCache() {
210
+ cachedPatterns = null;
211
+ }
212
+ //# sourceMappingURL=ignore-patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ignore-patterns.js","sourceRoot":"","sources":["../../../../../../src/lib/utils/ignore-patterns.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "codeslick-cli",
3
- "version": "1.3.0",
4
- "description": "CodeSlick CLI tool for pre-commit security scanning",
3
+ "version": "1.4.1",
4
+ "description": "CodeSlick CLI tool for pre-commit security scanning with Terraform IaC support",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "codeslick": "./bin/codeslick.cjs",
@@ -19,7 +19,10 @@
19
19
  "static-analysis",
20
20
  "pre-commit",
21
21
  "git-hook",
22
- "vulnerability-scanner"
22
+ "vulnerability-scanner",
23
+ "terraform",
24
+ "iac",
25
+ "infrastructure-as-code"
23
26
  ],
24
27
  "author": "CodeSlick <support@codeslick.dev>",
25
28
  "license": "MIT",
@@ -36,12 +39,12 @@
36
39
  "node": ">=18.0.0"
37
40
  },
38
41
  "dependencies": {
39
- "yargs": "^17.7.2",
40
42
  "chalk": "^4.1.2",
41
- "ora": "^5.4.1",
42
43
  "cli-table3": "^0.6.3",
43
- "glob": "^10.3.10",
44
- "typescript": "^5.3.3"
44
+ "glob": "^13.0.1",
45
+ "ora": "^5.4.1",
46
+ "typescript": "^5.3.3",
47
+ "yargs": "^17.7.2"
45
48
  },
46
49
  "devDependencies": {
47
50
  "@types/node": "^20.10.0",
@@ -291,8 +291,10 @@ export async function scanCommand(args: ScanArgs): Promise<void> {
291
291
  // Collect unique languages scanned
292
292
  const languages = [...new Set(results.map(r => r.language))];
293
293
 
294
- // Send telemetry (fire-and-forget, won't block exit)
295
- trackScan({
294
+ // Send telemetry store the promise so we can await it before process.exit.
295
+ // process.exit() terminates all pending async ops, so fire-and-forget never
296
+ // completes. The telemetry function has a 3-second timeout built in.
297
+ const telemetryDone = trackScan({
296
298
  filesScanned: results.length,
297
299
  languages,
298
300
  vulnerabilities: {
@@ -302,7 +304,7 @@ export async function scanCommand(args: ScanArgs): Promise<void> {
302
304
  low: totalLow,
303
305
  },
304
306
  scanDuration: duration,
305
- }).catch(() => {}); // Ignore telemetry errors
307
+ }).catch(() => {}); // Never let telemetry errors surface
306
308
 
307
309
  // WR2: Build threshold configuration from CLI args or use defaults
308
310
  // NOTE: For CLI, thresholds are ENABLED by default (unlike dashboard which is opt-in)
@@ -382,6 +384,7 @@ export async function scanCommand(args: ScanArgs): Promise<void> {
382
384
  }
383
385
  }
384
386
 
387
+ await telemetryDone; // Wait for telemetry before terminating process
385
388
  process.exit(1); // Exit with failure
386
389
  } else {
387
390
  if (!args.json) {
@@ -391,6 +394,7 @@ export async function scanCommand(args: ScanArgs): Promise<void> {
391
394
  }
392
395
  }
393
396
 
397
+ await telemetryDone; // Wait for telemetry before terminating process
394
398
  process.exit(0); // Exit with success
395
399
  }
396
400
  } catch (error) {
@@ -59,18 +59,38 @@ export function getSeveritySymbol(severity: string): string {
59
59
 
60
60
  /**
61
61
  * Print scan results summary table
62
+ *
63
+ * IMPORTANT: Recalculates counts from actual filtered vulnerabilities
64
+ * to ensure summary reflects false-positive filtering.
62
65
  */
63
66
  export function printSummaryTable(results: FileScanResult[]): void {
64
67
  const totalFiles = results.length;
65
- const totalCritical = results.reduce((sum, r) => sum + r.critical, 0);
66
- const totalHigh = results.reduce((sum, r) => sum + r.high, 0);
67
- const totalMedium = results.reduce((sum, r) => sum + r.medium, 0);
68
- const totalLow = results.reduce((sum, r) => sum + r.low, 0);
69
- const totalVulns = totalCritical + totalHigh + totalMedium + totalLow;
70
68
 
71
- const filesWithIssues = results.filter(
72
- (r) => r.critical > 0 || r.high > 0 || r.medium > 0 || r.low > 0
73
- ).length;
69
+ // Recalculate from actual vulnerabilities (post-filtering)
70
+ let totalCritical = 0;
71
+ let totalHigh = 0;
72
+ let totalMedium = 0;
73
+ let totalLow = 0;
74
+ let filesWithIssues = 0;
75
+
76
+ results.forEach((r) => {
77
+ const vulns = r.result.security?.vulnerabilities || [];
78
+ const crit = vulns.filter((v: any) => v.severity?.toLowerCase() === 'critical').length;
79
+ const high = vulns.filter((v: any) => v.severity?.toLowerCase() === 'high').length;
80
+ const med = vulns.filter((v: any) => v.severity?.toLowerCase() === 'medium').length;
81
+ const low = vulns.filter((v: any) => v.severity?.toLowerCase() === 'low').length;
82
+
83
+ totalCritical += crit;
84
+ totalHigh += high;
85
+ totalMedium += med;
86
+ totalLow += low;
87
+
88
+ if (crit + high + med + low > 0) {
89
+ filesWithIssues++;
90
+ }
91
+ });
92
+
93
+ const totalVulns = totalCritical + totalHigh + totalMedium + totalLow;
74
94
 
75
95
  console.log('');
76
96
  console.log(chalk.bold('Scan Summary'));
@@ -281,6 +301,8 @@ export function printCommitAllowed(): void {
281
301
  /**
282
302
  * Print summary table grouped by language
283
303
  * Shows files scanned, issues found, and critical count per language
304
+ *
305
+ * IMPORTANT: Recalculates counts from actual filtered vulnerabilities.
284
306
  */
285
307
  export function printLanguageSummary(results: FileScanResult[]): void {
286
308
  // Group results by language
@@ -289,12 +311,20 @@ export function printLanguageSummary(results: FileScanResult[]): void {
289
311
  for (const result of results) {
290
312
  const lang = result.language;
291
313
  const existing = byLanguage.get(lang) || { files: 0, issues: 0, critical: 0, high: 0, medium: 0, low: 0 };
314
+
315
+ // Recalculate from actual vulnerabilities
316
+ const vulns = result.result.security?.vulnerabilities || [];
317
+ const crit = vulns.filter((v: any) => v.severity?.toLowerCase() === 'critical').length;
318
+ const high = vulns.filter((v: any) => v.severity?.toLowerCase() === 'high').length;
319
+ const med = vulns.filter((v: any) => v.severity?.toLowerCase() === 'medium').length;
320
+ const low = vulns.filter((v: any) => v.severity?.toLowerCase() === 'low').length;
321
+
292
322
  existing.files++;
293
- existing.critical += result.critical;
294
- existing.high += result.high;
295
- existing.medium += result.medium;
296
- existing.low += result.low;
297
- existing.issues += result.critical + result.high + result.medium + result.low;
323
+ existing.critical += crit;
324
+ existing.high += high;
325
+ existing.medium += med;
326
+ existing.low += low;
327
+ existing.issues += crit + high + med + low;
298
328
  byLanguage.set(lang, existing);
299
329
  }
300
330
 
@@ -478,29 +508,76 @@ function truncateMessage(message: string, maxLength: number): string {
478
508
 
479
509
  /**
480
510
  * Output results as JSON
511
+ *
512
+ * IMPORTANT: Summary counts are calculated from ACTUAL filtered vulnerabilities,
513
+ * not from pre-calculated counts. This ensures the summary reflects
514
+ * false-positive filtering applied during analysis.
481
515
  */
482
516
  export function printJSONResults(results: FileScanResult[]): void {
517
+ // Recalculate counts from actual filtered vulnerabilities
518
+ // This ensures summary matches the vulnerabilities array after false-positive filtering
519
+ const recalculateCountsFromVulnerabilities = (r: FileScanResult) => {
520
+ const vulns = r.result.security?.vulnerabilities || [];
521
+ const counts = {
522
+ critical: vulns.filter((v: any) => v.severity?.toLowerCase() === 'critical').length,
523
+ high: vulns.filter((v: any) => v.severity?.toLowerCase() === 'high').length,
524
+ medium: vulns.filter((v: any) => v.severity?.toLowerCase() === 'medium').length,
525
+ low: vulns.filter((v: any) => v.severity?.toLowerCase() === 'low').length,
526
+ };
527
+
528
+ // Debug first file
529
+ if (process.env.DEBUG_COUNTS && r.relativePath === 'scripts/reset-quota.ts') {
530
+ console.error(`[DEBUG] ${r.relativePath}: old_high=${r.high}, new_high=${counts.high}, total_vulns=${vulns.length}`);
531
+ }
532
+
533
+ return counts;
534
+ };
535
+
536
+ // Calculate accurate summary from filtered vulnerabilities
537
+ let totalCritical = 0;
538
+ let totalHigh = 0;
539
+ let totalMedium = 0;
540
+ let totalLow = 0;
541
+ let filesWithIssues = 0;
542
+
543
+ const filesOutput = results.map((r) => {
544
+ const counts = recalculateCountsFromVulnerabilities(r);
545
+
546
+ // Debug: Log first mismatch
547
+ if (counts.high !== r.high && process.env.DEBUG_COUNTS) {
548
+ console.error(`[DEBUG] Mismatch in ${r.relativePath}: old=${r.high}, new=${counts.high}, vulns=${r.result.security?.vulnerabilities?.length || 0}`);
549
+ }
550
+
551
+ // Accumulate totals
552
+ totalCritical += counts.critical;
553
+ totalHigh += counts.high;
554
+ totalMedium += counts.medium;
555
+ totalLow += counts.low;
556
+
557
+ // Count files with issues
558
+ if (counts.critical + counts.high + counts.medium + counts.low > 0) {
559
+ filesWithIssues++;
560
+ }
561
+
562
+ return {
563
+ path: r.relativePath,
564
+ language: r.language,
565
+ vulnerabilities: r.result.security?.vulnerabilities || [],
566
+ counts,
567
+ };
568
+ });
569
+
483
570
  const output = {
484
571
  summary: {
485
572
  filesScanned: results.length,
486
- filesWithIssues: results.filter((r) => r.critical > 0 || r.high > 0 || r.medium > 0 || r.low > 0).length,
487
- totalVulnerabilities: results.reduce((sum, r) => r.critical + r.high + r.medium + r.low + sum, 0),
488
- critical: results.reduce((sum, r) => sum + r.critical, 0),
489
- high: results.reduce((sum, r) => sum + r.high, 0),
490
- medium: results.reduce((sum, r) => sum + r.medium, 0),
491
- low: results.reduce((sum, r) => sum + r.low, 0),
573
+ filesWithIssues,
574
+ totalVulnerabilities: totalCritical + totalHigh + totalMedium + totalLow,
575
+ critical: totalCritical,
576
+ high: totalHigh,
577
+ medium: totalMedium,
578
+ low: totalLow,
492
579
  },
493
- files: results.map((r) => ({
494
- path: r.relativePath,
495
- language: r.language,
496
- vulnerabilities: r.result.security?.vulnerabilities || [],
497
- counts: {
498
- critical: r.critical,
499
- high: r.high,
500
- medium: r.medium,
501
- low: r.low,
502
- },
503
- })),
580
+ files: filesOutput,
504
581
  };
505
582
 
506
583
  console.log(JSON.stringify(output, null, 2));
@@ -520,28 +597,48 @@ export function generateMarkdownReport(
520
597
  const filename = `codeslick-report-${timestamp}.md`;
521
598
  const filepath = join(process.cwd(), filename);
522
599
 
523
- // Calculate totals
600
+ // Calculate totals from actual vulnerabilities (post-filtering)
524
601
  const totalFiles = results.length;
525
- const totalCritical = results.reduce((sum, r) => sum + r.critical, 0);
526
- const totalHigh = results.reduce((sum, r) => sum + r.high, 0);
527
- const totalMedium = results.reduce((sum, r) => sum + r.medium, 0);
528
- const totalLow = results.reduce((sum, r) => sum + r.low, 0);
529
- const totalVulns = totalCritical + totalHigh + totalMedium + totalLow;
530
- const filesWithIssues = results.filter(r => r.critical + r.high + r.medium + r.low > 0).length;
602
+ let totalCritical = 0;
603
+ let totalHigh = 0;
604
+ let totalMedium = 0;
605
+ let totalLow = 0;
606
+ let filesWithIssues = 0;
531
607
 
532
- // Group by language
608
+ // Group by language and recalculate counts
533
609
  const byLanguage = new Map<string, { files: number; critical: number; high: number; medium: number; low: number }>();
534
610
  for (const result of results) {
535
611
  const lang = result.language;
536
612
  const existing = byLanguage.get(lang) || { files: 0, critical: 0, high: 0, medium: 0, low: 0 };
613
+
614
+ // Recalculate from actual vulnerabilities
615
+ const vulns = result.result.security?.vulnerabilities || [];
616
+ const crit = vulns.filter((v: any) => v.severity?.toLowerCase() === 'critical').length;
617
+ const high = vulns.filter((v: any) => v.severity?.toLowerCase() === 'high').length;
618
+ const med = vulns.filter((v: any) => v.severity?.toLowerCase() === 'medium').length;
619
+ const low = vulns.filter((v: any) => v.severity?.toLowerCase() === 'low').length;
620
+
537
621
  existing.files++;
538
- existing.critical += result.critical;
539
- existing.high += result.high;
540
- existing.medium += result.medium;
541
- existing.low += result.low;
622
+ existing.critical += crit;
623
+ existing.high += high;
624
+ existing.medium += med;
625
+ existing.low += low;
542
626
  byLanguage.set(lang, existing);
627
+
628
+ // Accumulate totals
629
+ totalCritical += crit;
630
+ totalHigh += high;
631
+ totalMedium += med;
632
+ totalLow += low;
633
+
634
+ // Count files with issues
635
+ if (crit + high + med + low > 0) {
636
+ filesWithIssues++;
637
+ }
543
638
  }
544
639
 
640
+ const totalVulns = totalCritical + totalHigh + totalMedium + totalLow;
641
+
545
642
  // Collect all vulnerabilities for Top 10
546
643
  const allVulns: Array<{ file: string; line: number; vuln: SecurityVulnerability }> = [];
547
644
  for (const result of results) {
@@ -656,8 +753,25 @@ export function generateMarkdownReport(
656
753
  md += `\n*Supported: .js, .jsx, .ts, .tsx, .py, .java*\n`;
657
754
  }
658
755
 
659
- // Files with issues (detailed)
660
- const filesWithProblems = results.filter(r => r.critical + r.high + r.medium + r.low > 0);
756
+ // Files with issues (detailed) - recalculate counts from actual vulnerabilities
757
+ const filesWithProblems = results
758
+ .map((r) => {
759
+ const vulns = r.result.security?.vulnerabilities || [];
760
+ const crit = vulns.filter((v: any) => v.severity?.toLowerCase() === 'critical').length;
761
+ const high = vulns.filter((v: any) => v.severity?.toLowerCase() === 'high').length;
762
+ const med = vulns.filter((v: any) => v.severity?.toLowerCase() === 'medium').length;
763
+ const low = vulns.filter((v: any) => v.severity?.toLowerCase() === 'low').length;
764
+ return {
765
+ relativePath: r.relativePath,
766
+ critical: crit,
767
+ high,
768
+ medium: med,
769
+ low,
770
+ total: crit + high + med + low,
771
+ };
772
+ })
773
+ .filter((f) => f.total > 0);
774
+
661
775
  if (filesWithProblems.length > 0) {
662
776
  md += `
663
777
  ---
@@ -693,16 +807,28 @@ export function generateMarkdownReport(
693
807
 
694
808
  /**
695
809
  * Print brief summary for screen (when report is generated)
810
+ *
811
+ * IMPORTANT: Recalculates counts from actual filtered vulnerabilities.
696
812
  */
697
813
  export function printBriefSummary(
698
814
  results: FileScanResult[],
699
815
  reportPath: string,
700
816
  duration: number
701
817
  ): void {
702
- const totalCritical = results.reduce((sum, r) => sum + r.critical, 0);
703
- const totalHigh = results.reduce((sum, r) => sum + r.high, 0);
704
- const totalMedium = results.reduce((sum, r) => sum + r.medium, 0);
705
- const totalLow = results.reduce((sum, r) => sum + r.low, 0);
818
+ // Recalculate from actual vulnerabilities (post-filtering)
819
+ let totalCritical = 0;
820
+ let totalHigh = 0;
821
+ let totalMedium = 0;
822
+ let totalLow = 0;
823
+
824
+ results.forEach((r) => {
825
+ const vulns = r.result.security?.vulnerabilities || [];
826
+ totalCritical += vulns.filter((v: any) => v.severity?.toLowerCase() === 'critical').length;
827
+ totalHigh += vulns.filter((v: any) => v.severity?.toLowerCase() === 'high').length;
828
+ totalMedium += vulns.filter((v: any) => v.severity?.toLowerCase() === 'medium').length;
829
+ totalLow += vulns.filter((v: any) => v.severity?.toLowerCase() === 'low').length;
830
+ });
831
+
706
832
  const totalVulns = totalCritical + totalHigh + totalMedium + totalLow;
707
833
 
708
834
  console.log('');