@voidagency/web-scanner 0.0.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.
- package/README.md +198 -0
- package/dist/aggregator.d.ts +65 -0
- package/dist/aggregator.d.ts.map +1 -0
- package/dist/aggregator.js +546 -0
- package/dist/aggregator.js.map +1 -0
- package/dist/categories.d.ts +59 -0
- package/dist/categories.d.ts.map +1 -0
- package/dist/categories.js +278 -0
- package/dist/categories.js.map +1 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +457 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +121 -0
- package/dist/config.js.map +1 -0
- package/dist/coverage.d.ts +49 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +165 -0
- package/dist/coverage.js.map +1 -0
- package/dist/enrichers/nvd.d.ts +55 -0
- package/dist/enrichers/nvd.d.ts.map +1 -0
- package/dist/enrichers/nvd.js +326 -0
- package/dist/enrichers/nvd.js.map +1 -0
- package/dist/report.d.ts +12 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +460 -0
- package/dist/report.js.map +1 -0
- package/dist/runners/nuclei.d.ts +59 -0
- package/dist/runners/nuclei.d.ts.map +1 -0
- package/dist/runners/nuclei.js +531 -0
- package/dist/runners/nuclei.js.map +1 -0
- package/dist/runners/testssl.d.ts +16 -0
- package/dist/runners/testssl.d.ts.map +1 -0
- package/dist/runners/testssl.js +179 -0
- package/dist/runners/testssl.js.map +1 -0
- package/dist/runners/zap.d.ts +30 -0
- package/dist/runners/zap.d.ts.map +1 -0
- package/dist/runners/zap.js +389 -0
- package/dist/runners/zap.js.map +1 -0
- package/dist/types.d.ts +172 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +54 -0
- package/templates/drupal-api-index-exposed.yaml +81 -0
- package/templates/drupal-api-user-detail.yaml +76 -0
- package/templates/drupal-api-user-listing.yaml +59 -0
- package/templates/drupal-dev-files-exposed.yaml +73 -0
- package/templates/drupal-file-path-disclosure.yaml +59 -0
- package/templates/drupal-files-listing.yaml +63 -0
- package/templates/drupal-install-error-disclosure.yaml +62 -0
- package/templates/drupal-theme-lockfiles.yaml +79 -0
- package/templates/drupal-version-detect.yaml +89 -0
- package/templates/http-options-enabled.yaml +56 -0
- package/templates/nextjs-version-detect.yaml +35 -0
- package/templates/php-version-detect.yaml +37 -0
- package/zap.yaml +33 -0
package/dist/report.js
ADDED
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Report Generator
|
|
3
|
+
* Generate HTML reports from scan results
|
|
4
|
+
* Uses Tailwind CSS + Alpine.js for styling and interactivity
|
|
5
|
+
*/
|
|
6
|
+
import Handlebars from 'handlebars';
|
|
7
|
+
// Register Handlebars helpers
|
|
8
|
+
Handlebars.registerHelper('severityClass', (severity) => {
|
|
9
|
+
const classes = {
|
|
10
|
+
critical: 'bg-red-600 text-white',
|
|
11
|
+
high: 'bg-orange-500 text-white',
|
|
12
|
+
medium: 'bg-yellow-500 text-black',
|
|
13
|
+
low: 'bg-blue-600 text-white',
|
|
14
|
+
info: 'bg-gray-500 text-white',
|
|
15
|
+
};
|
|
16
|
+
return classes[severity];
|
|
17
|
+
});
|
|
18
|
+
Handlebars.registerHelper('severityBadge', (severity) => {
|
|
19
|
+
return severity.toUpperCase();
|
|
20
|
+
});
|
|
21
|
+
Handlebars.registerHelper('severityBorder', (severity) => {
|
|
22
|
+
const borders = {
|
|
23
|
+
critical: 'border-l-red-600',
|
|
24
|
+
high: 'border-l-orange-500',
|
|
25
|
+
medium: 'border-l-yellow-500',
|
|
26
|
+
low: 'border-l-blue-600',
|
|
27
|
+
info: 'border-l-gray-500',
|
|
28
|
+
};
|
|
29
|
+
return borders[severity];
|
|
30
|
+
});
|
|
31
|
+
Handlebars.registerHelper('truncate', (str, len) => {
|
|
32
|
+
if (!str)
|
|
33
|
+
return '';
|
|
34
|
+
if (str.length <= len)
|
|
35
|
+
return str;
|
|
36
|
+
return str.substring(0, len) + '...';
|
|
37
|
+
});
|
|
38
|
+
Handlebars.registerHelper('formatDate', (isoDate) => {
|
|
39
|
+
return new Date(isoDate).toLocaleDateString('en-US', {
|
|
40
|
+
year: 'numeric',
|
|
41
|
+
month: 'short',
|
|
42
|
+
day: 'numeric',
|
|
43
|
+
hour: '2-digit',
|
|
44
|
+
minute: '2-digit',
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
Handlebars.registerHelper('ifCond', function (v1, v2, options) {
|
|
48
|
+
if (v1 === v2) {
|
|
49
|
+
return options.fn(this);
|
|
50
|
+
}
|
|
51
|
+
return options.inverse(this);
|
|
52
|
+
});
|
|
53
|
+
// HTML Template with Tailwind + Alpine.js
|
|
54
|
+
const HTML_TEMPLATE = `<!DOCTYPE html>
|
|
55
|
+
<html lang="en" x-data="{ darkMode: localStorage.getItem('darkMode') !== 'false' }"
|
|
56
|
+
x-init="$watch('darkMode', val => localStorage.setItem('darkMode', val))"
|
|
57
|
+
:class="{ 'dark': darkMode }">
|
|
58
|
+
<head>
|
|
59
|
+
<meta charset="UTF-8">
|
|
60
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
61
|
+
<title>Security Scan Report - {{target}}</title>
|
|
62
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
63
|
+
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
|
64
|
+
<script>
|
|
65
|
+
tailwind.config = {
|
|
66
|
+
darkMode: 'class',
|
|
67
|
+
theme: {
|
|
68
|
+
extend: {
|
|
69
|
+
colors: {
|
|
70
|
+
severity: {
|
|
71
|
+
critical: '#dc2626',
|
|
72
|
+
high: '#ea580c',
|
|
73
|
+
medium: '#ca8a04',
|
|
74
|
+
low: '#2563eb',
|
|
75
|
+
info: '#6b7280',
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
</script>
|
|
82
|
+
<style>
|
|
83
|
+
[x-cloak] { display: none !important; }
|
|
84
|
+
.gauge-arc { transition: stroke-dashoffset 0.5s ease; }
|
|
85
|
+
</style>
|
|
86
|
+
</head>
|
|
87
|
+
<body class="bg-slate-50 dark:bg-slate-900 text-slate-900 dark:text-gray-100 min-h-screen transition-colors duration-200">
|
|
88
|
+
<div class="max-w-6xl mx-auto px-4 py-8">
|
|
89
|
+
|
|
90
|
+
<!-- Header -->
|
|
91
|
+
<header class="text-center pb-6 mb-8 border-b border-slate-200 dark:border-slate-700">
|
|
92
|
+
<div class="flex justify-end mb-4">
|
|
93
|
+
<button @click="darkMode = !darkMode"
|
|
94
|
+
class="p-2 rounded-lg bg-slate-200 dark:bg-slate-700 hover:bg-slate-300 dark:hover:bg-slate-600 transition-colors">
|
|
95
|
+
<span x-show="!darkMode">🌙</span>
|
|
96
|
+
<span x-show="darkMode" x-cloak>☀️</span>
|
|
97
|
+
</button>
|
|
98
|
+
</div>
|
|
99
|
+
<h1 class="text-3xl font-bold mb-2 text-slate-900 dark:text-white">Security Scan Report</h1>
|
|
100
|
+
<p class="text-slate-600 dark:text-gray-400 text-lg font-mono">{{target}}</p>
|
|
101
|
+
</header>
|
|
102
|
+
|
|
103
|
+
<!-- Summary Section -->
|
|
104
|
+
<section class="bg-white dark:bg-slate-800 rounded-xl shadow-sm p-6 mb-8 border border-slate-200 dark:border-slate-700">
|
|
105
|
+
<h2 class="text-sm font-bold text-slate-500 dark:text-gray-400 uppercase tracking-wide mb-6">Summary</h2>
|
|
106
|
+
|
|
107
|
+
<!-- Severity Gauges -->
|
|
108
|
+
<div class="grid grid-cols-5 gap-4 mb-6">
|
|
109
|
+
{{#each severityGauges}}
|
|
110
|
+
<div class="flex flex-col items-center {{#if this.highlight}}ring-2 ring-offset-2 dark:ring-offset-slate-800 {{this.ringColor}} rounded-xl p-4 relative{{/if}}">
|
|
111
|
+
{{#if this.highlight}}
|
|
112
|
+
<span class="absolute -top-3 left-1/2 -translate-x-1/2 {{this.labelBg}} text-white text-xs font-bold px-3 py-1 rounded-full whitespace-nowrap shadow-sm">
|
|
113
|
+
Overall risk level
|
|
114
|
+
</span>
|
|
115
|
+
{{/if}}
|
|
116
|
+
<svg viewBox="0 0 100 60" class="w-24 h-14">
|
|
117
|
+
<path d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke-width="8" stroke-linecap="round"
|
|
118
|
+
class="stroke-slate-200 dark:stroke-slate-600" />
|
|
119
|
+
<path d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke-width="8" stroke-linecap="round"
|
|
120
|
+
class="gauge-arc {{this.strokeColor}}" stroke-dasharray="126" stroke-dashoffset="{{this.offset}}" />
|
|
121
|
+
</svg>
|
|
122
|
+
<span class="text-2xl font-bold mt-1 text-slate-900 dark:text-white">{{this.count}}</span>
|
|
123
|
+
<span class="text-xs font-medium text-slate-500 dark:text-gray-400 capitalize">{{this.label}}</span>
|
|
124
|
+
</div>
|
|
125
|
+
{{/each}}
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<!-- Scan Metadata -->
|
|
129
|
+
<div class="grid grid-cols-4 gap-4 pt-6 border-t border-slate-200 dark:border-slate-700">
|
|
130
|
+
<div>
|
|
131
|
+
<label class="text-xs font-medium text-slate-500 dark:text-gray-400 block uppercase">Start time</label>
|
|
132
|
+
<span class="font-medium text-slate-900 dark:text-gray-200">{{formatDate scanDate}}</span>
|
|
133
|
+
</div>
|
|
134
|
+
<div>
|
|
135
|
+
<label class="text-xs font-medium text-slate-500 dark:text-gray-400 block uppercase">Duration</label>
|
|
136
|
+
<span class="font-medium text-slate-900 dark:text-gray-200">{{duration}}</span>
|
|
137
|
+
</div>
|
|
138
|
+
<div>
|
|
139
|
+
<label class="text-xs font-medium text-slate-500 dark:text-gray-400 block uppercase">Profile</label>
|
|
140
|
+
<span class="font-medium text-slate-900 dark:text-gray-200 capitalize">{{profile}}</span>
|
|
141
|
+
</div>
|
|
142
|
+
<div>
|
|
143
|
+
<label class="text-xs font-medium text-slate-500 dark:text-gray-400 block uppercase">Total findings</label>
|
|
144
|
+
<span class="font-medium text-slate-900 dark:text-gray-200">{{totalFindings}}</span>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</section>
|
|
148
|
+
|
|
149
|
+
{{#if technologies.length}}
|
|
150
|
+
<!-- Technology Detection -->
|
|
151
|
+
<section class="bg-white dark:bg-slate-800 rounded-xl shadow-sm p-6 mb-8 border border-slate-200 dark:border-slate-700">
|
|
152
|
+
<h2 class="text-lg font-bold mb-4 text-slate-900 dark:text-white">Technology Detection</h2>
|
|
153
|
+
<div class="overflow-x-auto">
|
|
154
|
+
<table class="w-full">
|
|
155
|
+
<thead>
|
|
156
|
+
<tr class="border-b-2 border-slate-200 dark:border-slate-700">
|
|
157
|
+
<th class="text-left py-3 px-4 text-xs font-bold text-slate-500 dark:text-gray-400 uppercase tracking-wider">Host</th>
|
|
158
|
+
<th class="text-left py-3 px-4 text-xs font-bold text-slate-500 dark:text-gray-400 uppercase tracking-wider">Technologies</th>
|
|
159
|
+
</tr>
|
|
160
|
+
</thead>
|
|
161
|
+
<tbody>
|
|
162
|
+
{{#each technologies}}
|
|
163
|
+
<tr class="border-b border-slate-100 dark:border-slate-700/50 last:border-0">
|
|
164
|
+
<td class="py-3 px-4 font-mono text-sm text-slate-700 dark:text-gray-300">{{this.host}}</td>
|
|
165
|
+
<td class="py-3 px-4">
|
|
166
|
+
<div class="flex flex-wrap gap-2">
|
|
167
|
+
{{#each this.technologies}}
|
|
168
|
+
<span class="px-2 py-1 bg-sky-100 dark:bg-sky-900/30 text-sky-800 dark:text-sky-300 rounded text-sm font-medium border border-sky-200 dark:border-sky-800">{{this}}</span>
|
|
169
|
+
{{/each}}
|
|
170
|
+
</div>
|
|
171
|
+
</td>
|
|
172
|
+
</tr>
|
|
173
|
+
{{/each}}
|
|
174
|
+
</tbody>
|
|
175
|
+
</table>
|
|
176
|
+
</div>
|
|
177
|
+
</section>
|
|
178
|
+
{{/if}}
|
|
179
|
+
|
|
180
|
+
{{#if coverage.length}}
|
|
181
|
+
<!-- Test Coverage -->
|
|
182
|
+
<section class="bg-white dark:bg-slate-800 rounded-xl shadow-sm p-6 mb-8 border border-slate-200 dark:border-slate-700">
|
|
183
|
+
<h2 class="text-lg font-bold mb-4 text-slate-900 dark:text-white">Test Coverage</h2>
|
|
184
|
+
<div class="overflow-x-auto">
|
|
185
|
+
<table class="w-full">
|
|
186
|
+
<thead>
|
|
187
|
+
<tr class="border-b-2 border-slate-200 dark:border-slate-700">
|
|
188
|
+
<th class="text-left py-3 px-4 text-xs font-bold text-slate-500 dark:text-gray-400 uppercase tracking-wider">Category</th>
|
|
189
|
+
<th class="text-left py-3 px-4 text-xs font-bold text-slate-500 dark:text-gray-400 uppercase tracking-wider">Status</th>
|
|
190
|
+
<th class="text-left py-3 px-4 text-xs font-bold text-slate-500 dark:text-gray-400 uppercase tracking-wider">Count</th>
|
|
191
|
+
</tr>
|
|
192
|
+
</thead>
|
|
193
|
+
<tbody>
|
|
194
|
+
{{#each coverage}}
|
|
195
|
+
{{#if this.tested}}
|
|
196
|
+
<tr class="border-b border-slate-100 dark:border-slate-700/50 last:border-0 hover:bg-slate-50 dark:hover:bg-slate-700/30 transition-colors">
|
|
197
|
+
<td class="py-3 px-4">
|
|
198
|
+
<div class="font-semibold text-slate-900 dark:text-gray-200">{{this.name}}</div>
|
|
199
|
+
<div class="text-sm text-slate-500 dark:text-gray-400">{{this.description}}</div>
|
|
200
|
+
</td>
|
|
201
|
+
<td class="py-3 px-4">
|
|
202
|
+
{{#ifCond this.status "pass"}}
|
|
203
|
+
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400">
|
|
204
|
+
✅ Nothing found
|
|
205
|
+
</span>
|
|
206
|
+
{{/ifCond}}
|
|
207
|
+
{{#ifCond this.status "fail"}}
|
|
208
|
+
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400">
|
|
209
|
+
⚠️ Issues found
|
|
210
|
+
</span>
|
|
211
|
+
{{/ifCond}}
|
|
212
|
+
{{#ifCond this.status "info"}}
|
|
213
|
+
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400">
|
|
214
|
+
ℹ️ Detected
|
|
215
|
+
</span>
|
|
216
|
+
{{/ifCond}}
|
|
217
|
+
</td>
|
|
218
|
+
<td class="py-3 px-4 font-bold text-slate-700 dark:text-gray-300">{{this.count}}</td>
|
|
219
|
+
</tr>
|
|
220
|
+
{{/if}}
|
|
221
|
+
{{/each}}
|
|
222
|
+
</tbody>
|
|
223
|
+
</table>
|
|
224
|
+
</div>
|
|
225
|
+
</section>
|
|
226
|
+
{{/if}}
|
|
227
|
+
|
|
228
|
+
<!-- Findings -->
|
|
229
|
+
<section class="mb-8">
|
|
230
|
+
<h2 class="text-lg font-bold mb-4 text-slate-900 dark:text-white">Findings</h2>
|
|
231
|
+
|
|
232
|
+
{{#each findings}}
|
|
233
|
+
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-sm mb-4 overflow-hidden border border-slate-200 dark:border-slate-700 border-l-4 {{severityBorder this.severity}}"
|
|
234
|
+
x-data="{ open: true }">
|
|
235
|
+
<div class="flex items-center gap-4 p-4 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors"
|
|
236
|
+
@click="open = !open">
|
|
237
|
+
<span class="text-slate-400 font-mono text-sm w-12">#{{this.id}}</span>
|
|
238
|
+
<span class="font-semibold flex-1 text-slate-900 dark:text-gray-100">{{this.title}}</span>
|
|
239
|
+
<span class="px-2.5 py-1 rounded text-xs font-bold shadow-sm uppercase tracking-wide {{severityClass this.severity}}">
|
|
240
|
+
{{severityBadge this.severity}}
|
|
241
|
+
</span>
|
|
242
|
+
<svg class="w-5 h-5 text-slate-400 transition-transform" :class="{ 'rotate-180': open }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
243
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
|
244
|
+
</svg>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
<div x-show="open" x-cloak x-transition class="border-t border-slate-100 dark:border-slate-700 bg-slate-50/50 dark:bg-slate-800/50">
|
|
248
|
+
<div class="p-5 space-y-4">
|
|
249
|
+
{{#if this.matcher}}
|
|
250
|
+
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-lg p-3 font-mono text-sm break-all shadow-sm">
|
|
251
|
+
<span class="font-bold text-blue-700 dark:text-blue-400 block mb-1">Matched:</span>
|
|
252
|
+
<span class="text-slate-700 dark:text-slate-300">{{this.matcher}}</span>
|
|
253
|
+
</div>
|
|
254
|
+
{{/if}}
|
|
255
|
+
|
|
256
|
+
{{#if this.extracted}}
|
|
257
|
+
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-lg p-3 font-mono text-sm shadow-sm">
|
|
258
|
+
<span class="font-bold text-purple-700 dark:text-purple-400 block mb-1">Extracted:</span>
|
|
259
|
+
<span class="text-slate-700 dark:text-slate-300">
|
|
260
|
+
{{#each this.extracted}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}
|
|
261
|
+
</span>
|
|
262
|
+
</div>
|
|
263
|
+
{{/if}}
|
|
264
|
+
|
|
265
|
+
{{#if this.cveTable}}
|
|
266
|
+
<div class="overflow-x-auto">
|
|
267
|
+
<table class="w-full text-sm">
|
|
268
|
+
<thead>
|
|
269
|
+
<tr class="border-b-2 border-slate-200 dark:border-slate-700">
|
|
270
|
+
<th class="text-left py-2 px-3 font-bold text-slate-600 dark:text-gray-400">CVE</th>
|
|
271
|
+
<th class="text-left py-2 px-3 font-bold text-slate-600 dark:text-gray-400">CVSS</th>
|
|
272
|
+
<th class="text-left py-2 px-3 font-bold text-slate-600 dark:text-gray-400">Severity</th>
|
|
273
|
+
<th class="text-left py-2 px-3 font-bold text-slate-600 dark:text-gray-400">Summary</th>
|
|
274
|
+
</tr>
|
|
275
|
+
</thead>
|
|
276
|
+
<tbody>
|
|
277
|
+
{{#each this.cveTable}}
|
|
278
|
+
<tr class="border-b border-slate-100 dark:border-slate-700/50 hover:bg-slate-50 dark:hover:bg-slate-700/30">
|
|
279
|
+
<td class="py-2 px-3 font-mono text-blue-600 dark:text-blue-400">
|
|
280
|
+
<a href="https://nvd.nist.gov/vuln/detail/{{this.cve}}" target="_blank" class="hover:underline">{{this.cve}}</a>
|
|
281
|
+
</td>
|
|
282
|
+
<td class="py-2 px-3 font-mono">{{this.cvss}}</td>
|
|
283
|
+
<td class="py-2 px-3">
|
|
284
|
+
<span class="px-2 py-0.5 rounded text-xs font-bold uppercase {{severityClass this.severity}}">{{this.severity}}</span>
|
|
285
|
+
</td>
|
|
286
|
+
<td class="py-2 px-3 text-slate-700 dark:text-slate-300">{{this.summary}}</td>
|
|
287
|
+
</tr>
|
|
288
|
+
{{/each}}
|
|
289
|
+
</tbody>
|
|
290
|
+
</table>
|
|
291
|
+
</div>
|
|
292
|
+
{{/if}}
|
|
293
|
+
|
|
294
|
+
<div class="flex flex-wrap gap-3 text-sm text-slate-600 dark:text-gray-400">
|
|
295
|
+
{{#if this.affectedUrls}}
|
|
296
|
+
<span class="inline-flex items-center gap-1 bg-slate-100 dark:bg-slate-700 px-2 py-1 rounded">🎯 {{this.affectedUrls.length}} URLs affected</span>
|
|
297
|
+
{{else}}
|
|
298
|
+
<span class="inline-flex items-center gap-1 bg-slate-100 dark:bg-slate-700 px-2 py-1 rounded">🎯 {{this.target}}</span>
|
|
299
|
+
{{/if}}
|
|
300
|
+
{{#if this.cve}}<span class="inline-flex items-center gap-1 bg-slate-100 dark:bg-slate-700 px-2 py-1 rounded">🔒 {{this.cve}}</span>{{/if}}
|
|
301
|
+
{{#if this.cwe}}<span class="inline-flex items-center gap-1 bg-slate-100 dark:bg-slate-700 px-2 py-1 rounded">📋 {{this.cwe}}</span>{{/if}}
|
|
302
|
+
</div>
|
|
303
|
+
|
|
304
|
+
{{#if this.affectedUrls}}
|
|
305
|
+
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-lg p-3">
|
|
306
|
+
<strong class="text-sm font-semibold text-slate-900 dark:text-gray-200 block mb-2">Affected URLs:</strong>
|
|
307
|
+
<ul class="space-y-1 text-sm font-mono text-slate-600 dark:text-gray-400 max-h-40 overflow-y-auto">
|
|
308
|
+
{{#each this.affectedUrls}}
|
|
309
|
+
<li class="truncate hover:text-slate-900 dark:hover:text-gray-200">• {{this}}</li>
|
|
310
|
+
{{/each}}
|
|
311
|
+
</ul>
|
|
312
|
+
</div>
|
|
313
|
+
{{/if}}
|
|
314
|
+
|
|
315
|
+
{{#if this.description}}
|
|
316
|
+
<div class="prose prose-sm max-w-none text-slate-700 dark:text-gray-300">
|
|
317
|
+
<p>{{this.description}}</p>
|
|
318
|
+
</div>
|
|
319
|
+
{{/if}}
|
|
320
|
+
|
|
321
|
+
{{#if this.curl}}
|
|
322
|
+
<details class="group">
|
|
323
|
+
<summary class="cursor-pointer text-sm font-medium text-slate-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 mb-2">
|
|
324
|
+
📋 Reproduce command
|
|
325
|
+
</summary>
|
|
326
|
+
<pre class="bg-slate-900 text-slate-50 rounded-lg p-4 text-xs overflow-x-auto font-mono border border-slate-700 shadow-sm">{{this.curl}}</pre>
|
|
327
|
+
</details>
|
|
328
|
+
{{/if}}
|
|
329
|
+
|
|
330
|
+
{{#if this.references}}
|
|
331
|
+
<div class="text-sm border-t border-slate-200 dark:border-slate-700 pt-3">
|
|
332
|
+
<strong class="text-slate-900 dark:text-gray-200">References:</strong>
|
|
333
|
+
<div class="mt-1 flex flex-col gap-1">
|
|
334
|
+
{{#each this.references}}
|
|
335
|
+
<a href="{{this}}" target="_blank" class="text-blue-600 dark:text-blue-400 hover:underline break-all inline-flex items-center gap-1">
|
|
336
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg>
|
|
337
|
+
{{this}}
|
|
338
|
+
</a>
|
|
339
|
+
{{/each}}
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
{{/if}}
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
{{/each}}
|
|
347
|
+
</section>
|
|
348
|
+
|
|
349
|
+
<!-- Summary Table -->
|
|
350
|
+
<section class="bg-white dark:bg-slate-800 rounded-xl shadow-sm p-6 mb-8 border border-slate-200 dark:border-slate-700">
|
|
351
|
+
<h2 class="text-lg font-bold mb-4 text-slate-900 dark:text-white">Summary Table</h2>
|
|
352
|
+
<div class="overflow-x-auto">
|
|
353
|
+
<table class="w-full">
|
|
354
|
+
<thead>
|
|
355
|
+
<tr class="border-b-2 border-slate-200 dark:border-slate-700">
|
|
356
|
+
<th class="text-left py-3 px-4 text-xs font-bold text-slate-500 dark:text-gray-400 uppercase tracking-wider">#</th>
|
|
357
|
+
<th class="text-left py-3 px-4 text-xs font-bold text-slate-500 dark:text-gray-400 uppercase tracking-wider">Finding</th>
|
|
358
|
+
<th class="text-left py-3 px-4 text-xs font-bold text-slate-500 dark:text-gray-400 uppercase tracking-wider">Severity</th>
|
|
359
|
+
<th class="text-left py-3 px-4 text-xs font-bold text-slate-500 dark:text-gray-400 uppercase tracking-wider">Target</th>
|
|
360
|
+
<th class="text-left py-3 px-4 text-xs font-bold text-slate-500 dark:text-gray-400 uppercase tracking-wider">CVE</th>
|
|
361
|
+
</tr>
|
|
362
|
+
</thead>
|
|
363
|
+
<tbody>
|
|
364
|
+
{{#each findings}}
|
|
365
|
+
<tr class="border-b border-slate-100 dark:border-slate-700/50 last:border-0 hover:bg-slate-50 dark:hover:bg-slate-700/30 transition-colors">
|
|
366
|
+
<td class="py-3 px-4 font-mono text-sm text-slate-500">{{this.id}}</td>
|
|
367
|
+
<td class="py-3 px-4 font-medium text-slate-700 dark:text-gray-200">{{truncate this.title 50}}</td>
|
|
368
|
+
<td class="py-3 px-4">
|
|
369
|
+
<span class="px-2 py-1 rounded text-xs font-bold uppercase {{severityClass this.severity}}">
|
|
370
|
+
{{severityBadge this.severity}}
|
|
371
|
+
</span>
|
|
372
|
+
</td>
|
|
373
|
+
<td class="py-3 px-4 font-mono text-sm text-slate-600 dark:text-gray-400 truncate max-w-xs">
|
|
374
|
+
{{#if this.affectedUrls}}{{this.affectedUrls.length}} URLs{{else}}{{truncate this.target 40}}{{/if}}
|
|
375
|
+
</td>
|
|
376
|
+
<td class="py-3 px-4 text-sm text-slate-600 dark:text-gray-400">{{this.cve}}</td>
|
|
377
|
+
</tr>
|
|
378
|
+
{{/each}}
|
|
379
|
+
</tbody>
|
|
380
|
+
</table>
|
|
381
|
+
</div>
|
|
382
|
+
</section>
|
|
383
|
+
|
|
384
|
+
{{#if passedChecks.length}}
|
|
385
|
+
<!-- Passed Checks -->
|
|
386
|
+
<section class="bg-white dark:bg-slate-800 rounded-xl shadow-sm p-6 mb-8 border border-slate-200 dark:border-slate-700">
|
|
387
|
+
<h2 class="text-lg font-bold mb-4 text-slate-900 dark:text-white">Passed Security Checks</h2>
|
|
388
|
+
<p class="text-slate-500 dark:text-gray-400 text-sm mb-4">The following security checks were performed but no issues were found:</p>
|
|
389
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
390
|
+
{{#each passedChecks}}
|
|
391
|
+
<div class="flex items-start gap-3 p-3 rounded-lg bg-emerald-50 dark:bg-emerald-900/20 border-l-4 border-emerald-500 border border-slate-100 dark:border-slate-700/50">
|
|
392
|
+
<span class="text-emerald-600 dark:text-emerald-400 font-bold">✓</span>
|
|
393
|
+
<div>
|
|
394
|
+
<div class="font-bold text-sm text-slate-900 dark:text-gray-100">{{this.name}}</div>
|
|
395
|
+
<div class="text-xs text-slate-500 dark:text-gray-400 mt-0.5">{{this.description}}</div>
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
{{/each}}
|
|
399
|
+
</div>
|
|
400
|
+
</section>
|
|
401
|
+
{{/if}}
|
|
402
|
+
|
|
403
|
+
<!-- Footer -->
|
|
404
|
+
<footer class="text-center py-8 text-slate-500 dark:text-gray-500 text-sm border-t border-slate-200 dark:border-slate-800">
|
|
405
|
+
<p>Generated by VoidSec Scanner v0.1.0</p>
|
|
406
|
+
</footer>
|
|
407
|
+
</div>
|
|
408
|
+
|
|
409
|
+
</body>
|
|
410
|
+
</html>`;
|
|
411
|
+
// Compile template
|
|
412
|
+
const template = Handlebars.compile(HTML_TEMPLATE);
|
|
413
|
+
/**
|
|
414
|
+
* Calculate gauge offset for SVG arc (126 = full arc length)
|
|
415
|
+
*/
|
|
416
|
+
function calculateGaugeOffset(count, max = 20) {
|
|
417
|
+
if (count === 0)
|
|
418
|
+
return 126;
|
|
419
|
+
const ratio = Math.min(count / max, 1);
|
|
420
|
+
return Math.round(126 - (ratio * 126));
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Build severity gauge data for template
|
|
424
|
+
*/
|
|
425
|
+
function buildSeverityGauges(summary, total) {
|
|
426
|
+
const maxGauge = Math.max(total, 10);
|
|
427
|
+
// Determine highest severity for highlight
|
|
428
|
+
const highestSeverity = summary.critical > 0 ? 'critical'
|
|
429
|
+
: summary.high > 0 ? 'high'
|
|
430
|
+
: summary.medium > 0 ? 'medium'
|
|
431
|
+
: summary.low > 0 ? 'low'
|
|
432
|
+
: 'info';
|
|
433
|
+
const gaugeConfig = [
|
|
434
|
+
{ key: 'critical', label: 'Critical', strokeColor: 'stroke-red-600', ringColor: 'ring-red-600', labelBg: 'bg-red-600' },
|
|
435
|
+
{ key: 'high', label: 'High', strokeColor: 'stroke-orange-500', ringColor: 'ring-orange-500', labelBg: 'bg-orange-500' },
|
|
436
|
+
{ key: 'medium', label: 'Medium', strokeColor: 'stroke-yellow-500', ringColor: 'ring-yellow-500', labelBg: 'bg-yellow-500' },
|
|
437
|
+
{ key: 'low', label: 'Low', strokeColor: 'stroke-blue-600', ringColor: 'ring-blue-600', labelBg: 'bg-blue-600' },
|
|
438
|
+
{ key: 'info', label: 'Info', strokeColor: 'stroke-green-500', ringColor: 'ring-green-500', labelBg: 'bg-green-500' },
|
|
439
|
+
];
|
|
440
|
+
return gaugeConfig.map(g => ({
|
|
441
|
+
...g,
|
|
442
|
+
count: summary[g.key],
|
|
443
|
+
offset: calculateGaugeOffset(summary[g.key], maxGauge),
|
|
444
|
+
highlight: g.key === highestSeverity && summary[g.key] > 0,
|
|
445
|
+
}));
|
|
446
|
+
}
|
|
447
|
+
export function generateHtmlReport(report) {
|
|
448
|
+
const enhancedReport = {
|
|
449
|
+
...report,
|
|
450
|
+
severityGauges: buildSeverityGauges(report.summary, report.totalFindings || 0),
|
|
451
|
+
};
|
|
452
|
+
return template(enhancedReport);
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Generate JSON report from scan results
|
|
456
|
+
*/
|
|
457
|
+
export function generateJsonReport(report) {
|
|
458
|
+
return JSON.stringify(report, null, 2);
|
|
459
|
+
}
|
|
460
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nuclei Runner
|
|
3
|
+
* Execute nuclei scans and parse JSONL output
|
|
4
|
+
*
|
|
5
|
+
* DESIGN: Keep it simple - don't try to reformat Nuclei's output.
|
|
6
|
+
* Just parse and pass through. Let the template handle display.
|
|
7
|
+
*
|
|
8
|
+
* Note: Headless templates require accepting macOS keychain prompt once ("Always Allow")
|
|
9
|
+
*/
|
|
10
|
+
import { Finding, ScanProfile, TechDetection } from '../types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Set severity overrides from config
|
|
13
|
+
*/
|
|
14
|
+
export declare function setSeverityOverrides(overrides: Record<string, string>): void;
|
|
15
|
+
/**
|
|
16
|
+
* Extract technology detections from findings
|
|
17
|
+
*/
|
|
18
|
+
export declare function extractTechnologies(findings: Finding[]): TechDetection[];
|
|
19
|
+
/**
|
|
20
|
+
* Get tags for a given stack
|
|
21
|
+
*/
|
|
22
|
+
export declare function getStackTags(stack: string): string[];
|
|
23
|
+
/**
|
|
24
|
+
* Get available stacks
|
|
25
|
+
*/
|
|
26
|
+
export declare function getAvailableStacks(): string[];
|
|
27
|
+
/**
|
|
28
|
+
* Run Nuclei scan on target URL(s)
|
|
29
|
+
*/
|
|
30
|
+
export declare function runNuclei(targets: string | string[], profile?: ScanProfile, options?: {
|
|
31
|
+
rateLimit?: number;
|
|
32
|
+
timeout?: number;
|
|
33
|
+
stack?: string;
|
|
34
|
+
}): Promise<{
|
|
35
|
+
findings: Finding[];
|
|
36
|
+
technologies: TechDetection[];
|
|
37
|
+
}>;
|
|
38
|
+
/**
|
|
39
|
+
* Check if Nuclei is installed
|
|
40
|
+
*/
|
|
41
|
+
export declare function isNucleiInstalled(): Promise<boolean>;
|
|
42
|
+
/**
|
|
43
|
+
* Extract Drupal themes from HTML
|
|
44
|
+
* Looks for: /themes/xxx/ patterns in script/link tags
|
|
45
|
+
*/
|
|
46
|
+
export declare function extractDrupalThemes(targetUrl: string): Promise<string[]>;
|
|
47
|
+
/**
|
|
48
|
+
* Run Nuclei template with dynamic variables
|
|
49
|
+
* Used for checks that need runtime-extracted values (like theme names)
|
|
50
|
+
*/
|
|
51
|
+
export declare function runNucleiWithVars(target: string, templatePath: string, vars: Record<string, string>, options?: {
|
|
52
|
+
rateLimit?: number;
|
|
53
|
+
}): Promise<Finding[]>;
|
|
54
|
+
/**
|
|
55
|
+
* Run Drupal-specific scans (all templates + dynamic checks)
|
|
56
|
+
* Includes: static templates, theme lock files, API user exposure
|
|
57
|
+
*/
|
|
58
|
+
export declare function runDrupalDynamicScans(target: string, onProgress?: (msg: string) => void): Promise<Finding[]>;
|
|
59
|
+
//# sourceMappingURL=nuclei.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nuclei.d.ts","sourceRoot":"","sources":["../../src/runners/nuclei.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,OAAO,EAAgB,WAAW,EAAY,aAAa,EAAE,MAAM,aAAa,CAAC;AAiD1F;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAQ5E;AAyHD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,aAAa,EAAE,CA8BxE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAGpD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAE7C;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,EAC1B,OAAO,GAAE,WAAwB,EACjC,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACrE,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAAC,YAAY,EAAE,aAAa,EAAE,CAAA;CAAE,CAAC,CAmDjE;AAED;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO1D;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAqB9E;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACnC,OAAO,CAAC,OAAO,EAAE,CAAC,CAkCpB;AA6MD;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GACjC,OAAO,CAAC,OAAO,EAAE,CAAC,CA+BpB"}
|