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.
- package/README.md +38 -74
- package/dist/bin/vibe-guard.js +93 -264
- package/dist/bin/vibe-guard.js.map +1 -1
- package/dist/index.d.ts +4 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -69
- package/dist/index.js.map +1 -1
- package/dist/reporter.d.ts +4 -11
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +125 -461
- package/dist/reporter.js.map +1 -1
- package/dist/rules/exposed-secrets.d.ts.map +1 -1
- package/dist/rules/exposed-secrets.js +27 -8
- package/dist/rules/exposed-secrets.js.map +1 -1
- package/dist/rules/hardcoded-sensitive-data.js +1 -1
- package/dist/rules/hardcoded-sensitive-data.js.map +1 -1
- package/dist/rules/index.d.ts +3 -1
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +7 -1
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/insecure-deserialization.js +1 -1
- package/dist/rules/insecure-deserialization.js.map +1 -1
- package/dist/rules/insecure-error-handling.js +1 -1
- package/dist/rules/insecure-error-handling.js.map +1 -1
- package/dist/rules/insecure-file-upload.js +1 -1
- package/dist/rules/insecure-file-upload.js.map +1 -1
- package/dist/rules/insecure-http.js +1 -1
- package/dist/rules/insecure-http.js.map +1 -1
- package/dist/rules/insecure-logging.js +1 -1
- package/dist/rules/insecure-logging.js.map +1 -1
- package/dist/rules/kubernetes-security.js +3 -3
- package/dist/rules/kubernetes-security.js.map +1 -1
- package/dist/rules/unpinned-supply-chain-dependencies.d.ts +12 -0
- package/dist/rules/unpinned-supply-chain-dependencies.d.ts.map +1 -0
- package/dist/rules/unpinned-supply-chain-dependencies.js +56 -0
- package/dist/rules/unpinned-supply-chain-dependencies.js.map +1 -0
- package/dist/rules/unsafe-llm-prompt-construction.d.ts +9 -0
- package/dist/rules/unsafe-llm-prompt-construction.d.ts.map +1 -0
- package/dist/rules/unsafe-llm-prompt-construction.js +31 -0
- package/dist/rules/unsafe-llm-prompt-construction.js.map +1 -0
- package/dist/rules/xss-detection.js +1 -1
- package/dist/rules/xss-detection.js.map +1 -1
- package/dist/scanner.d.ts +1 -2
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +43 -116
- package/dist/scanner.js.map +1 -1
- package/dist/types/version.d.ts +1 -1
- package/dist/types/version.js +1 -1
- 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
|
-
|
|
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
|
|
13
|
+
return chalk_1.default.green(`No security issues found in ${result.filesScanned} files`);
|
|
15
14
|
}
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
$schema: "https://schemastore.
|
|
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:
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
81
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
269
|
-
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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, '&')
|
|
404
157
|
.replace(/</g, '<')
|
|
405
158
|
.replace(/>/g, '>')
|
|
406
159
|
.replace(/"/g, '"')
|
|
407
|
-
.replace(/'/g, '&#
|
|
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, ''');
|
|
497
161
|
}
|
|
498
162
|
}
|
|
499
163
|
exports.Reporter = Reporter;
|