vibe-guard 1.2.0 → 2.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 (49) hide show
  1. package/README.md +38 -74
  2. package/dist/bin/vibe-guard.js +93 -264
  3. package/dist/bin/vibe-guard.js.map +1 -1
  4. package/dist/index.d.ts +4 -9
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +36 -69
  7. package/dist/index.js.map +1 -1
  8. package/dist/reporter.d.ts +4 -11
  9. package/dist/reporter.d.ts.map +1 -1
  10. package/dist/reporter.js +125 -461
  11. package/dist/reporter.js.map +1 -1
  12. package/dist/rules/exposed-secrets.d.ts.map +1 -1
  13. package/dist/rules/exposed-secrets.js +27 -8
  14. package/dist/rules/exposed-secrets.js.map +1 -1
  15. package/dist/rules/hardcoded-sensitive-data.js +1 -1
  16. package/dist/rules/hardcoded-sensitive-data.js.map +1 -1
  17. package/dist/rules/index.d.ts +3 -1
  18. package/dist/rules/index.d.ts.map +1 -1
  19. package/dist/rules/index.js +7 -1
  20. package/dist/rules/index.js.map +1 -1
  21. package/dist/rules/insecure-deserialization.js +1 -1
  22. package/dist/rules/insecure-deserialization.js.map +1 -1
  23. package/dist/rules/insecure-error-handling.js +1 -1
  24. package/dist/rules/insecure-error-handling.js.map +1 -1
  25. package/dist/rules/insecure-file-upload.js +1 -1
  26. package/dist/rules/insecure-file-upload.js.map +1 -1
  27. package/dist/rules/insecure-http.js +1 -1
  28. package/dist/rules/insecure-http.js.map +1 -1
  29. package/dist/rules/insecure-logging.js +1 -1
  30. package/dist/rules/insecure-logging.js.map +1 -1
  31. package/dist/rules/kubernetes-security.js +3 -3
  32. package/dist/rules/kubernetes-security.js.map +1 -1
  33. package/dist/rules/unpinned-supply-chain-dependencies.d.ts +12 -0
  34. package/dist/rules/unpinned-supply-chain-dependencies.d.ts.map +1 -0
  35. package/dist/rules/unpinned-supply-chain-dependencies.js +56 -0
  36. package/dist/rules/unpinned-supply-chain-dependencies.js.map +1 -0
  37. package/dist/rules/unsafe-llm-prompt-construction.d.ts +9 -0
  38. package/dist/rules/unsafe-llm-prompt-construction.d.ts.map +1 -0
  39. package/dist/rules/unsafe-llm-prompt-construction.js +31 -0
  40. package/dist/rules/unsafe-llm-prompt-construction.js.map +1 -0
  41. package/dist/rules/xss-detection.js +1 -1
  42. package/dist/rules/xss-detection.js.map +1 -1
  43. package/dist/scanner.d.ts +1 -2
  44. package/dist/scanner.d.ts.map +1 -1
  45. package/dist/scanner.js +43 -116
  46. package/dist/scanner.js.map +1 -1
  47. package/dist/types/version.d.ts +1 -1
  48. package/dist/types/version.js +1 -1
  49. package/package.json +20 -18
package/dist/reporter.js CHANGED
@@ -4,496 +4,160 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Reporter = void 0;
7
- const Table = require('cli-table3');
8
7
  const chalk_1 = __importDefault(require("chalk"));
