@hailbytes/pentest-calculator 1.0.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/CHANGELOG.md +22 -0
- package/LICENSE +374 -0
- package/README.md +231 -0
- package/hailbytes-pentest-calculator.js +783 -0
- package/index.d.ts +51 -0
- package/package.json +60 -0
|
@@ -0,0 +1,783 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HailBytes Penetration Testing Scope Calculator
|
|
3
|
+
* Zero-dependency Web Component — pure ES module, no build step required.
|
|
4
|
+
*
|
|
5
|
+
* Usage: <hailbytes-pentest-calculator></hailbytes-pentest-calculator>
|
|
6
|
+
*
|
|
7
|
+
* Attributes:
|
|
8
|
+
* theme="dark|light" (default: dark)
|
|
9
|
+
*
|
|
10
|
+
* Events dispatched:
|
|
11
|
+
* pentest-calculated — fired on every calculation, detail = full result object
|
|
12
|
+
* pentest-quote-requested — fired when "Get an accurate quote" is clicked, detail = last result
|
|
13
|
+
*
|
|
14
|
+
* License: MPL-2.0
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const STYLES = `
|
|
18
|
+
:host {
|
|
19
|
+
--accent: #ff6b35;
|
|
20
|
+
--accent-hover: #e85d2a;
|
|
21
|
+
--bg: #1a1a2e;
|
|
22
|
+
--bg-card: #16213e;
|
|
23
|
+
--bg-input: #0f3460;
|
|
24
|
+
--border: #2a2a4a;
|
|
25
|
+
--text: #e8e8f0;
|
|
26
|
+
--text-muted: #8888aa;
|
|
27
|
+
--success: #4caf50;
|
|
28
|
+
--warning: #ff9800;
|
|
29
|
+
--error: #f44336;
|
|
30
|
+
--radius: 10px;
|
|
31
|
+
--shadow: 0 4px 24px rgba(0,0,0,0.4);
|
|
32
|
+
display: block;
|
|
33
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
34
|
+
color: var(--text);
|
|
35
|
+
background: var(--bg);
|
|
36
|
+
border-radius: 16px;
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
}
|
|
39
|
+
:host([theme="light"]) {
|
|
40
|
+
--bg: #f5f5f5;
|
|
41
|
+
--bg-card: #ffffff;
|
|
42
|
+
--bg-input: #f0f0f8;
|
|
43
|
+
--border: #ddd;
|
|
44
|
+
--text: #1a1a2e;
|
|
45
|
+
--text-muted: #555570;
|
|
46
|
+
--shadow: 0 4px 24px rgba(0,0,0,0.1);
|
|
47
|
+
}
|
|
48
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
49
|
+
|
|
50
|
+
:host([branding="off"]) .hb-branding,
|
|
51
|
+
:host([branding="off"]) .hailbytes-badge { display: none; }
|
|
52
|
+
|
|
53
|
+
/* Footer */
|
|
54
|
+
.hb-footer {
|
|
55
|
+
padding: 14px 32px;
|
|
56
|
+
text-align: center;
|
|
57
|
+
font-size: 0.82rem;
|
|
58
|
+
color: var(--text-muted);
|
|
59
|
+
border-top: 1px solid var(--border);
|
|
60
|
+
}
|
|
61
|
+
.hb-footer a {
|
|
62
|
+
color: var(--accent);
|
|
63
|
+
text-decoration: none;
|
|
64
|
+
font-weight: 600;
|
|
65
|
+
}
|
|
66
|
+
.hb-footer a:hover { text-decoration: underline; }
|
|
67
|
+
|
|
68
|
+
/* Header */
|
|
69
|
+
.header {
|
|
70
|
+
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
|
71
|
+
padding: 28px 32px 20px;
|
|
72
|
+
border-bottom: 2px solid var(--accent);
|
|
73
|
+
}
|
|
74
|
+
:host([theme="light"]) .header {
|
|
75
|
+
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
76
|
+
}
|
|
77
|
+
.header h1 {
|
|
78
|
+
font-size: 1.5rem;
|
|
79
|
+
font-weight: 700;
|
|
80
|
+
color: #fff;
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
gap: 10px;
|
|
84
|
+
margin-bottom: 6px;
|
|
85
|
+
}
|
|
86
|
+
.header h1 .icon { color: var(--accent); font-size: 1.6rem; }
|
|
87
|
+
.header p { color: #aab; font-size: 0.88rem; }
|
|
88
|
+
.hailbytes-badge {
|
|
89
|
+
display: inline-block;
|
|
90
|
+
background: var(--accent);
|
|
91
|
+
color: #fff;
|
|
92
|
+
font-size: 0.65rem;
|
|
93
|
+
font-weight: 700;
|
|
94
|
+
letter-spacing: 1px;
|
|
95
|
+
padding: 2px 8px;
|
|
96
|
+
border-radius: 4px;
|
|
97
|
+
vertical-align: middle;
|
|
98
|
+
margin-left: 4px;
|
|
99
|
+
text-transform: uppercase;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Progress */
|
|
103
|
+
.progress-bar-wrap {
|
|
104
|
+
padding: 20px 32px 0;
|
|
105
|
+
background: var(--bg-card);
|
|
106
|
+
}
|
|
107
|
+
.steps-row {
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
margin-bottom: 12px;
|
|
111
|
+
}
|
|
112
|
+
.step-item {
|
|
113
|
+
display: flex;
|
|
114
|
+
flex-direction: column;
|
|
115
|
+
align-items: center;
|
|
116
|
+
gap: 4px;
|
|
117
|
+
flex: 1;
|
|
118
|
+
position: relative;
|
|
119
|
+
}
|
|
120
|
+
.step-item:not(:last-child)::after {
|
|
121
|
+
content: '';
|
|
122
|
+
position: absolute;
|
|
123
|
+
top: 16px;
|
|
124
|
+
left: calc(50% + 16px);
|
|
125
|
+
right: calc(-50% + 16px);
|
|
126
|
+
height: 2px;
|
|
127
|
+
background: var(--border);
|
|
128
|
+
transition: background 0.4s;
|
|
129
|
+
}
|
|
130
|
+
.step-item.done:not(:last-child)::after,
|
|
131
|
+
.step-item.active:not(:last-child)::after {
|
|
132
|
+
background: var(--accent);
|
|
133
|
+
}
|
|
134
|
+
.step-circle {
|
|
135
|
+
width: 32px; height: 32px;
|
|
136
|
+
border-radius: 50%;
|
|
137
|
+
border: 2px solid var(--border);
|
|
138
|
+
display: flex; align-items: center; justify-content: center;
|
|
139
|
+
font-weight: 700; font-size: 0.85rem;
|
|
140
|
+
color: var(--text-muted);
|
|
141
|
+
background: var(--bg);
|
|
142
|
+
transition: all 0.3s;
|
|
143
|
+
z-index: 1;
|
|
144
|
+
}
|
|
145
|
+
.step-item.active .step-circle {
|
|
146
|
+
border-color: var(--accent);
|
|
147
|
+
background: var(--accent);
|
|
148
|
+
color: #fff;
|
|
149
|
+
box-shadow: 0 0 0 4px rgba(255,107,53,0.25);
|
|
150
|
+
}
|
|
151
|
+
.step-item.done .step-circle {
|
|
152
|
+
border-color: var(--success);
|
|
153
|
+
background: var(--success);
|
|
154
|
+
color: #fff;
|
|
155
|
+
}
|
|
156
|
+
.step-label {
|
|
157
|
+
font-size: 0.72rem;
|
|
158
|
+
color: var(--text-muted);
|
|
159
|
+
text-align: center;
|
|
160
|
+
font-weight: 500;
|
|
161
|
+
white-space: nowrap;
|
|
162
|
+
}
|
|
163
|
+
.step-item.active .step-label { color: var(--accent); }
|
|
164
|
+
.step-item.done .step-label { color: var(--success); }
|
|
165
|
+
|
|
166
|
+
/* Body */
|
|
167
|
+
.body { padding: 24px 32px 32px; background: var(--bg-card); }
|
|
168
|
+
|
|
169
|
+
/* Step panels */
|
|
170
|
+
.step-panel { display: none; }
|
|
171
|
+
.step-panel.active { display: block; animation: fadeIn 0.3s ease; }
|
|
172
|
+
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
|
|
173
|
+
|
|
174
|
+
.step-title {
|
|
175
|
+
font-size: 1.15rem;
|
|
176
|
+
font-weight: 700;
|
|
177
|
+
color: var(--text);
|
|
178
|
+
margin-bottom: 20px;
|
|
179
|
+
padding-bottom: 10px;
|
|
180
|
+
border-bottom: 1px solid var(--border);
|
|
181
|
+
display: flex; align-items: center; gap: 8px;
|
|
182
|
+
}
|
|
183
|
+
.step-title .step-icon { color: var(--accent); }
|
|
184
|
+
|
|
185
|
+
/* Form elements */
|
|
186
|
+
.field-group {
|
|
187
|
+
display: grid;
|
|
188
|
+
grid-template-columns: 1fr 1fr;
|
|
189
|
+
gap: 16px;
|
|
190
|
+
margin-bottom: 16px;
|
|
191
|
+
}
|
|
192
|
+
@media (max-width: 560px) {
|
|
193
|
+
.field-group { grid-template-columns: 1fr; }
|
|
194
|
+
.body, .progress-bar-wrap { padding-left: 16px; padding-right: 16px; }
|
|
195
|
+
.header { padding-left: 16px; padding-right: 16px; }
|
|
196
|
+
}
|
|
197
|
+
.field { display: flex; flex-direction: column; gap: 6px; }
|
|
198
|
+
.field.full { grid-column: 1 / -1; }
|
|
199
|
+
label {
|
|
200
|
+
font-size: 0.82rem;
|
|
201
|
+
font-weight: 600;
|
|
202
|
+
color: var(--text-muted);
|
|
203
|
+
text-transform: uppercase;
|
|
204
|
+
letter-spacing: 0.5px;
|
|
205
|
+
}
|
|
206
|
+
input[type="number"], select {
|
|
207
|
+
background: var(--bg-input);
|
|
208
|
+
border: 1px solid var(--border);
|
|
209
|
+
border-radius: var(--radius);
|
|
210
|
+
color: var(--text);
|
|
211
|
+
font-size: 0.95rem;
|
|
212
|
+
padding: 10px 14px;
|
|
213
|
+
width: 100%;
|
|
214
|
+
outline: none;
|
|
215
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
216
|
+
appearance: none;
|
|
217
|
+
-webkit-appearance: none;
|
|
218
|
+
}
|
|
219
|
+
input[type="number"]:focus, select:focus {
|
|
220
|
+
border-color: var(--accent);
|
|
221
|
+
box-shadow: 0 0 0 3px rgba(255,107,53,0.2);
|
|
222
|
+
}
|
|
223
|
+
select {
|
|
224
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ff6b35'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
|
|
225
|
+
background-repeat: no-repeat;
|
|
226
|
+
background-position: right 10px center;
|
|
227
|
+
background-size: 20px;
|
|
228
|
+
padding-right: 36px;
|
|
229
|
+
cursor: pointer;
|
|
230
|
+
}
|
|
231
|
+
select option { background: #1a1a2e; color: #e8e8f0; }
|
|
232
|
+
|
|
233
|
+
/* Checkboxes */
|
|
234
|
+
.checkbox-group { display: flex; flex-direction: column; gap: 8px; }
|
|
235
|
+
.checkbox-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
|
|
236
|
+
@media (max-width: 560px) { .checkbox-grid { grid-template-columns: 1fr; } }
|
|
237
|
+
.checkbox-item {
|
|
238
|
+
display: flex; align-items: center; gap: 10px;
|
|
239
|
+
background: var(--bg-input);
|
|
240
|
+
border: 1px solid var(--border);
|
|
241
|
+
border-radius: 8px;
|
|
242
|
+
padding: 10px 12px;
|
|
243
|
+
cursor: pointer;
|
|
244
|
+
transition: border-color 0.2s, background 0.2s;
|
|
245
|
+
user-select: none;
|
|
246
|
+
}
|
|
247
|
+
.checkbox-item:hover { border-color: var(--accent); }
|
|
248
|
+
.checkbox-item input[type="checkbox"] {
|
|
249
|
+
width: 17px; height: 17px;
|
|
250
|
+
accent-color: var(--accent);
|
|
251
|
+
cursor: pointer;
|
|
252
|
+
flex-shrink: 0;
|
|
253
|
+
}
|
|
254
|
+
.checkbox-item.checked {
|
|
255
|
+
border-color: var(--accent);
|
|
256
|
+
background: rgba(255,107,53,0.08);
|
|
257
|
+
}
|
|
258
|
+
.checkbox-label { font-size: 0.88rem; color: var(--text); line-height: 1.3; }
|
|
259
|
+
.checkbox-label small { display: block; color: var(--text-muted); font-size: 0.74rem; }
|
|
260
|
+
|
|
261
|
+
/* Nav buttons */
|
|
262
|
+
.nav-row {
|
|
263
|
+
display: flex;
|
|
264
|
+
justify-content: space-between;
|
|
265
|
+
align-items: center;
|
|
266
|
+
margin-top: 24px;
|
|
267
|
+
gap: 12px;
|
|
268
|
+
}
|
|
269
|
+
.btn {
|
|
270
|
+
display: inline-flex; align-items: center; gap: 8px;
|
|
271
|
+
padding: 11px 24px;
|
|
272
|
+
border: none; border-radius: var(--radius);
|
|
273
|
+
font-size: 0.95rem; font-weight: 600;
|
|
274
|
+
cursor: pointer; transition: all 0.2s;
|
|
275
|
+
text-decoration: none;
|
|
276
|
+
}
|
|
277
|
+
.btn-primary {
|
|
278
|
+
background: var(--accent); color: #fff;
|
|
279
|
+
}
|
|
280
|
+
.btn-primary:hover { background: var(--accent-hover); transform: translateY(-1px); box-shadow: 0 4px 12px rgba(255,107,53,0.4); }
|
|
281
|
+
.btn-secondary {
|
|
282
|
+
background: var(--bg-input); color: var(--text); border: 1px solid var(--border);
|
|
283
|
+
}
|
|
284
|
+
.btn-secondary:hover { border-color: var(--accent); color: var(--accent); }
|
|
285
|
+
.btn-lg { padding: 14px 32px; font-size: 1.05rem; }
|
|
286
|
+
|
|
287
|
+
/* Results */
|
|
288
|
+
.results-grid {
|
|
289
|
+
display: grid;
|
|
290
|
+
grid-template-columns: 1fr 1fr;
|
|
291
|
+
gap: 16px;
|
|
292
|
+
margin-bottom: 20px;
|
|
293
|
+
}
|
|
294
|
+
@media (max-width: 560px) { .results-grid { grid-template-columns: 1fr; } }
|
|
295
|
+
|
|
296
|
+
.result-card {
|
|
297
|
+
background: var(--bg-input);
|
|
298
|
+
border: 1px solid var(--border);
|
|
299
|
+
border-radius: var(--radius);
|
|
300
|
+
padding: 18px;
|
|
301
|
+
}
|
|
302
|
+
.result-card.accent-card {
|
|
303
|
+
border-color: var(--accent);
|
|
304
|
+
background: rgba(255,107,53,0.08);
|
|
305
|
+
}
|
|
306
|
+
.result-card h3 {
|
|
307
|
+
font-size: 0.78rem;
|
|
308
|
+
font-weight: 700;
|
|
309
|
+
text-transform: uppercase;
|
|
310
|
+
letter-spacing: 0.8px;
|
|
311
|
+
color: var(--text-muted);
|
|
312
|
+
margin-bottom: 8px;
|
|
313
|
+
}
|
|
314
|
+
.result-card .big-number {
|
|
315
|
+
font-size: 2.2rem;
|
|
316
|
+
font-weight: 800;
|
|
317
|
+
color: var(--accent);
|
|
318
|
+
line-height: 1;
|
|
319
|
+
margin-bottom: 4px;
|
|
320
|
+
}
|
|
321
|
+
.result-card .sub { font-size: 0.82rem; color: var(--text-muted); }
|
|
322
|
+
.result-card.full { grid-column: 1 / -1; }
|
|
323
|
+
|
|
324
|
+
.cost-range {
|
|
325
|
+
font-size: 1.1rem;
|
|
326
|
+
font-weight: 700;
|
|
327
|
+
color: var(--text);
|
|
328
|
+
margin-top: 4px;
|
|
329
|
+
}
|
|
330
|
+
.cost-range span { color: var(--accent); }
|
|
331
|
+
|
|
332
|
+
/* Deliverables */
|
|
333
|
+
.deliverables-list {
|
|
334
|
+
list-style: none;
|
|
335
|
+
display: flex;
|
|
336
|
+
flex-direction: column;
|
|
337
|
+
gap: 6px;
|
|
338
|
+
margin-top: 8px;
|
|
339
|
+
}
|
|
340
|
+
.deliverables-list li {
|
|
341
|
+
display: flex; align-items: center; gap: 8px;
|
|
342
|
+
font-size: 0.88rem; color: var(--text);
|
|
343
|
+
}
|
|
344
|
+
.deliverables-list li::before {
|
|
345
|
+
content: '✓';
|
|
346
|
+
color: var(--success);
|
|
347
|
+
font-weight: 700;
|
|
348
|
+
flex-shrink: 0;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* Team pills */
|
|
352
|
+
.team-pills {
|
|
353
|
+
display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px;
|
|
354
|
+
}
|
|
355
|
+
.team-pill {
|
|
356
|
+
background: rgba(255,107,53,0.12);
|
|
357
|
+
border: 1px solid var(--accent);
|
|
358
|
+
color: var(--accent);
|
|
359
|
+
border-radius: 20px;
|
|
360
|
+
padding: 4px 14px;
|
|
361
|
+
font-size: 0.82rem;
|
|
362
|
+
font-weight: 600;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/* CTA */
|
|
366
|
+
.cta-section {
|
|
367
|
+
text-align: center;
|
|
368
|
+
padding: 20px;
|
|
369
|
+
background: linear-gradient(135deg, rgba(255,107,53,0.08), rgba(255,107,53,0.02));
|
|
370
|
+
border: 1px solid rgba(255,107,53,0.3);
|
|
371
|
+
border-radius: var(--radius);
|
|
372
|
+
margin-top: 4px;
|
|
373
|
+
}
|
|
374
|
+
.cta-section p {
|
|
375
|
+
color: var(--text-muted);
|
|
376
|
+
font-size: 0.85rem;
|
|
377
|
+
margin-bottom: 14px;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/* Disclaimer */
|
|
381
|
+
.disclaimer {
|
|
382
|
+
margin-top: 16px;
|
|
383
|
+
padding: 12px 16px;
|
|
384
|
+
background: rgba(255,152,0,0.08);
|
|
385
|
+
border-left: 3px solid var(--warning);
|
|
386
|
+
border-radius: 0 8px 8px 0;
|
|
387
|
+
font-size: 0.8rem;
|
|
388
|
+
color: var(--text-muted);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/* Recalculate */
|
|
392
|
+
.recalc-row {
|
|
393
|
+
display: flex;
|
|
394
|
+
justify-content: center;
|
|
395
|
+
margin-top: 20px;
|
|
396
|
+
}
|
|
397
|
+
`;
|
|
398
|
+
|
|
399
|
+
const TEMPLATE = document.createElement('template');
|
|
400
|
+
TEMPLATE.innerHTML = `<style>${STYLES}</style>
|
|
401
|
+
<div class="header">
|
|
402
|
+
<h1><span class="icon">🔐</span> Penetration Testing Scope Calculator <span class="hailbytes-badge">HailBytes</span></h1>
|
|
403
|
+
<p>Estimate engagement scope, duration, and budget in 3 steps</p>
|
|
404
|
+
</div>
|
|
405
|
+
|
|
406
|
+
<div class="progress-bar-wrap">
|
|
407
|
+
<div class="steps-row">
|
|
408
|
+
<div class="step-item active" data-step="1">
|
|
409
|
+
<div class="step-circle">1</div>
|
|
410
|
+
<span class="step-label">Scope</span>
|
|
411
|
+
</div>
|
|
412
|
+
<div class="step-item" data-step="2">
|
|
413
|
+
<div class="step-circle">2</div>
|
|
414
|
+
<span class="step-label">Depth</span>
|
|
415
|
+
</div>
|
|
416
|
+
<div class="step-item" data-step="3">
|
|
417
|
+
<div class="step-circle">3</div>
|
|
418
|
+
<span class="step-label">Results</span>
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
</div>
|
|
422
|
+
|
|
423
|
+
<div class="body">
|
|
424
|
+
|
|
425
|
+
<!-- STEP 1: SCOPE -->
|
|
426
|
+
<div class="step-panel active" data-panel="1">
|
|
427
|
+
<div class="step-title"><span class="step-icon">🎯</span> Define Your Target Scope</div>
|
|
428
|
+
<div class="field-group">
|
|
429
|
+
<div class="field">
|
|
430
|
+
<label for="target_type">Target Type</label>
|
|
431
|
+
<select id="target_type">
|
|
432
|
+
<option value="web_app">Web Application</option>
|
|
433
|
+
<option value="network">Network / Infrastructure</option>
|
|
434
|
+
<option value="mobile">Mobile Application</option>
|
|
435
|
+
<option value="cloud">Cloud Environment</option>
|
|
436
|
+
<option value="combined">Combined (multiple types)</option>
|
|
437
|
+
</select>
|
|
438
|
+
</div>
|
|
439
|
+
<div class="field">
|
|
440
|
+
<label for="num_targets">Number of Targets</label>
|
|
441
|
+
<input type="number" id="num_targets" min="1" max="500" value="1" placeholder="e.g. 3">
|
|
442
|
+
</div>
|
|
443
|
+
<div class="field">
|
|
444
|
+
<label for="environment">Environment</label>
|
|
445
|
+
<select id="environment">
|
|
446
|
+
<option value="development">Development</option>
|
|
447
|
+
<option value="staging">Staging</option>
|
|
448
|
+
<option value="production">Production</option>
|
|
449
|
+
<option value="all">All Environments</option>
|
|
450
|
+
</select>
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
|
|
454
|
+
<div class="field">
|
|
455
|
+
<label>Testing Options</label>
|
|
456
|
+
<div class="checkbox-group">
|
|
457
|
+
<label class="checkbox-item" id="lbl-auth">
|
|
458
|
+
<input type="checkbox" id="authenticated_testing">
|
|
459
|
+
<span class="checkbox-label">Authenticated Testing<small>Requires valid credentials — tests post-login attack surface</small></span>
|
|
460
|
+
</label>
|
|
461
|
+
<label class="checkbox-item" id="lbl-se">
|
|
462
|
+
<input type="checkbox" id="social_engineering">
|
|
463
|
+
<span class="checkbox-label">Social Engineering<small>Phishing, vishing, pretexting simulations</small></span>
|
|
464
|
+
</label>
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
|
|
468
|
+
<div class="nav-row">
|
|
469
|
+
<span></span>
|
|
470
|
+
<button class="btn btn-primary" id="to-step-2">Next: Testing Depth →</button>
|
|
471
|
+
</div>
|
|
472
|
+
</div>
|
|
473
|
+
|
|
474
|
+
<!-- STEP 2: DEPTH -->
|
|
475
|
+
<div class="step-panel" data-panel="2">
|
|
476
|
+
<div class="step-title"><span class="step-icon">🔍</span> Testing Depth & Compliance</div>
|
|
477
|
+
<div class="field-group">
|
|
478
|
+
<div class="field">
|
|
479
|
+
<label for="test_depth">Testing Depth</label>
|
|
480
|
+
<select id="test_depth">
|
|
481
|
+
<option value="basic">Basic — surface-level review</option>
|
|
482
|
+
<option value="standard" selected>Standard — industry baseline</option>
|
|
483
|
+
<option value="comprehensive">Comprehensive — full coverage</option>
|
|
484
|
+
<option value="red_team">Red Team — adversary simulation</option>
|
|
485
|
+
</select>
|
|
486
|
+
</div>
|
|
487
|
+
<div class="field">
|
|
488
|
+
<label for="report_type">Report Type</label>
|
|
489
|
+
<select id="report_type">
|
|
490
|
+
<option value="executive_summary">Executive Summary</option>
|
|
491
|
+
<option value="technical">Technical Report</option>
|
|
492
|
+
<option value="full_both">Full Report (both)</option>
|
|
493
|
+
</select>
|
|
494
|
+
</div>
|
|
495
|
+
</div>
|
|
496
|
+
|
|
497
|
+
<div class="field">
|
|
498
|
+
<label>Compliance Standards (select all that apply)</label>
|
|
499
|
+
<div class="checkbox-grid" id="compliance-grid">
|
|
500
|
+
<label class="checkbox-item"><input type="checkbox" name="compliance" value="pci_dss"><span class="checkbox-label">PCI DSS<small>Payment card security</small></span></label>
|
|
501
|
+
<label class="checkbox-item"><input type="checkbox" name="compliance" value="hipaa"><span class="checkbox-label">HIPAA<small>Healthcare data</small></span></label>
|
|
502
|
+
<label class="checkbox-item"><input type="checkbox" name="compliance" value="soc2"><span class="checkbox-label">SOC 2<small>Service organization controls</small></span></label>
|
|
503
|
+
<label class="checkbox-item"><input type="checkbox" name="compliance" value="iso27001"><span class="checkbox-label">ISO 27001<small>Information security management</small></span></label>
|
|
504
|
+
<label class="checkbox-item"><input type="checkbox" name="compliance" value="nist"><span class="checkbox-label">NIST<small>National cybersecurity framework</small></span></label>
|
|
505
|
+
<label class="checkbox-item"><input type="checkbox" name="compliance" value="none"><span class="checkbox-label">None<small>No specific compliance requirement</small></span></label>
|
|
506
|
+
</div>
|
|
507
|
+
</div>
|
|
508
|
+
|
|
509
|
+
<div class="field" style="margin-top:16px">
|
|
510
|
+
<label>Add-ons</label>
|
|
511
|
+
<div class="checkbox-group">
|
|
512
|
+
<label class="checkbox-item" id="lbl-remediation">
|
|
513
|
+
<input type="checkbox" id="remediation_support">
|
|
514
|
+
<span class="checkbox-label">Remediation Support<small>30-day post-engagement support included</small></span>
|
|
515
|
+
</label>
|
|
516
|
+
</div>
|
|
517
|
+
</div>
|
|
518
|
+
|
|
519
|
+
<div class="nav-row">
|
|
520
|
+
<button class="btn btn-secondary" id="to-step-1">← Back</button>
|
|
521
|
+
<button class="btn btn-primary" id="to-step-3">Calculate Results →</button>
|
|
522
|
+
</div>
|
|
523
|
+
</div>
|
|
524
|
+
|
|
525
|
+
<!-- STEP 3: RESULTS -->
|
|
526
|
+
<div class="step-panel" data-panel="3">
|
|
527
|
+
<div class="step-title"><span class="step-icon">📊</span> Scope Estimate</div>
|
|
528
|
+
|
|
529
|
+
<div class="results-grid">
|
|
530
|
+
<div class="result-card accent-card">
|
|
531
|
+
<h3>Estimated Duration</h3>
|
|
532
|
+
<div class="big-number" id="res-days">—</div>
|
|
533
|
+
<div class="sub">business days</div>
|
|
534
|
+
</div>
|
|
535
|
+
<div class="result-card">
|
|
536
|
+
<h3>Team Size</h3>
|
|
537
|
+
<div class="big-number" id="res-team">—</div>
|
|
538
|
+
<div class="sub" id="res-team-label">engineers</div>
|
|
539
|
+
</div>
|
|
540
|
+
<div class="result-card full">
|
|
541
|
+
<h3>Estimated Budget Range</h3>
|
|
542
|
+
<div class="cost-range" id="res-cost">—</div>
|
|
543
|
+
<div class="sub">Based on $1,500–$3,500 / engineer-day</div>
|
|
544
|
+
</div>
|
|
545
|
+
<div class="result-card full">
|
|
546
|
+
<h3>Key Deliverables</h3>
|
|
547
|
+
<ul class="deliverables-list" id="res-deliverables"></ul>
|
|
548
|
+
</div>
|
|
549
|
+
<div class="result-card full" id="team-card">
|
|
550
|
+
<h3>Recommended Team Composition</h3>
|
|
551
|
+
<div class="team-pills" id="res-team-pills"></div>
|
|
552
|
+
</div>
|
|
553
|
+
</div>
|
|
554
|
+
|
|
555
|
+
<div class="cta-section">
|
|
556
|
+
<p>These estimates are for budgeting guidance only — actual engagements vary based on findings complexity and remediation scope.</p>
|
|
557
|
+
<button class="btn btn-primary btn-lg" id="quote-btn">🚀 Get an Accurate Quote</button>
|
|
558
|
+
</div>
|
|
559
|
+
|
|
560
|
+
<div class="disclaimer">
|
|
561
|
+
⚠️ <strong>Disclaimer:</strong> All estimates are preliminary and for planning purposes only. Final pricing requires scoping conversation with a HailBytes engineer.
|
|
562
|
+
</div>
|
|
563
|
+
|
|
564
|
+
<div class="recalc-row">
|
|
565
|
+
<button class="btn btn-secondary" id="recalc-btn">← Recalculate</button>
|
|
566
|
+
</div>
|
|
567
|
+
</div>
|
|
568
|
+
|
|
569
|
+
<div class="hb-footer hb-branding">
|
|
570
|
+
by <a href="https://hailbytes.com/asm" target="_blank" rel="noopener">HailBytes</a> — managed attack surface management
|
|
571
|
+
</div>
|
|
572
|
+
|
|
573
|
+
</div>`;
|
|
574
|
+
|
|
575
|
+
class HailbytesPentestCalculator extends HTMLElement {
|
|
576
|
+
static get observedAttributes() { return ['theme', 'branding']; }
|
|
577
|
+
|
|
578
|
+
constructor() {
|
|
579
|
+
super();
|
|
580
|
+
this._shadow = this.attachShadow({ mode: 'open' });
|
|
581
|
+
this._shadow.appendChild(TEMPLATE.content.cloneNode(true));
|
|
582
|
+
this._currentStep = 1;
|
|
583
|
+
this._lastResult = null;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
connectedCallback() {
|
|
587
|
+
this._bindEvents();
|
|
588
|
+
this._updateProgress(1);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
attributeChangedCallback() { /* theme is CSS-only via :host([theme]) */ }
|
|
592
|
+
|
|
593
|
+
// ─── Navigation ────────────────────────────────────────────────────────
|
|
594
|
+
|
|
595
|
+
_bindEvents() {
|
|
596
|
+
const s = this._shadow;
|
|
597
|
+
|
|
598
|
+
s.getElementById('to-step-2').addEventListener('click', () => this._goTo(2));
|
|
599
|
+
s.getElementById('to-step-1').addEventListener('click', () => this._goTo(1));
|
|
600
|
+
s.getElementById('to-step-3').addEventListener('click', () => {
|
|
601
|
+
this._calculate();
|
|
602
|
+
this._goTo(3);
|
|
603
|
+
});
|
|
604
|
+
s.getElementById('recalc-btn').addEventListener('click', () => this._goTo(1));
|
|
605
|
+
s.getElementById('quote-btn').addEventListener('click', () => {
|
|
606
|
+
this.dispatchEvent(new CustomEvent('pentest-quote-requested', {
|
|
607
|
+
bubbles: true, composed: true, detail: this._lastResult
|
|
608
|
+
}));
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
// Checkbox visual state
|
|
612
|
+
s.querySelectorAll('.checkbox-item input[type="checkbox"]').forEach(cb => {
|
|
613
|
+
cb.addEventListener('change', () => {
|
|
614
|
+
const item = cb.closest('.checkbox-item');
|
|
615
|
+
item.classList.toggle('checked', cb.checked);
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
// "None" compliance mutual exclusivity
|
|
620
|
+
const noneBox = s.querySelector('input[value="none"]');
|
|
621
|
+
const otherBoxes = [...s.querySelectorAll('input[name="compliance"]')].filter(c => c.value !== 'none');
|
|
622
|
+
noneBox.addEventListener('change', () => {
|
|
623
|
+
if (noneBox.checked) {
|
|
624
|
+
otherBoxes.forEach(c => { c.checked = false; c.closest('.checkbox-item').classList.remove('checked'); });
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
otherBoxes.forEach(c => {
|
|
628
|
+
c.addEventListener('change', () => {
|
|
629
|
+
if (c.checked) { noneBox.checked = false; noneBox.closest('.checkbox-item').classList.remove('checked'); }
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
_goTo(step) {
|
|
635
|
+
const s = this._shadow;
|
|
636
|
+
s.querySelectorAll('.step-panel').forEach(p => p.classList.remove('active'));
|
|
637
|
+
s.querySelector(`.step-panel[data-panel="${step}"]`).classList.add('active');
|
|
638
|
+
this._currentStep = step;
|
|
639
|
+
this._updateProgress(step);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
_updateProgress(current) {
|
|
643
|
+
this._shadow.querySelectorAll('.step-item').forEach(item => {
|
|
644
|
+
const n = parseInt(item.dataset.step);
|
|
645
|
+
item.classList.remove('active', 'done');
|
|
646
|
+
if (n === current) item.classList.add('active');
|
|
647
|
+
else if (n < current) item.classList.add('done');
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// ─── Calculation Engine ──────────────────────────────────────────────────
|
|
652
|
+
|
|
653
|
+
_calculate() {
|
|
654
|
+
const s = this._shadow;
|
|
655
|
+
|
|
656
|
+
// Collect inputs
|
|
657
|
+
const targetType = s.getElementById('target_type').value;
|
|
658
|
+
const numTargets = Math.min(500, Math.max(1, parseInt(s.getElementById('num_targets').value) || 1));
|
|
659
|
+
const environment = s.getElementById('environment').value;
|
|
660
|
+
const authTesting = s.getElementById('authenticated_testing').checked;
|
|
661
|
+
const socialEng = s.getElementById('social_engineering').checked;
|
|
662
|
+
const testDepth = s.getElementById('test_depth').value;
|
|
663
|
+
const reportType = s.getElementById('report_type').value;
|
|
664
|
+
const remSupport = s.getElementById('remediation_support').checked;
|
|
665
|
+
const compliance = [...s.querySelectorAll('input[name="compliance"]:checked')]
|
|
666
|
+
.map(c => c.value).filter(v => v !== 'none');
|
|
667
|
+
|
|
668
|
+
// ── Base days by target type ────────────────────────────────────────────────
|
|
669
|
+
const baseDays = { web_app: 3, network: 4, mobile: 4, cloud: 3, combined: 6 };
|
|
670
|
+
|
|
671
|
+
// ── num_targets factor ────────────────────────────────────────────────────────
|
|
672
|
+
let targetFactor;
|
|
673
|
+
if (numTargets === 1) targetFactor = 1.0;
|
|
674
|
+
else if (numTargets <= 5) targetFactor = 1.3;
|
|
675
|
+
else if (numTargets <= 15) targetFactor = 1.6;
|
|
676
|
+
else targetFactor = 2.0;
|
|
677
|
+
|
|
678
|
+
// ── depth multiplier ────────────────────────────────────────────────────────
|
|
679
|
+
const depthMult = { basic: 0.7, standard: 1.0, comprehensive: 1.5, red_team: 2.5 };
|
|
680
|
+
|
|
681
|
+
// ── Calculate days ──────────────────────────────────────────────────────────
|
|
682
|
+
let days = baseDays[targetType] * targetFactor * depthMult[testDepth];
|
|
683
|
+
|
|
684
|
+
// Compliance add-ons
|
|
685
|
+
days += compliance.length * 0.5;
|
|
686
|
+
|
|
687
|
+
// Report add-on
|
|
688
|
+
if (reportType === 'full_both') days += 1;
|
|
689
|
+
|
|
690
|
+
// Testing add-ons
|
|
691
|
+
if (authTesting) days += 0.5;
|
|
692
|
+
if (socialEng) days += 2;
|
|
693
|
+
|
|
694
|
+
days = Math.ceil(days * 10) / 10; // 1 decimal
|
|
695
|
+
|
|
696
|
+
// ── Cost range ────────────────────────────────────────────────────────────
|
|
697
|
+
const DAY_RATE_LOW = 1500;
|
|
698
|
+
const DAY_RATE_HIGH = 3500;
|
|
699
|
+
const costLow = Math.round(days * DAY_RATE_LOW);
|
|
700
|
+
const costHigh = Math.round(days * DAY_RATE_HIGH);
|
|
701
|
+
|
|
702
|
+
// ── Team size ────────────────────────────────────────────────────────────────
|
|
703
|
+
let teamSize, teamLabel, teamPills;
|
|
704
|
+
if (testDepth === 'basic' || testDepth === 'standard') {
|
|
705
|
+
teamSize = 1;
|
|
706
|
+
teamLabel = 'engineer';
|
|
707
|
+
teamPills = ['1× Security Engineer'];
|
|
708
|
+
} else if (testDepth === 'comprehensive') {
|
|
709
|
+
teamSize = 2;
|
|
710
|
+
teamLabel = 'engineers';
|
|
711
|
+
teamPills = ['1× Lead Penetration Tester', '1× Security Engineer'];
|
|
712
|
+
} else { // red_team
|
|
713
|
+
teamSize = 3;
|
|
714
|
+
teamLabel = 'engineers (minimum)';
|
|
715
|
+
teamPills = ['1× Red Team Lead', '1× Infrastructure Specialist', '1× Social Engineer'];
|
|
716
|
+
if (socialEng) teamPills.push('1× OSINT Analyst');
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// ── Deliverables ─────────────────────────────────────────────────────────────
|
|
720
|
+
const deliverables = [];
|
|
721
|
+
deliverables.push('Detailed vulnerability findings report');
|
|
722
|
+
deliverables.push('Risk-rated finding inventory');
|
|
723
|
+
if (reportType === 'executive_summary' || reportType === 'full_both') {
|
|
724
|
+
deliverables.push('Executive summary with business impact');
|
|
725
|
+
}
|
|
726
|
+
if (reportType === 'technical' || reportType === 'full_both') {
|
|
727
|
+
deliverables.push('Technical report with proof-of-concept evidence');
|
|
728
|
+
deliverables.push('Step-by-step remediation guidance');
|
|
729
|
+
}
|
|
730
|
+
if (compliance.length > 0) {
|
|
731
|
+
deliverables.push(`Compliance mapping (${compliance.map(c => c.toUpperCase().replace('_', ' ')).join(', ')})`);
|
|
732
|
+
}
|
|
733
|
+
if (authTesting) deliverables.push('Authenticated attack path analysis');
|
|
734
|
+
if (socialEng) deliverables.push('Social engineering campaign report');
|
|
735
|
+
if (remSupport) deliverables.push('30-day remediation support & re-test');
|
|
736
|
+
if (testDepth === 'red_team') {
|
|
737
|
+
deliverables.push('Attack narrative & kill-chain documentation');
|
|
738
|
+
deliverables.push('Detection gap analysis');
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// ── Build result object ──────────────────────────────────────────────────────
|
|
742
|
+
const result = {
|
|
743
|
+
inputs: { targetType, numTargets, environment, authTesting, socialEng, testDepth, compliance, reportType, remSupport },
|
|
744
|
+
days,
|
|
745
|
+
costLow,
|
|
746
|
+
costHigh,
|
|
747
|
+
teamSize,
|
|
748
|
+
teamLabel,
|
|
749
|
+
teamPills,
|
|
750
|
+
deliverables,
|
|
751
|
+
timestamp: new Date().toISOString()
|
|
752
|
+
};
|
|
753
|
+
this._lastResult = result;
|
|
754
|
+
|
|
755
|
+
// ── Render ─────────────────────────────────────────────────────────────────
|
|
756
|
+
this._renderResults(result);
|
|
757
|
+
|
|
758
|
+
// ── Emit event ─────────────────────────────────────────────────────────────
|
|
759
|
+
this.dispatchEvent(new CustomEvent('pentest-calculated', {
|
|
760
|
+
bubbles: true, composed: true, detail: result
|
|
761
|
+
}));
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
_renderResults(r) {
|
|
765
|
+
const s = this._shadow;
|
|
766
|
+
s.getElementById('res-days').textContent = r.days;
|
|
767
|
+
s.getElementById('res-team').textContent = r.teamSize;
|
|
768
|
+
s.getElementById('res-team-label').textContent = r.teamLabel;
|
|
769
|
+
s.getElementById('res-cost').innerHTML =
|
|
770
|
+
`<span>$${r.costLow.toLocaleString()}</span> – <span>$${r.costHigh.toLocaleString()}</span>`;
|
|
771
|
+
|
|
772
|
+
const dl = s.getElementById('res-deliverables');
|
|
773
|
+
dl.innerHTML = r.deliverables.map(d => `<li>${d}</li>`).join('');
|
|
774
|
+
|
|
775
|
+
const tp = s.getElementById('res-team-pills');
|
|
776
|
+
tp.innerHTML = r.teamPills.map(p => `<span class="team-pill">${p}</span>`).join('');
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
customElements.define('hailbytes-pentest-calculator', HailbytesPentestCalculator);
|
|
781
|
+
|
|
782
|
+
export default HailbytesPentestCalculator;
|
|
783
|
+
export { HailbytesPentestCalculator };
|