barrikade-lens 0.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/LICENSE +21 -0
- package/README.md +117 -0
- package/bin/cli.js +37 -0
- package/package.json +51 -0
- package/src/exporters/html-exporter.js +683 -0
- package/src/exporters/json-exporter.js +17 -0
- package/src/runner.js +189 -0
- package/src/scanners/config-auditor.js +550 -0
- package/src/scanners/env-scanner.js +72 -0
- package/src/scanners/history-scanner.js +107 -0
- package/src/scanners/port-scanner.js +129 -0
- package/src/scanners/process-scanner.js +103 -0
- package/src/scanners/secret-scanner.js +88 -0
- package/src/ui/banner.js +26 -0
- package/src/ui/dashboard.js +67 -0
- package/src/ui/summary.js +139 -0
- package/src/ui/tables.js +258 -0
- package/src/utils/analyzer.js +418 -0
- package/src/utils/paths.js +293 -0
- package/src/utils/patterns.js +148 -0
- package/src/utils/telemetry.js +217 -0
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { calculateRiskScore } from '../ui/summary.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Exports scan results as a self-contained, beautifully-styled HTML report.
|
|
6
|
+
*
|
|
7
|
+
* @param {any} results Aggregated scan results
|
|
8
|
+
* @param {string} outputPath File path to write the HTML report to
|
|
9
|
+
*/
|
|
10
|
+
export async function exportHtml(results, outputPath) {
|
|
11
|
+
const summary = results.summary;
|
|
12
|
+
const score = calculateRiskScore(summary.criticalCount, summary.highCount, summary.mediumCount);
|
|
13
|
+
const capabilities = results.capabilities;
|
|
14
|
+
|
|
15
|
+
let scoreClass = 'score-high';
|
|
16
|
+
let ratingStr = 'SECURE / LOW RISK';
|
|
17
|
+
if (score < 50) {
|
|
18
|
+
scoreClass = 'score-low';
|
|
19
|
+
ratingStr = 'CRITICAL SECURITY EXPOSURE';
|
|
20
|
+
} else if (score < 80) {
|
|
21
|
+
scoreClass = 'score-medium';
|
|
22
|
+
ratingStr = 'MODERATE RISK';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const activePorts = results.ports.filter(p => p.open);
|
|
26
|
+
const configsFound = results.configs.filter(c => c.exists);
|
|
27
|
+
|
|
28
|
+
const getHtmlBadgeClass = (domain, status) => {
|
|
29
|
+
if (domain === 'toolExecution') {
|
|
30
|
+
if (status === 'ACTIVE') return 'status-critical';
|
|
31
|
+
if (status === 'CAPABLE') return 'status-high';
|
|
32
|
+
return 'status-safe';
|
|
33
|
+
}
|
|
34
|
+
if (domain === 'localInference') {
|
|
35
|
+
if (status === 'ACTIVE') return 'status-high';
|
|
36
|
+
if (status === 'CAPABLE') return 'status-safe';
|
|
37
|
+
return 'status-safe';
|
|
38
|
+
}
|
|
39
|
+
if (domain === 'workspacePresence') {
|
|
40
|
+
if (status === 'DETECTED') return 'status-high';
|
|
41
|
+
return 'status-safe';
|
|
42
|
+
}
|
|
43
|
+
if (domain === 'credentialExposure') {
|
|
44
|
+
if (status === 'EXPOSED') return 'status-critical';
|
|
45
|
+
return 'status-safe';
|
|
46
|
+
}
|
|
47
|
+
return '';
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const getHtmlBadgeLabel = (domain, status) => {
|
|
51
|
+
if (domain === 'workspacePresence') {
|
|
52
|
+
return status === 'DETECTED' ? 'DETECTED' : 'NOT FOUND';
|
|
53
|
+
}
|
|
54
|
+
if (domain === 'credentialExposure') {
|
|
55
|
+
return status === 'EXPOSED' ? 'EXPOSED' : 'SECURE';
|
|
56
|
+
}
|
|
57
|
+
return status;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const html = `<!DOCTYPE html>
|
|
61
|
+
<html lang="en">
|
|
62
|
+
<head>
|
|
63
|
+
<meta charset="UTF-8">
|
|
64
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
65
|
+
<title>Barrikade Lens - Shadow AI Security Audit Report</title>
|
|
66
|
+
<style>
|
|
67
|
+
:root {
|
|
68
|
+
--bg: #0A0A0B;
|
|
69
|
+
--card-bg: #121214;
|
|
70
|
+
--text: #F4F4F5;
|
|
71
|
+
--text-muted: #A1A1AA;
|
|
72
|
+
--orange: #FF6600;
|
|
73
|
+
--orange-dim: #E05A00;
|
|
74
|
+
--border: #27272A;
|
|
75
|
+
--green: #10B981;
|
|
76
|
+
--red: #EF4444;
|
|
77
|
+
--yellow: #F59E0B;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
body {
|
|
81
|
+
background-color: var(--bg);
|
|
82
|
+
color: var(--text);
|
|
83
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
84
|
+
margin: 0;
|
|
85
|
+
padding: 0;
|
|
86
|
+
line-height: 1.5;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.container {
|
|
90
|
+
max-width: 1000px;
|
|
91
|
+
margin: 0 auto;
|
|
92
|
+
padding: 40px 20px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
header {
|
|
96
|
+
border-bottom: 1px solid var(--border);
|
|
97
|
+
padding-bottom: 20px;
|
|
98
|
+
margin-bottom: 40px;
|
|
99
|
+
display: flex;
|
|
100
|
+
justify-content: space-between;
|
|
101
|
+
align-items: center;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.logo-container h1 {
|
|
105
|
+
margin: 0;
|
|
106
|
+
font-size: 24px;
|
|
107
|
+
letter-spacing: 2px;
|
|
108
|
+
color: var(--text);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.logo-container h1 span {
|
|
112
|
+
color: var(--orange);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.logo-container p {
|
|
116
|
+
margin: 4px 0 0 0;
|
|
117
|
+
font-size: 14px;
|
|
118
|
+
color: var(--text-muted);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.badge {
|
|
122
|
+
background-color: var(--border);
|
|
123
|
+
color: var(--text);
|
|
124
|
+
padding: 6px 12px;
|
|
125
|
+
border-radius: 4px;
|
|
126
|
+
font-size: 12px;
|
|
127
|
+
font-weight: 600;
|
|
128
|
+
border: 1px solid var(--border);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.grid {
|
|
132
|
+
display: grid;
|
|
133
|
+
grid-template-columns: 2fr 1fr;
|
|
134
|
+
gap: 30px;
|
|
135
|
+
margin-bottom: 30px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.capability-grid {
|
|
139
|
+
display: grid;
|
|
140
|
+
grid-template-columns: repeat(2, 1fr);
|
|
141
|
+
gap: 20px;
|
|
142
|
+
margin-bottom: 30px;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@media (max-width: 768px) {
|
|
146
|
+
.grid, .capability-grid {
|
|
147
|
+
grid-template-columns: 1fr;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.card {
|
|
152
|
+
background-color: var(--card-bg);
|
|
153
|
+
border: 1px solid var(--border);
|
|
154
|
+
border-radius: 8px;
|
|
155
|
+
padding: 24px;
|
|
156
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.4);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.capability-card {
|
|
160
|
+
border: 1px solid var(--border);
|
|
161
|
+
border-radius: 8px;
|
|
162
|
+
background-color: var(--card-bg);
|
|
163
|
+
padding: 20px;
|
|
164
|
+
display: flex;
|
|
165
|
+
flex-direction: column;
|
|
166
|
+
justify-content: space-between;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.capability-header {
|
|
170
|
+
display: flex;
|
|
171
|
+
justify-content: space-between;
|
|
172
|
+
align-items: center;
|
|
173
|
+
margin-bottom: 12px;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.capability-title {
|
|
177
|
+
font-weight: 700;
|
|
178
|
+
font-size: 15px;
|
|
179
|
+
color: var(--text);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.capability-desc {
|
|
183
|
+
font-size: 13px;
|
|
184
|
+
color: var(--text-muted);
|
|
185
|
+
margin: 0;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.score-widget {
|
|
189
|
+
text-align: center;
|
|
190
|
+
display: flex;
|
|
191
|
+
flex-direction: column;
|
|
192
|
+
justify-content: center;
|
|
193
|
+
align-items: center;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.score-circle {
|
|
197
|
+
width: 140px;
|
|
198
|
+
height: 140px;
|
|
199
|
+
border-radius: 50%;
|
|
200
|
+
border: 8px solid var(--border);
|
|
201
|
+
display: flex;
|
|
202
|
+
justify-content: center;
|
|
203
|
+
align-items: center;
|
|
204
|
+
font-size: 42px;
|
|
205
|
+
font-weight: 800;
|
|
206
|
+
margin-bottom: 16px;
|
|
207
|
+
position: relative;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.score-high {
|
|
211
|
+
border-color: var(--green);
|
|
212
|
+
color: var(--green);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.score-medium {
|
|
216
|
+
border-color: var(--yellow);
|
|
217
|
+
color: var(--yellow);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.score-low {
|
|
221
|
+
border-color: var(--red);
|
|
222
|
+
color: var(--red);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.rating {
|
|
226
|
+
font-weight: 700;
|
|
227
|
+
font-size: 14px;
|
|
228
|
+
letter-spacing: 1px;
|
|
229
|
+
text-transform: uppercase;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
h2 {
|
|
233
|
+
font-size: 18px;
|
|
234
|
+
margin-top: 0;
|
|
235
|
+
margin-bottom: 20px;
|
|
236
|
+
border-left: 3px solid var(--orange);
|
|
237
|
+
padding-left: 10px;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.metric-row {
|
|
241
|
+
display: flex;
|
|
242
|
+
justify-content: space-between;
|
|
243
|
+
padding: 10px 0;
|
|
244
|
+
border-bottom: 1px solid var(--border);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.metric-row:last-child {
|
|
248
|
+
border-bottom: none;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.metric-label {
|
|
252
|
+
color: var(--text-muted);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.metric-val {
|
|
256
|
+
font-weight: 600;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
table {
|
|
260
|
+
width: 100%;
|
|
261
|
+
border-collapse: collapse;
|
|
262
|
+
margin-top: 10px;
|
|
263
|
+
margin-bottom: 30px;
|
|
264
|
+
font-size: 14px;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
th {
|
|
268
|
+
text-align: left;
|
|
269
|
+
padding: 12px;
|
|
270
|
+
border-bottom: 2px solid var(--border);
|
|
271
|
+
color: var(--orange);
|
|
272
|
+
font-weight: 600;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
td {
|
|
276
|
+
padding: 12px;
|
|
277
|
+
border-bottom: 1px solid var(--border);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
tr:last-child td {
|
|
281
|
+
border-bottom: none;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.status-badge {
|
|
285
|
+
display: inline-block;
|
|
286
|
+
padding: 2px 8px;
|
|
287
|
+
border-radius: 4px;
|
|
288
|
+
font-size: 11px;
|
|
289
|
+
font-weight: 700;
|
|
290
|
+
text-transform: uppercase;
|
|
291
|
+
white-space: nowrap;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.status-critical {
|
|
295
|
+
background-color: rgba(239, 68, 68, 0.15);
|
|
296
|
+
color: var(--red);
|
|
297
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.status-high {
|
|
301
|
+
background-color: rgba(245, 158, 11, 0.15);
|
|
302
|
+
color: var(--orange);
|
|
303
|
+
border: 1px solid rgba(245, 158, 11, 0.3);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.status-medium {
|
|
307
|
+
background-color: rgba(245, 158, 11, 0.1);
|
|
308
|
+
color: var(--yellow);
|
|
309
|
+
border: 1px solid rgba(245, 158, 11, 0.2);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.status-safe {
|
|
313
|
+
background-color: rgba(16, 185, 129, 0.15);
|
|
314
|
+
color: var(--green);
|
|
315
|
+
border: 1px solid rgba(16, 185, 129, 0.3);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.evidence-list {
|
|
319
|
+
margin: 0;
|
|
320
|
+
padding-left: 20px;
|
|
321
|
+
font-size: 14px;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.evidence-list li {
|
|
325
|
+
margin-bottom: 8px;
|
|
326
|
+
color: var(--text);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.recommendations {
|
|
330
|
+
margin-top: 30px;
|
|
331
|
+
background-color: #1A1A1E;
|
|
332
|
+
border: 1px solid var(--border);
|
|
333
|
+
border-radius: 8px;
|
|
334
|
+
padding: 24px;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.recommendations ol {
|
|
338
|
+
padding-left: 20px;
|
|
339
|
+
margin: 0;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.recommendations li {
|
|
343
|
+
margin-bottom: 12px;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.recommendations li:last-child {
|
|
347
|
+
margin-bottom: 0;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.cta-container {
|
|
351
|
+
margin-top: 40px;
|
|
352
|
+
background: linear-gradient(135deg, #1C1510 0%, #121214 100%);
|
|
353
|
+
border: 1px solid var(--orange);
|
|
354
|
+
border-radius: 8px;
|
|
355
|
+
padding: 30px;
|
|
356
|
+
text-align: center;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.cta-container h3 {
|
|
360
|
+
margin-top: 0;
|
|
361
|
+
color: var(--orange);
|
|
362
|
+
font-size: 20px;
|
|
363
|
+
letter-spacing: 1px;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.cta-btn {
|
|
367
|
+
display: inline-block;
|
|
368
|
+
background-color: var(--orange);
|
|
369
|
+
color: white;
|
|
370
|
+
text-decoration: none;
|
|
371
|
+
padding: 12px 28px;
|
|
372
|
+
border-radius: 4px;
|
|
373
|
+
font-weight: 700;
|
|
374
|
+
font-size: 15px;
|
|
375
|
+
margin-top: 15px;
|
|
376
|
+
transition: background-color 0.2s;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.cta-btn:hover {
|
|
380
|
+
background-color: var(--orange-dim);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.empty-state {
|
|
384
|
+
color: var(--text-muted);
|
|
385
|
+
text-align: center;
|
|
386
|
+
padding: 20px 0;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.remediation-tip {
|
|
390
|
+
font-size: 12px;
|
|
391
|
+
color: var(--text-muted);
|
|
392
|
+
margin-top: 4px;
|
|
393
|
+
}
|
|
394
|
+
</style>
|
|
395
|
+
</head>
|
|
396
|
+
<body>
|
|
397
|
+
<div class="container">
|
|
398
|
+
<header>
|
|
399
|
+
<div class="logo-container">
|
|
400
|
+
<h1>BARRIKADE <span>LENS</span></h1>
|
|
401
|
+
<p>Shadow AI Workstation Vulnerability Report</p>
|
|
402
|
+
</div>
|
|
403
|
+
<div class="badge">🔒 100% LOCALLY AUDITED</div>
|
|
404
|
+
</header>
|
|
405
|
+
|
|
406
|
+
<div class="grid">
|
|
407
|
+
<div class="card">
|
|
408
|
+
<h2>Executive Summary</h2>
|
|
409
|
+
<div class="metric-row">
|
|
410
|
+
<span class="metric-label">Audit Timestamp</span>
|
|
411
|
+
<span class="metric-val">${new Date().toLocaleString()}</span>
|
|
412
|
+
</div>
|
|
413
|
+
<div class="metric-row">
|
|
414
|
+
<span class="metric-label">Operating System</span>
|
|
415
|
+
<span class="metric-val">${results.platform.toUpperCase()}</span>
|
|
416
|
+
</div>
|
|
417
|
+
<div class="metric-row">
|
|
418
|
+
<span class="metric-label">Discovered AI Agents</span>
|
|
419
|
+
<span class="metric-val">${summary.agentsCount || 0} (${summary.agentsActive || 0} active, ${summary.agentsInstalled || 0} installed)</span>
|
|
420
|
+
</div>
|
|
421
|
+
<div class="metric-row">
|
|
422
|
+
<span class="metric-label">Config Files Found</span>
|
|
423
|
+
<span class="metric-val">${summary.configsCount}</span>
|
|
424
|
+
</div>
|
|
425
|
+
<div class="metric-row">
|
|
426
|
+
<span class="metric-label">Active Agent Connections (MCP)</span>
|
|
427
|
+
<span class="metric-val">${summary.serversCount}</span>
|
|
428
|
+
</div>
|
|
429
|
+
<div class="metric-row">
|
|
430
|
+
<span class="metric-label">Exposed secrets or configurations</span>
|
|
431
|
+
<span class="metric-val" style="color: ${summary.secretsCount > 0 ? 'var(--orange)' : 'var(--green)'}">${summary.secretsCount}</span>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
|
|
435
|
+
<div class="card score-widget">
|
|
436
|
+
<div class="score-circle ${scoreClass}">${score}</div>
|
|
437
|
+
<div class="rating ${scoreClass}">${ratingStr}</div>
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
440
|
+
|
|
441
|
+
<!-- Capability Grid -->
|
|
442
|
+
<h2 style="margin-bottom: 20px;">Autonomous AI Capability Evaluation</h2>
|
|
443
|
+
<div class="capability-grid">
|
|
444
|
+
<div class="capability-card">
|
|
445
|
+
<div class="capability-header">
|
|
446
|
+
<span class="capability-title">Tool & Code Execution</span>
|
|
447
|
+
<span class="status-badge ${getHtmlBadgeClass('toolExecution', capabilities.toolExecution.status)}">
|
|
448
|
+
${getHtmlBadgeLabel('toolExecution', capabilities.toolExecution.status)}
|
|
449
|
+
</span>
|
|
450
|
+
</div>
|
|
451
|
+
<p class="capability-desc">${capabilities.toolExecution.detail}</p>
|
|
452
|
+
</div>
|
|
453
|
+
|
|
454
|
+
<div class="capability-card">
|
|
455
|
+
<div class="capability-header">
|
|
456
|
+
<span class="capability-title">Local Model Inference</span>
|
|
457
|
+
<span class="status-badge ${getHtmlBadgeClass('localInference', capabilities.localInference.status)}">
|
|
458
|
+
${getHtmlBadgeLabel('localInference', capabilities.localInference.status)}
|
|
459
|
+
</span>
|
|
460
|
+
</div>
|
|
461
|
+
<p class="capability-desc">${capabilities.localInference.detail}</p>
|
|
462
|
+
</div>
|
|
463
|
+
|
|
464
|
+
<div class="capability-card">
|
|
465
|
+
<div class="capability-header">
|
|
466
|
+
<span class="capability-title">Agent Workspace Presence</span>
|
|
467
|
+
<span class="status-badge ${getHtmlBadgeClass('workspacePresence', capabilities.workspacePresence.status)}">
|
|
468
|
+
${getHtmlBadgeLabel('workspacePresence', capabilities.workspacePresence.status)}
|
|
469
|
+
</span>
|
|
470
|
+
</div>
|
|
471
|
+
<p class="capability-desc">${capabilities.workspacePresence.detail}</p>
|
|
472
|
+
</div>
|
|
473
|
+
|
|
474
|
+
<div class="capability-card">
|
|
475
|
+
<div class="capability-header">
|
|
476
|
+
<span class="capability-title">Credential Security</span>
|
|
477
|
+
<span class="status-badge ${getHtmlBadgeClass('credentialExposure', capabilities.credentialExposure.status)}">
|
|
478
|
+
${getHtmlBadgeLabel('credentialExposure', capabilities.credentialExposure.status)}
|
|
479
|
+
</span>
|
|
480
|
+
</div>
|
|
481
|
+
<p class="capability-desc">${capabilities.credentialExposure.detail}</p>
|
|
482
|
+
</div>
|
|
483
|
+
</div>
|
|
484
|
+
|
|
485
|
+
<!-- Discovered Agents Card -->
|
|
486
|
+
<div class="card" style="margin-bottom: 30px;">
|
|
487
|
+
<h2>Discovered Shadow AI Agents Inventory</h2>
|
|
488
|
+
${!results.agents || results.agents.length === 0 ? `
|
|
489
|
+
<div class="empty-state">No AI agents or tools discovered on this workstation.</div>
|
|
490
|
+
` : `
|
|
491
|
+
<table>
|
|
492
|
+
<thead>
|
|
493
|
+
<tr>
|
|
494
|
+
<th>Agent / Tool Name</th>
|
|
495
|
+
<th>Status</th>
|
|
496
|
+
<th>Supporting Evidence</th>
|
|
497
|
+
</tr>
|
|
498
|
+
</thead>
|
|
499
|
+
<tbody>
|
|
500
|
+
${[...results.agents].sort((a, b) => {
|
|
501
|
+
if (a.status === 'ACTIVE' && b.status !== 'ACTIVE') return -1;
|
|
502
|
+
if (a.status !== 'ACTIVE' && b.status === 'ACTIVE') return 1;
|
|
503
|
+
return a.name.localeCompare(b.name);
|
|
504
|
+
}).map(agent => {
|
|
505
|
+
const statusClass = agent.status === 'ACTIVE' ? 'status-critical' : 'status-safe';
|
|
506
|
+
const cleanEvidence = agent.evidence.map(e => {
|
|
507
|
+
const idx = e.indexOf(':');
|
|
508
|
+
return idx !== -1 ? e.substring(0, idx).trim() : e;
|
|
509
|
+
});
|
|
510
|
+
const uniqueEvidence = Array.from(new Set(cleanEvidence)).join(', ');
|
|
511
|
+
return `
|
|
512
|
+
<tr>
|
|
513
|
+
<td><strong>${agent.name}</strong></td>
|
|
514
|
+
<td><span class="status-badge ${statusClass}">${agent.status}</span></td>
|
|
515
|
+
<td>${uniqueEvidence}</td>
|
|
516
|
+
</tr>
|
|
517
|
+
`;
|
|
518
|
+
}).join('')}
|
|
519
|
+
</tbody>
|
|
520
|
+
</table>
|
|
521
|
+
`}
|
|
522
|
+
</div>
|
|
523
|
+
|
|
524
|
+
<!-- Audit Evidence collected -->
|
|
525
|
+
<div class="card" style="margin-bottom: 30px;">
|
|
526
|
+
<h2>Collected Audit Evidence</h2>
|
|
527
|
+
${results.evidence.length === 0 ? `
|
|
528
|
+
<div class="empty-state">No agent infrastructure or evidence detected on this workstation.</div>
|
|
529
|
+
` : `
|
|
530
|
+
<ul class="evidence-list">
|
|
531
|
+
${results.evidence.map(e => `<li>${e}</li>`).join('')}
|
|
532
|
+
</ul>
|
|
533
|
+
`}
|
|
534
|
+
</div>
|
|
535
|
+
|
|
536
|
+
<!-- Active Configurations -->
|
|
537
|
+
<div class="card" style="margin-bottom: 30px;">
|
|
538
|
+
<h2>Active Agent & MCP Configurations</h2>
|
|
539
|
+
${configsFound.length === 0 ? `
|
|
540
|
+
<div class="empty-state">No agent config files discovered.</div>
|
|
541
|
+
` : `
|
|
542
|
+
<table>
|
|
543
|
+
<thead>
|
|
544
|
+
<tr>
|
|
545
|
+
<th>Tool / Client</th>
|
|
546
|
+
<th>Scope</th>
|
|
547
|
+
<th>Server Name</th>
|
|
548
|
+
<th>Type</th>
|
|
549
|
+
<th>Command / Endpoint</th>
|
|
550
|
+
</tr>
|
|
551
|
+
</thead>
|
|
552
|
+
<tbody>
|
|
553
|
+
${configsFound.map(c => {
|
|
554
|
+
if (c.servers.length === 0) {
|
|
555
|
+
return `<tr>
|
|
556
|
+
<td><strong>${c.tool}</strong></td>
|
|
557
|
+
<td>${c.scope}</td>
|
|
558
|
+
<td colspan="3" style="color: var(--text-muted); font-style: italic;">No servers configured</td>
|
|
559
|
+
</tr>`;
|
|
560
|
+
}
|
|
561
|
+
return c.servers.map(s => `
|
|
562
|
+
<tr>
|
|
563
|
+
<td><strong>${c.tool}</strong></td>
|
|
564
|
+
<td>${c.scope}</td>
|
|
565
|
+
<td>${s.disabled ? `<span style="text-decoration: line-through; opacity: 0.6;">${s.name}</span>` : s.name}</td>
|
|
566
|
+
<td>${s.type.toUpperCase()}</td>
|
|
567
|
+
<td><code>${s.type === 'sse' ? (s.url || '-') : `${s.command || '-'} ${(s.args || []).join(' ')}`}</code></td>
|
|
568
|
+
</tr>
|
|
569
|
+
`).join('');
|
|
570
|
+
}).join('')}
|
|
571
|
+
</tbody>
|
|
572
|
+
</table>
|
|
573
|
+
`}
|
|
574
|
+
</div>
|
|
575
|
+
|
|
576
|
+
<!-- Port Sweep -->
|
|
577
|
+
<div class="card" style="margin-bottom: 30px;">
|
|
578
|
+
<h2>Local Inference Server Status</h2>
|
|
579
|
+
${activePorts.length === 0 ? `
|
|
580
|
+
<div class="empty-state" style="color: var(--green);">✔ No local model inference servers active.</div>
|
|
581
|
+
` : `
|
|
582
|
+
<table>
|
|
583
|
+
<thead>
|
|
584
|
+
<tr>
|
|
585
|
+
<th>Port</th>
|
|
586
|
+
<th>Service</th>
|
|
587
|
+
<th>Status</th>
|
|
588
|
+
<th>Binding</th>
|
|
589
|
+
<th>Risk Level</th>
|
|
590
|
+
</tr>
|
|
591
|
+
</thead>
|
|
592
|
+
<tbody>
|
|
593
|
+
${activePorts.map(p => `
|
|
594
|
+
<tr>
|
|
595
|
+
<td><strong>${p.port}</strong></td>
|
|
596
|
+
<td>${p.service}</td>
|
|
597
|
+
<td><span class="status-badge status-safe">Running</span></td>
|
|
598
|
+
<td><code>${p.binding}</code></td>
|
|
599
|
+
<td>
|
|
600
|
+
${p.exposed ? `
|
|
601
|
+
<span class="status-badge status-critical">Critical (Exposed to LAN)</span>
|
|
602
|
+
` : `
|
|
603
|
+
<span class="status-badge status-safe">Safe (Local only)</span>
|
|
604
|
+
`}
|
|
605
|
+
</td>
|
|
606
|
+
</tr>
|
|
607
|
+
`).join('')}
|
|
608
|
+
</tbody>
|
|
609
|
+
</table>
|
|
610
|
+
`}
|
|
611
|
+
</div>
|
|
612
|
+
|
|
613
|
+
<!-- Security Findings -->
|
|
614
|
+
<div class="card" style="margin-bottom: 30px;">
|
|
615
|
+
<h2>Credential & Risk Findings</h2>
|
|
616
|
+
${results.secrets.length === 0 ? `
|
|
617
|
+
<div class="empty-state" style="color: var(--green);">✔ No plaintext secrets or insecure configuration flags detected.</div>
|
|
618
|
+
` : `
|
|
619
|
+
<table>
|
|
620
|
+
<thead>
|
|
621
|
+
<tr>
|
|
622
|
+
<th>Severity</th>
|
|
623
|
+
<th>Source / Location</th>
|
|
624
|
+
<th>Risk Type</th>
|
|
625
|
+
<th>Pattern / Detail</th>
|
|
626
|
+
<th>Line</th>
|
|
627
|
+
</tr>
|
|
628
|
+
</thead>
|
|
629
|
+
<tbody>
|
|
630
|
+
${results.secrets.map(s => {
|
|
631
|
+
let sevClass = 'status-medium';
|
|
632
|
+
if (s.risk === 'CRITICAL') sevClass = 'status-critical';
|
|
633
|
+
else if (s.risk === 'HIGH') sevClass = 'status-high';
|
|
634
|
+
|
|
635
|
+
return `
|
|
636
|
+
<tr>
|
|
637
|
+
<td><span class="status-badge ${sevClass}">${s.risk}</span></td>
|
|
638
|
+
<td><strong>${s.tool}</strong><br><span style="font-size: 11px; color: var(--text-muted);">${s.filePath.split('/').pop()}</span></td>
|
|
639
|
+
<td>${s.type}</td>
|
|
640
|
+
<td>
|
|
641
|
+
<code style="color: var(--red);">${s.matched}</code>
|
|
642
|
+
<div class="remediation-tip">💡 ${s.remediation}</div>
|
|
643
|
+
</td>
|
|
644
|
+
<td>${s.line || 'N/A'}</td>
|
|
645
|
+
</tr>
|
|
646
|
+
`;
|
|
647
|
+
}).join('')}
|
|
648
|
+
</tbody>
|
|
649
|
+
</table>
|
|
650
|
+
`}
|
|
651
|
+
</div>
|
|
652
|
+
|
|
653
|
+
<!-- Action items -->
|
|
654
|
+
<div class="recommendations">
|
|
655
|
+
<h2 style="border-left: none; padding-left: 0; color: var(--orange); margin-bottom: 15px;">🔒 Immediate Remediation Checklist</h2>
|
|
656
|
+
<ol>
|
|
657
|
+
${summary.portsExposed > 0 ? `
|
|
658
|
+
<li><strong>Secure Local AI Binding:</strong> Edit your Ollama/LM Studio configurations to bind solely to <code>127.0.0.1</code>. This blocks lateral access from other devices on the LAN.</li>
|
|
659
|
+
` : ''}
|
|
660
|
+
${summary.secretsCount > 0 ? `
|
|
661
|
+
<li><strong>Vault Plaintext Secrets:</strong> Remove hardcoded credentials from <code>mcp.json</code> or other tool settings. Switch to environment variable interpolation (e.g. <code>\${env:OPENAI_API_KEY}</code>) which resolves secrets dynamically at runtime.</li>
|
|
662
|
+
` : ''}
|
|
663
|
+
<li><strong>Enforce Command Execution Checks:</strong> Verify that Brave Mode is disabled in IDE MCP settings, and review auto-approval configuration lists.</li>
|
|
664
|
+
<li><strong>Establish Central Lifecycle Governance:</strong> Ensure team members follow structured governance practices for local agent toolchains to avoid lateral network exposure.</li>
|
|
665
|
+
</ol>
|
|
666
|
+
</div>
|
|
667
|
+
|
|
668
|
+
<!-- CTA Box -->
|
|
669
|
+
<div class="cta-container">
|
|
670
|
+
<h3>PROTECT YOUR FLEET WITH BARRIKADE</h3>
|
|
671
|
+
<p style="color: var(--text-muted); max-width: 700px; margin: 10px auto;">
|
|
672
|
+
Local sweeps secure individual machines. But enterprise environments need continuous visibility.
|
|
673
|
+
Barrikade Enterprise offers automated fleet monitoring, centralized credentials brokering,
|
|
674
|
+
runtime guardrails, and compliance audits for developers' local AI assistant ecosystems.
|
|
675
|
+
</p>
|
|
676
|
+
<a href="https://barrikade.ai" class="cta-btn" target="_blank">Book a Demo at Barrikade.ai</a>
|
|
677
|
+
</div>
|
|
678
|
+
</div>
|
|
679
|
+
</body>
|
|
680
|
+
</html>`;
|
|
681
|
+
|
|
682
|
+
await fs.writeFile(outputPath, html, 'utf8');
|
|
683
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Exports scan results as a JSON structure.
|
|
5
|
+
*
|
|
6
|
+
* @param {any} results Aggregated scan results
|
|
7
|
+
* @param {string} [outputPath] Optional file path to write the JSON to
|
|
8
|
+
*/
|
|
9
|
+
export async function exportJson(results, outputPath) {
|
|
10
|
+
const jsonString = JSON.stringify(results, null, 2);
|
|
11
|
+
|
|
12
|
+
if (outputPath) {
|
|
13
|
+
await fs.writeFile(outputPath, jsonString, 'utf8');
|
|
14
|
+
} else {
|
|
15
|
+
console.log(jsonString);
|
|
16
|
+
}
|
|
17
|
+
}
|