9
- // Class for reporting the scan results
8
+ const path_1 = __importDefault(require("path"));
9
+ const version_1 = require("./types/version");
10
10
  class Reporter {
11
- // Formats the scan results as a table
12
11
  formatTable(result) {
13
12
  if (result.issues.length === 0) {
14
- return this.formatSuccess(result);
13
+ return chalk_1.default.green(`No security issues found in ${result.filesScanned} files`);
15
14
  }
16
- const table = new Table({
17
- head: [
18
- chalk_1.default.bold('Rule'),
19
- chalk_1.default.bold('Severity'),
20
- chalk_1.default.bold('File'),
21
- chalk_1.default.bold('Line'),
22
- chalk_1.default.bold('Message')
23
- ],
24
- colWidths: [25, 12, 40, 8, 60],
25
- wordWrap: true
26
- });
27
- result.issues.forEach(issue => {
28
- table.push([
29
- issue.rule,
30
- this.colorSeverity(issue.severity),
31
- this.truncateFilePath(issue.file),
32
- issue.line.toString(),
33
- issue.message
34
- ]);
35
- });
36
- return this.formatHeader(result) + '\n\n' + table.toString() + '\n\n' + this.formatSummary(result);
15
+ const issues = result.issues.map((i) => {
16
+ const finding = `${this.colorSeverity(i.severity)} ${i.rule.padEnd(25)} ${this.truncateFilePath(i.file)}:${i.line} ${i.message}`;
17
+ return i.suggestion ? `${finding}\n Remediation: ${i.suggestion}` : finding;
18
+ }).join('\n');
19
+ return `${chalk_1.default.red.bold(`Found ${result.issuesFound} issues in ${result.filesScanned} files`)}\n${issues}`;
37
20
  }
38
- // Formats the scan results as a JSON string
39
21
  formatJson(result) {
40
22
  return JSON.stringify(result, null, 2);
41
23
  }
42
- // Formats the scan results as a SARIF string
24
+ truncateFilePath(filePath, maxLength = 50) {
25
+ if (!filePath || filePath.trim() === '' || /^[\.\/\\]*$/.test(filePath)) {
26
+ return '';
27
+ }
28
+ const sanitized = path_1.default.posix.normalize(filePath)
29
+ .replace(/\\/g, '/')
30
+ .replace(/^\/+/, '')
31
+ .replace(/^(\.\.[/\\])+/, '');
32
+ if (sanitized.length <= maxLength)
33
+ return sanitized;
34
+ const parts = sanitized.split('/');
35
+ if (parts.length === 2)
36
+ return sanitized;
37
+ if (parts.length >= 3) {
38
+ const first = parts[0];
39
+ const last = parts[parts.length - 1];
40
+ const result = `${first}/.../${last}`;
41
+ if (result.length <= maxLength)
42
+ return result;
43
+ }
44
+ const left = Math.ceil((maxLength - 3) / 2);
45
+ const right = Math.floor((maxLength - 3) / 2);
46
+ return `${sanitized.slice(0, left)}...${sanitized.slice(-right)}`;
47
+ }
43
48
  formatSarif(result) {
44
- const sarif = {
45
- $schema: "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json",
49
+ return JSON.stringify({
50
+ $schema: "https://json.schemastore.org/sarif-2.1.0-rtm.5.json",
46
51
  version: "2.1.0",
47
- runs: [
48
- {
52
+ runs: [{
49
53
  tool: {
50
54
  driver: {
51
55
  name: "Vibe-Guard",
52
- version: "1.1.5",
53
- informationUri: "https://github.com/Devjosef/vibe-guard",
54
- rules: this.generateSarifRules(result)
56
+ version: version_1.VERSION,
57
+ informationUri: "https://github.com/Devjosef/vibe-guard"
55
58
  }
56
59
  },
57
- results: this.generateSarifResults(result),
58
- invocations: [
59
- {
60
- executionSuccessful: true,
61
- toolExecutionNotifications: [
62
- {
63
- descriptor: {
64
- id: "vibe-guard-scan"
65
- },
66
- message: {
67
- text: `Scanned ${result.filesScanned} files and found ${result.issuesFound} security issues`
68
- }
60
+ invocations: [{
61
+ executionSuccessful: true
62
+ }],
63
+ results: result.issues.map(issue => ({
64
+ ruleId: issue.rule,
65
+ level: issue.severity === 'critical' || issue.severity === 'high'
66
+ ? 'error'
67
+ : issue.severity === 'medium' ? 'warning' : 'note',
68
+ message: {
69
+ text: issue.suggestion
70
+ ? `${issue.message}\nRemediation: ${issue.suggestion}`
71
+ : issue.message
72
+ },
73
+ locations: [{
74
+ physicalLocation: {
75
+ artifactLocation: { uri: issue.file },
76
+ region: { startLine: issue.line }
69
77
  }
70
- ]
71
- }
72
- ]
73
- }
74
- ]
75
- };
76
- return JSON.stringify(sarif, null, 2);
78
+ }]
79
+ }))
80
+ }]
81
+ }, null, 2);
77
82
  }
78
- // Formats the scan results as an HTML string
79
83
  formatHtml(result) {
80
- const html = `<!DOCTYPE html>
81
- <html lang="en">
84
+ let contentHtml = '';
85
+ if (result.issues.length === 0) {
86
+ contentHtml = `
87
+ <div class="summary">
88
+ <h2>Scan Summary</h2>
89
+ <p><strong>No security issues found</strong></p>
90
+ </div>`;
91
+ }
92
+ else {
93
+ const issuesHtml = result.issues.map(issue => {
94
+ const rule = this.escapeHtml(issue.rule);
95
+ const file = this.escapeHtml(this.truncateFilePath(issue.file));
96
+ const message = this.escapeHtml(issue.message);
97
+ const suggestion = this.escapeHtml(issue.suggestion);
98
+ return `<div style="border-left: 4px solid ${this.getSeverityColor(issue.severity)}; padding: 10px; margin: 10px 0; background: #f8f9fa;">
99
+ <h3 style="margin-top: 0; color: ${this.getSeverityColor(issue.severity)};">${rule}</h3>
100
+ <p><strong>File:</strong> ${file}:${issue.line}</p>
101
+ <p>${message}</p>
102
+ ${suggestion ? `<p><strong>Suggested fix:</strong> ${suggestion}</p>` : ''}
103
+ </div>`;
104
+ }).join('');
105
+ contentHtml = `
106
+ <div class="summary">
107
+ <h2>Scan Summary</h2>
108
+ <p>Found <strong>${result.issuesFound}</strong> issues in <strong>${result.filesScanned}</strong> files</p>
109
+ </div>
110
+ ${issuesHtml}`;
111
+ }
112
+ return `<!DOCTYPE html>
113
+ <html>
82
114
  <head>
83
- <meta charset="UTF-8">
84
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
85
- <title>Vibe-Guard Security Report</title>
86
- <style>
87
- * {
88
- margin: 0;
89
- padding: 0;
90
- box-sizing: border-box;
91
- }
92
-
93
- body {
94
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
95
- line-height: 1.6;
96
- color: #333;
97
- background: #f5f5f5;
98
- padding: 2rem;
99
- }
100
-
101
- .container {
102
- max-width: 1200px;
103
- margin: 0 auto;
104
- background: white;
105
- border-radius: 8px;
106
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
107
- overflow: hidden;
108
- }
109
-
110
- .header {
111
- background: linear-gradient(135deg, #ff4757, #ff3838);
112
- color: white;
113
- padding: 2rem;
114
- text-align: center;
115
- }
116
-
117
- .header h1 {
118
- font-size: 2.5rem;
119
- margin-bottom: 0.5rem;
120
- }
121
-
122
- .header p {
123
- font-size: 1.1rem;
124
- opacity: 0.9;
125
- }
126
-
127
- .summary {
128
- padding: 2rem;
129
- background: #f8f9fa;
130
- border-bottom: 1px solid #e9ecef;
131
- }
132
-
133
- .summary-grid {
134
- display: grid;
135
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
136
- gap: 1rem;
137
- margin-top: 1rem;
138
- }
139
-
140
- .summary-card {
141
- background: white;
142
- padding: 1.5rem;
143
- border-radius: 8px;
144
- text-align: center;
145
- border-left: 4px solid;
146
- }
147
-
148
- .summary-card.critical { border-left-color: #dc3545; }
149
- .summary-card.high { border-left-color: #fd7e14; }
150
- .summary-card.medium { border-left-color: #ffc107; }
151
- .summary-card.low { border-left-color: #17a2b8; }
152
-
153
- .summary-card h3 {
154
- font-size: 2rem;
155
- margin-bottom: 0.5rem;
156
- }
157
-
158
- .summary-card.critical h3 { color: #dc3545; }
159
- .summary-card.high h3 { color: #fd7e14; }
160
- .summary-card.medium h3 { color: #ffc107; }
161
- .summary-card.low h3 { color: #17a2b8; }
162
-
163
- .issues {
164
- padding: 2rem;
165
- }
166
-
167
- .issue {
168
- background: white;
169
- border: 1px solid #e9ecef;
170
- border-radius: 8px;
171
- margin-bottom: 1rem;
172
- overflow: hidden;
173
- }
174
-
175
- .issue-header {
176
- padding: 1rem;
177
- border-bottom: 1px solid #e9ecef;
178
- display: flex;
179
- justify-content: space-between;
180
- align-items: center;
181
- }
182
-
183
- .issue-title {
184
- font-weight: 600;
185
- font-size: 1.1rem;
186
- }
187
-
188
- .severity {
189
- padding: 0.25rem 0.75rem;
190
- border-radius: 20px;
191
- font-size: 0.875rem;
192
- font-weight: 600;
193
- text-transform: uppercase;
194
- }
195
-
196
- .severity.critical { background: #dc3545; color: white; }
197
- .severity.high { background: #fd7e14; color: white; }
198
- .severity.medium { background: #ffc107; color: #212529; }
199
- .severity.low { background: #17a2b8; color: white; }
200
-
201
- .issue-content {
202
- padding: 1rem;
203
- }
204
-
205
- .issue-meta {
206
- display: grid;
207
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
208
- gap: 1rem;
209
- margin-bottom: 1rem;
210
- }
211
-
212
- .meta-item {
213
- background: #f8f9fa;
214
- padding: 0.75rem;
215
- border-radius: 4px;
216
- }
217
-
218
- .meta-label {
219
- font-size: 0.875rem;
220
- color: #6c757d;
221
- margin-bottom: 0.25rem;
222
- }
223
-
224
- .meta-value {
225
- font-weight: 600;
226
- font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
227
- }
228
-
229
- .code-snippet {
230
- background: #f8f9fa;
231
- border: 1px solid #e9ecef;
232
- border-radius: 4px;
233
- padding: 1rem;
234
- font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
235
- font-size: 0.875rem;
236
- overflow-x: auto;
237
- }
238
-
239
- .suggestion {
240
- background: #e7f3ff;
241
- border: 1px solid #b3d9ff;
242
- border-radius: 4px;
243
- padding: 1rem;
244
- margin-top: 1rem;
245
- }
246
-
247
- .suggestion h4 {
248
- color: #0066cc;
249
- margin-bottom: 0.5rem;
250
- }
251
-
252
- .footer {
253
- background: #f8f9fa;
254
- padding: 2rem;
255
- text-align: center;
256
- color: #6c757d;
257
- border-top: 1px solid #e9ecef;
258
- }
259
-
260
- @media (max-width: 768px) {
261
- body { padding: 1rem; }
262
- .header h1 { font-size: 2rem; }
263
- .issue-header { flex-direction: column; align-items: flex-start; gap: 0.5rem; }
264
- }
265
- </style>
115
+ <title>Vibe-Guard Security Scan Results</title>
116
+ <style>
117
+ body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; max-width: 1000px; margin: 0 auto; padding: 20px; }
118
+ h1 { color: #1e40af; }
119
+ .summary { background: #f0f9ff; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
120
+ </style>
266
121
  </head>
267
122
  <body>
268
- <div class="container">
269
- <div class="header">
270
- <h1>ā–ˆā–ˆ Vibe-Guard Security Report</h1>
271
- <p>Security scan completed on ${new Date().toLocaleDateString()}</p>
272
- </div>
273
-
274
- <div class="summary">
275
- <h2>Summary</h2>
276
- <div class="summary-grid">
277
- <div class="summary-card critical">
278
- <h3>${result.summary.critical}</h3>
279
- <p>Critical Issues</p>
280
- </div>
281
- <div class="summary-card high">
282
- <h3>${result.summary.high}</h3>
283
- <p>High Issues</p>
284
- </div>
285
- <div class="summary-card medium">
286
- <h3>${result.summary.medium}</h3>
287
- <p>Medium Issues</p>
288
- </div>
289
- <div class="summary-card low">
290
- <h3>${result.summary.low}</h3>
291
- <p>Low Issues</p>
292
- </div>
293
- </div>
294
- <p style="margin-top: 1rem; color: #6c757d;">
295
- Scanned ${result.filesScanned} files and found ${result.issuesFound} security issues
296
- </p>
297
- </div>
298
-
299
- <div class="issues">
300
- <h2>Security Issues</h2>
301
- ${result.issues.length === 0 ?
302
- '<div style="text-align: center; padding: 3rem; color: #28a745;"><h3>āœ… No security issues found!</h3><p>Your code looks secure.</p></div>' :
303
- result.issues.map(issue => this.formatHtmlIssue(issue)).join('')}
304
- </div>
305
-
306
- <div class="footer">
307
- <p>Generated by Vibe-Guard v1.1.5 | <a href="https://github.com/Devjosef/vibe-guard">GitHub</a></p>
308
- </div>
309
- </div>
123
+ <h1>Vibe-Guard Security Report</h1>
124
+ ${contentHtml}
310
125
  </body>
311
126
  </html>`;
312
- return html;
313
127
  }
314
- // Generates the SARIF rules
315
- generateSarifRules(result) {
316
- const ruleNames = [...new Set(result.issues.map(issue => issue.rule))];
317
- return ruleNames.map(ruleName => ({
318
- id: ruleName,
319
- name: ruleName,
320
- shortDescription: {
321
- text: `Security rule: ${ruleName}`
322
- }
323
- }));
324
- }
325
- // Generates the SARIF results
326
- generateSarifResults(result) {
327
- return result.issues.map(issue => ({
328
- ruleId: issue.rule,
329
- level: this.mapSeverityToSarifLevel(issue.severity),
330
- message: {
331
- text: issue.message
332
- },
333
- locations: [
334
- {
335
- physicalLocation: {
336
- artifactLocation: {
337
- uri: issue.file
338
- },
339
- region: {
340
- startLine: issue.line,
341
- startColumn: issue.column
342
- }
343
- }
344
- }
345
- ],
346
- properties: {
347
- suggestion: issue.suggestion
348
- }
349
- }));
350
- }
351
- // Maps the severity to the SARIF level
352
- mapSeverityToSarifLevel(severity) {
353
- switch (severity) {
354
- case 'critical':
355
- case 'high':
356
- return 'error';
357
- case 'medium':
358
- return 'warning';
359
- case 'low':
360
- return 'note';
361
- default:
362
- return 'note';
363
- }
364
- }
365
- // Formats the HTML issue
366
- formatHtmlIssue(issue) {
367
- return `
368
- <div class="issue">
369
- <div class="issue-header">
370
- <div class="issue-title">${this.escapeHtml(issue.rule)}</div>
371
- <div class="severity ${issue.severity}">${issue.severity.toUpperCase()}</div>
372
- </div>
373
- <div class="issue-content">
374
- <p><strong>${this.escapeHtml(issue.message)}</strong></p>
375
-
376
- <div class="issue-meta">
377
- <div class="meta-item">
378
- <div class="meta-label">File</div>
379
- <div class="meta-value">${this.escapeHtml(issue.file)}</div>
380
- </div>
381
- <div class="meta-item">
382
- <div class="meta-label">Line</div>
383
- <div class="meta-value">${issue.line}:${issue.column}</div>
384
- </div>
385
- <div class="meta-item">
386
- <div class="meta-label">Rule</div>
387
- <div class="meta-value">${this.escapeHtml(issue.rule)}</div>
388
- </div>
389
- </div>
390
-
391
- <div class="code-snippet">${this.escapeHtml(issue.code)}</div>
392
-
393
- <div class="suggestion">
394
- <h4>šŸ’” Suggestion</h4>
395
- <p>${this.escapeHtml(issue.suggestion)}</p>
396
- </div>
397
- </div>
398
- </div>
399
- `;
400
- }
401
- escapeHtml(text) {
402
- return text
128
+ formatIssueDetails(issue) {
129
+ const location = `${this.truncateFilePath(issue.file)}:${issue.line}${issue.column ? `:${issue.column}` : ''}`;
130
+ const fullMessage = `${issue.message}\n\n${issue.code}` +
131
+ (issue.suggestion ? `\nSuggested fix: ${issue.suggestion}` : '');
132
+ return `${chalk_1.default.bold.yellow(issue.rule)} ${this.colorSeverity(issue.severity)}\n` +
133
+ `${chalk_1.default.gray(`File: ${location}`)}\n\n` +
134
+ `${fullMessage}`;
135
+ }
136
+ colorSeverity(sev) {
137
+ switch (sev) {
138
+ case 'critical': return chalk_1.default.red.bold('CRITICAL');
139
+ case 'high': return chalk_1.default.red('HIGH');
140
+ case 'medium': return chalk_1.default.yellow('MEDIUM');
141
+ case 'low': return chalk_1.default.blue('LOW');
142
+ default: return String(sev).toUpperCase();
143
+ }
144
+ }
145
+ getSeverityColor(sev) {
146
+ switch (sev) {
147
+ case 'critical': return '#dc3545';
148
+ case 'high': return '#fd7e14';
149
+ case 'medium': return '#ffc107';
150
+ case 'low': return '#0d6efd';
151
+ default: return '#6c757d';
152
+ }
153
+ }
154
+ escapeHtml(value) {
155
+ return value
403
156
  .replace(/&/g, '&amp;')
404
157
  .replace(/</g, '&lt;')
405
158
  .replace(/>/g, '&gt;')
406
159
  .replace(/"/g, '&quot;')
407
- .replace(/'/g, '&#039;');
408
- }
409
- formatSuccess(result) {
410
- const header = chalk_1.default.green.bold('╔══════════════════════════════════════════════════════════════════════════════╗');
411
- const title = chalk_1.default.green.bold('ā•‘ VIBE-GUARD SECURITY SCAN COMPLETE ā•‘');
412
- const subtitle = chalk_1.default.green.bold('ā•‘ NO THREATS DETECTED ā•‘');
413
- const summary = chalk_1.default.green(`ā•‘ āœ… No security issues found in ${result.filesScanned} files`);
414
- const footer = chalk_1.default.green.bold('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•');
415
- return `${header}\n${title}\n${subtitle}\n${summary.padEnd(75)} ā•‘\n${footer}\n`;
416
- }
417
- formatHeader(result) {
418
- const header = chalk_1.default.red.bold('╔══════════════════════════════════════════════════════════════════════════════╗');
419
- const title = chalk_1.default.red.bold('ā•‘ VIBE-GUARD SECURITY ISSUES DETECTED ā•‘');
420
- const subtitle = chalk_1.default.yellow.bold(`ā•‘ ${result.issuesFound} THREATS FOUND IN ${result.filesScanned} FILES ā•‘`);
421
- const footer = chalk_1.default.red.bold('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•');
422
- return `${header}\n${title}\n${subtitle}\n${footer}\n`;
423
- }
424
- formatSummary(result) {
425
- const { summary } = result;
426
- const parts = [];
427
- if (summary.critical > 0) {
428
- parts.push(chalk_1.default.red.bold(`${summary.critical} CRITICAL`));
429
- }
430
- if (summary.high > 0) {
431
- parts.push(chalk_1.default.red(`${summary.high} HIGH`));
432
- }
433
- if (summary.medium > 0) {
434
- parts.push(chalk_1.default.yellow(`${summary.medium} MEDIUM`));
435
- }
436
- if (summary.low > 0) {
437
- parts.push(chalk_1.default.blue(`${summary.low} LOW`));
438
- }
439
- const summaryText = parts.length > 0 ? parts.join(' | ') : 'No issues';
440
- return chalk_1.default.bold('╔══════════════════════════════════════════════════════════════════════════════╗\n') +
441
- chalk_1.default.bold('ā•‘ SUMMARY ā•‘\n') +
442
- chalk_1.default.bold('ā•‘ ') + summaryText.padEnd(73) + chalk_1.default.bold('ā•‘\n') +
443
- chalk_1.default.bold('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n\n') +
444
- this.formatRecommendations();
445
- }
446
- formatRecommendations() {
447
- return chalk_1.default.cyan.bold('╔══════════════════════════════════════════════════════════════════════════════╗\n') +
448
- chalk_1.default.cyan.bold('ā•‘ RECOMMENDATIONS ā•‘\n') +
449
- chalk_1.default.cyan.bold('ā•‘ • Review and fix critical and high severity issues immediately').padEnd(73) + chalk_1.default.cyan.bold('ā•‘\n') +
450
- chalk_1.default.cyan.bold('ā•‘ • Consider implementing security linting in your CI/CD pipeline').padEnd(73) + chalk_1.default.cyan.bold('ā•‘\n') +
451
- chalk_1.default.cyan.bold('ā•‘ • Run Vibe-Guard regularly during development').padEnd(73) + chalk_1.default.cyan.bold('ā•‘\n') +
452
- chalk_1.default.cyan.bold('ā•‘ • Check our documentation for detailed fix suggestions').padEnd(73) + chalk_1.default.cyan.bold('ā•‘\n') +
453
- chalk_1.default.cyan.bold('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•');
454
- }
455
- // Colors the severity
456
- colorSeverity(severity) {
457
- switch (severity) {
458
- case 'critical':
459
- return chalk_1.default.red.bold('CRITICAL');
460
- case 'high':
461
- return chalk_1.default.red('HIGH');
462
- case 'medium':
463
- return chalk_1.default.yellow('MEDIUM');
464
- case 'low':
465
- return chalk_1.default.blue('LOW');
466
- default:
467
- return String(severity).toUpperCase();
468
- }
469
- }
470
- // Truncates the file path
471
- truncateFilePath(filePath, maxLength = 35) {
472
- let sanitizedPath = filePath.replace(/\\+/g, '/').replace(/\\/g, '/').replace(/\/+/g, '/');
473
- sanitizedPath = sanitizedPath.replace(/\.\./g, '').replace(/\/+/g, '/');
474
- sanitizedPath = sanitizedPath.replace(/^\/+/, '');
475
- if (sanitizedPath.length <= maxLength) {
476
- return sanitizedPath;
477
- }
478
- const parts = sanitizedPath.split('/');
479
- if (parts.length <= 2) {
480
- return sanitizedPath;
481
- }
482
- const first = parts[0];
483
- const last = parts[parts.length - 1];
484
- const truncated = `${first}/.../${last}`;
485
- if (truncated.length <= maxLength) {
486
- return truncated;
487
- }
488
- return '...' + sanitizedPath.slice(-(maxLength - 3));
489
- }
490
- formatIssueDetails(issue) {
491
- const header = chalk_1.default.red.bold(`\n🚨 ${issue.rule} (${this.colorSeverity(issue.severity)})`);
492
- const location = chalk_1.default.gray(`šŸ“ ${issue.file}:${issue.line}:${issue.column}`);
493
- const message = chalk_1.default.white(`šŸ’¬ ${issue.message}`);
494
- const code = chalk_1.default.gray(`šŸ“ Code: ${issue.code}`);
495
- const suggestion = chalk_1.default.green(`šŸ’” Suggestion: ${issue.suggestion}`);
496
- return `${header}\n${location}\n${message}\n${code}\n${suggestion}\n`;
160
+ .replace(/'/g, '&#39;');
497
161
  }
498
162
  }
499
163
  exports.Reporter = Reporter;