-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
347 lines (302 loc) · 21.9 KB
/
script.js
File metadata and controls
347 lines (302 loc) · 21.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
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
347
// Generated from YAML data - DO NOT EDIT MANUALLY
const siteData = {"title": "Build better security programs,<br>one metric at a time", "tagline": "Assess your security program health with interactive metrics", "hero": "", "subtitle": "Benchmark your security organization health against industry standards", "footer": "", "text": "Built by and for Security Leaders", "year": 2025};
const metricsData = [{"id": "security-org-size", "title": "Organization Size", "icon": "tenancy", "status": "active", "description": "Track the size and efficiency of your security team relative to your organization", "controls": [{"id": "security-headcount", "label": "Security Team", "type": "range", "min": 1, "max": 500, "default": 10}, {"id": "engineering-headcount", "label": "Engineering/Technology", "type": "range", "min": 10, "max": 5000, "default": 500}, {"id": "company-headcount", "label": "Total Employees", "type": "range", "min": 100, "max": 100000, "default": 1500}], "results": [{"id": "security-vs-eng-percent", "label": "Security vs Engineering", "format": "percentage", "benchmark_key": "security_team_ratio.engineering"}, {"id": "security-vs-company-percent", "label": "Security vs Company", "format": "percentage", "benchmark_key": "security_team_ratio.company"}, {"id": "users-per-security-person", "label": "Users per Security Person", "format": "number", "benchmark_key": "users_per_security_person"}, {"id": "records-per-security-person", "label": "Data Records per Security Person (M)", "format": "number", "benchmark_key": "records_per_security_person"}]}, {"id": "customer-base", "title": "Customer & User Base", "icon": "groups", "status": "active", "description": "Track customer accounts, end users, and data records managed by your organization", "controls": [{"id": "customer-accounts", "label": "Customer Accounts", "type": "range", "min": 10, "max": 1000000, "default": 5000}, {"id": "end-users", "label": "Total End Users", "type": "range", "min": 100, "max": 10000000, "default": 25000}, {"id": "data-records", "label": "Data Records (millions)", "type": "range", "min": 1, "max": 1000, "default": 50}], "results": [{"id": "users-per-customer", "label": "Average Users per Customer", "format": "number", "benchmark_key": "users_per_customer"}, {"id": "records-per-customer", "label": "Data Records per Customer (K)", "format": "number", "benchmark_key": "records_per_customer"}, {"id": "data-density-score", "label": "Data Density Score", "format": "score", "benchmark_key": "data_density_score"}]}, {"id": "vulnerability-management", "title": "Vulnerability Management", "icon": "bug_report", "status": "active", "description": "Track your vulnerability remediation metrics, SLA compliance, and program maturity", "controls": [{"id": "num-repos", "label": "Code Repositories", "type": "range", "min": 1, "max": 10000, "default": 250}, {"id": "num-services", "label": "Services/Applications", "type": "range", "min": 1, "max": 1000, "default": 50}, {"id": "active-vulns", "label": "All Active Vulnerabilities", "type": "range", "min": 0, "max": 10000, "default": 850}, {"id": "exploitable-vulns", "label": "Active Exploitable Vulns", "type": "range", "min": 0, "max": 1000, "default": 45}, {"id": "vulns-past-sla", "label": "Vulnerabilities Past SLA", "type": "range", "min": 0, "max": 1000, "default": 12}, {"id": "avg-days-open", "label": "Average Days Open", "type": "range", "min": 1, "max": 365, "default": 45}], "results": [{"id": "sla-compliance-rate", "label": "SLA Compliance Rate", "format": "percentage", "benchmark_key": "vuln_sla_compliance"}, {"id": "vulns-per-repo", "label": "Vulnerabilities per Repository", "format": "number", "benchmark_key": "vulns_per_repo"}, {"id": "exploitable-percentage", "label": "Exploitable Vulnerability Rate", "format": "percentage", "benchmark_key": "exploitable_vuln_rate"}, {"id": "mean-time-to-fix", "label": "Mean Time to Fix (days)", "format": "number", "benchmark_key": "mean_time_to_fix"}]}, {"id": "security-budget", "title": "Security Budget Analysis", "icon": "account_balance", "status": "active", "description": "Analyze security spending patterns, ROI calculations, and budget optimization", "controls": [{"id": "security-budget", "label": "Annual Security Budget ($M)", "type": "range", "min": 0.1, "max": 100, "default": 2.5, "step": 0.1}, {"id": "operating-costs", "label": "Annual Operating Costs ($M)", "type": "range", "min": 1, "max": 1000, "default": 50}, {"id": "annual-revenue", "label": "Annual Revenue ($M)", "type": "range", "min": 1, "max": 10000, "default": 250}, {"id": "total-employees", "label": "Total Employees", "type": "range", "min": 10, "max": 50000, "default": 2500}], "results": [{"id": "budget-vs-revenue", "label": "Budget vs Revenue", "format": "percentage", "benchmark_key": "budget_vs_revenue"}, {"id": "budget-vs-opex", "label": "Budget vs Operating Costs", "format": "percentage", "benchmark_key": "budget_vs_opex"}, {"id": "budget-per-employee", "label": "Budget per Employee", "format": "currency", "benchmark_key": "budget_per_employee"}, {"id": "cost-per-end-user", "label": "Cost per End User", "format": "currency", "benchmark_key": "cost_per_end_user"}]}, {"id": "training-culture", "title": "Security Training & Culture", "icon": "school", "status": "active", "description": "Measure security awareness, training completion rates, and culture maturity", "controls": [{"id": "training-budget", "label": "Annual Training Budget ($K)", "type": "range", "min": 10, "max": 5000, "default": 250}, {"id": "employees-trained", "label": "Employees Completed Training", "type": "range", "min": 10, "max": 50000, "default": 2200}, {"id": "total-training-employees", "label": "Total Employees (Training Target)", "type": "range", "min": 10, "max": 50000, "default": 2500}, {"id": "phishing-tests-sent", "label": "Phishing Tests Sent", "type": "range", "min": 100, "max": 100000, "default": 12000}, {"id": "phishing-failures", "label": "Phishing Test Failures", "type": "range", "min": 0, "max": 10000, "default": 720}], "results": [{"id": "training-cost-per-employee", "label": "Training Cost per Employee", "format": "currency", "benchmark_key": "training_cost_per_employee"}, {"id": "training-completion-rate", "label": "Training Completion Rate", "format": "percentage", "benchmark_key": "training_completion_rate"}, {"id": "phishing-failure-rate", "label": "Phishing Failure Rate", "format": "percentage", "benchmark_key": "phishing_failure_rate"}, {"id": "security-awareness-score", "label": "Security Awareness Score", "format": "score", "benchmark_key": "security_awareness_score"}]}];
const benchmarks = {"company_sizes": "", "startup": "", "name": "Manufacturing", "employee_range": "10000+", "small": "", "medium": "", "large": "", "enterprise": "", "industries": "", "technology": "", "risk_multiplier": 0.9, "financial": "", "healthcare": "", "retail": "", "manufacturing": "", "benchmarks": "", "security_team_ratio": "", "engineering": "", "healthy": "{ min: 80, max: 100 } # composite awareness score", "warning": "{ min: 60, max: 80 }", "critical": "{ min: 0, max: 60 }", "company": "", "users_per_security_person": "", "records_per_security_person": "", "users_per_customer": "", "saas_b2b": "", "consumer": "", "records_per_customer": "", "data_heavy": "", "data_density_score": "", "vuln_sla_compliance": "", "vulns_per_repo": "", "exploitable_vuln_rate": "", "mean_time_to_fix": "", "budget_per_employee": "", "budget_vs_revenue": "", "budget_vs_opex": "", "cost_per_end_user": "", "saas": "", "training_cost_per_employee": "", "training_completion_rate": "", "phishing_failure_rate": "", "security_awareness_score": ""};
document.addEventListener('DOMContentLoaded', function() {
console.log('Loading metrics calculator with', metricsData.length, 'metric cards');
// Generate metric cards dynamically
generateMetricCards(metricsData);
// Initialize calculations after cards are created
initializeCalculations();
});
function generateMetricCards(metrics) {
const container = document.getElementById('metrics-grid');
if (!container) {
console.error('Metrics grid container not found');
return;
}
// Clear existing content
container.innerHTML = '';
metrics.forEach(metric => {
const card = createMetricCard(metric);
container.appendChild(card);
});
}
function createMetricCard(metric) {
const card = document.createElement('div');
card.className = 'metric-card';
card.id = metric.id + '-card';
card.innerHTML = `
<div class="card-header">
<span class="material-symbols-outlined">${metric.icon}</span>
<h3>${metric.title}</h3>
</div>
<div class="metric-controls">
${generateControls(metric.controls || [], metric.id)}
</div>
<div class="results">
${generateResults(metric.results || [])}
</div>
`;
return card;
}
function generateControls(controls, metricId) {
if (!controls || controls.length === 0) {
return '<p>No controls defined</p>';
}
return controls.map(control => `
<div class="slider-group">
<label for="${control.id}">${control.label}</label>
<div class="slider-container">
<input type="range" id="${control.id}"
min="${control.min}" max="${control.max}"
value="${control.default}"
${control.step ? `step="${control.step}"` : ''}
class="slider" data-metric="${metricId}">
<input type="number" id="${control.id}-input"
min="${control.min}" max="${control.max}"
value="${control.default}"
${control.step ? `step="${control.step}"` : ''}
class="number-input" data-metric="${metricId}">
</div>
</div>
`).join('');
}
function generateResults(results) {
if (!results || results.length === 0) {
return '<p>No results defined</p>';
}
return results.map(result => `
<div class="result-item">
<div class="result-value" id="${result.id}">Calculating...</div>
<div class="result-label">${result.label}</div>
<div class="benchmark" id="${result.id}-benchmark">Calculating...</div>
</div>
`).join('');
}
// Initialize all metrics after cards are created
function initializeCalculations() {
// Benchmark data
const benchmarks = {
security_team_ratio: {
engineering: { healthy: { min: 1.0, max: 2.5 }, warning: { min: 0.5, max: 1.0 }, critical: { min: 0.0, max: 0.5 } },
company: { healthy: { min: 1.0, max: 2.0 }, warning: { min: 0.5, max: 1.0 }, critical: { min: 0.0, max: 0.5 } }
},
users_per_security_person: {
healthy: { min: 50, max: 400 }, warning: { min: 400, max: 800 }, critical: { min: 800, max: 2000 }
},
records_per_security_person: {
healthy: { min: 0.1, max: 5.0 }, warning: { min: 5.0, max: 15.0 }, critical: { min: 15.0, max: 50.0 }
},
vulns_per_repo: {
healthy: { min: 0, max: 2 }, warning: { min: 2, max: 5 }, critical: { min: 5, max: 20 }
},
exploitable_percentage: {
healthy: { min: 0, max: 5 }, warning: { min: 5, max: 15 }, critical: { min: 15, max: 50 }
},
vuln_sla_compliance: {
healthy: { min: 85, max: 100 }, warning: { min: 70, max: 85 }, critical: { min: 0, max: 70 }
},
mean_time_to_fix: {
healthy: { min: 0, max: 30 }, warning: { min: 30, max: 90 }, critical: { min: 90, max: 365 }
},
budget_vs_revenue: {
healthy: { min: 1.0, max: 3.0 }, warning: { min: 0.5, max: 1.0 }, critical: { min: 0.0, max: 0.5 }
},
budget_vs_opex: {
healthy: { min: 2.0, max: 8.0 }, warning: { min: 1.0, max: 2.0 }, critical: { min: 0.0, max: 1.0 }
},
budget_per_employee: {
healthy: { min: 2000, max: 5000 }, warning: { min: 1000, max: 2000 }, critical: { min: 0, max: 1000 }
},
cost_per_end_user: {
healthy: { min: 50, max: 200 }, warning: { min: 20, max: 50 }, critical: { min: 0, max: 20 }
},
training_cost_per_employee: {
healthy: { min: 100, max: 500 }, warning: { min: 50, max: 100 }, critical: { min: 0, max: 50 }
},
training_completion_rate: {
healthy: { min: 90, max: 100 }, warning: { min: 75, max: 90 }, critical: { min: 0, max: 75 }
},
phishing_failure_rate: {
healthy: { min: 0, max: 5 }, warning: { min: 5, max: 15 }, critical: { min: 15, max: 50 }
},
security_awareness_score: {
healthy: { min: 85, max: 100 }, warning: { min: 70, max: 85 }, critical: { min: 0, max: 70 }
}
};
// Initialize event listeners for all cards
metricsData.forEach(metric => {
initializeMetric(metric.id);
});
function initializeMetric(metricId) {
const metricCard = document.getElementById(metricId + '-card');
if (!metricCard) return;
// Find all sliders and inputs for this metric
const sliders = metricCard.querySelectorAll('.slider[data-metric="' + metricId + '"]');
const inputs = metricCard.querySelectorAll('.number-input[data-metric="' + metricId + '"]');
// Set up sync for each slider/input pair
sliders.forEach(slider => {
const input = document.getElementById(slider.id + '-input');
if (input) {
syncSliderAndInput(slider, input, metricId);
}
});
// Initial calculation
calculateMetric(metricId);
}
function syncSliderAndInput(slider, input, metricId) {
slider.addEventListener('input', function () {
input.value = slider.value;
calculateMetric(metricId);
});
input.addEventListener('input', function () {
slider.value = input.value;
calculateMetric(metricId);
});
}
function calculateMetric(metricId) {
switch (metricId) {
case 'security-org-size':
calculateSecurityOrgSize();
break;
case 'customer-base':
calculateCustomerBase();
break;
case 'vulnerability-management':
calculateVulnManagement();
break;
case 'security-budget':
calculateSecurityBudget();
break;
case 'training-culture':
calculateTrainingCulture();
break;
}
}
function calculateSecurityOrgSize() {
const securityHeadcount = getInputValue('security-headcount');
const engineeringHeadcount = getInputValue('engineering-headcount');
const companyHeadcount = getInputValue('company-headcount');
const endUsers = getInputValue('end-users');
const dataRecords = getInputValue('data-records');
const securityVsEngPercent = (securityHeadcount / engineeringHeadcount * 100);
const securityVsCompanyPercent = (securityHeadcount / companyHeadcount * 100);
const usersPerSecurityPerson = Math.round(endUsers / securityHeadcount);
const recordsPerSecurityPerson = (dataRecords / securityHeadcount);
updateResult('security-vs-eng-percent', securityVsEngPercent, 'percentage', 'security_team_ratio.engineering');
updateResult('security-vs-company-percent', securityVsCompanyPercent, 'percentage', 'security_team_ratio.company');
updateResult('users-per-security-person', usersPerSecurityPerson, 'number', 'users_per_security_person');
updateResult('records-per-security-person', recordsPerSecurityPerson, 'number', 'records_per_security_person', ' M');
}
function calculateCustomerBase() {
const customerAccounts = getInputValue('customer-accounts');
const endUsers = getInputValue('end-users');
const dataRecords = getInputValue('data-records');
const usersPerCustomer = (endUsers / customerAccounts);
const recordsPerCustomer = (dataRecords * 1000000 / customerAccounts / 1000); // Convert to K
const dataDensityScore = Math.min(100, Math.round((recordsPerCustomer / 10) * 25 + (usersPerCustomer / 10) * 25 + 50));
updateResult('users-per-customer', usersPerCustomer, 'number', 'users_per_customer');
updateResult('records-per-customer', recordsPerCustomer, 'number', 'records_per_customer', ' K');
updateResult('data-density-score', dataDensityScore, 'score', 'data_density_score');
}
function calculateVulnManagement() {
const numRepos = getInputValue('num-repos');
const activeVulns = getInputValue('active-vulns');
const exploitableVulns = getInputValue('exploitable-vulns');
const vulnsPastSla = getInputValue('vulns-past-sla');
const avgDaysOpen = getInputValue('avg-days-open');
const slaComplianceRate = ((activeVulns - vulnsPastSla) / activeVulns * 100);
const vulnsPerRepo = (activeVulns / numRepos);
const exploitablePercentage = (exploitableVulns / activeVulns * 100);
const meanTimeToFix = avgDaysOpen;
updateResult('sla-compliance-rate', slaComplianceRate, 'percentage', 'vuln_sla_compliance');
updateResult('vulns-per-repo', vulnsPerRepo, 'number', 'vulns_per_repo');
updateResult('exploitable-percentage', exploitablePercentage, 'percentage', 'exploitable_percentage');
updateResult('mean-time-to-fix', meanTimeToFix, 'number', 'mean_time_to_fix', ' days');
}
function calculateSecurityBudget() {
const securityBudget = getInputValue('security-budget') * 1000000; // Convert to dollars
const annualRevenue = getInputValue('annual-revenue') * 1000000;
const operatingCosts = getInputValue('operating-costs') * 1000000;
const totalEmployees = getInputValue('total-employees');
const endUsers = getInputValue('end-users') || 25000; // Fallback if not available
const budgetVsRevenue = (securityBudget / annualRevenue * 100);
const budgetVsOpex = (securityBudget / operatingCosts * 100);
const budgetPerEmployee = (securityBudget / totalEmployees);
const costPerEndUser = (securityBudget / endUsers);
updateResult('budget-vs-revenue', budgetVsRevenue, 'percentage', 'budget_vs_revenue');
updateResult('budget-vs-opex', budgetVsOpex, 'percentage', 'budget_vs_opex');
updateResult('budget-per-employee', budgetPerEmployee, 'currency', 'budget_per_employee');
updateResult('cost-per-end-user', costPerEndUser, 'currency', 'cost_per_end_user');
}
function calculateTrainingCulture() {
const trainingBudget = getInputValue('training-budget') * 1000; // Convert to dollars
const employeesTrained = getInputValue('employees-trained');
const totalEmployees = getInputValue('total-training-employees');
const phishingTestsSent = getInputValue('phishing-tests-sent');
const phishingFailures = getInputValue('phishing-failures');
const trainingCostPerEmployee = (trainingBudget / totalEmployees);
const trainingCompletionRate = (employeesTrained / totalEmployees * 100);
const phishingFailureRate = (phishingFailures / phishingTestsSent * 100);
const securityAwarenessScore = Math.round((100 - phishingFailureRate) * 0.6 + trainingCompletionRate * 0.4);
updateResult('training-cost-per-employee', trainingCostPerEmployee, 'currency', 'training_cost_per_employee');
updateResult('training-completion-rate', trainingCompletionRate, 'percentage', 'training_completion_rate');
updateResult('phishing-failure-rate', phishingFailureRate, 'percentage', 'phishing_failure_rate');
updateResult('security-awareness-score', securityAwarenessScore, 'score', 'security_awareness_score');
}
function getInputValue(id) {
const element = document.getElementById(id) || document.getElementById(id + '-input');
return element ? parseFloat(element.value) || 0 : 0;
}
function updateResult(elementId, value, format, benchmarkKey, suffix = '') {
const element = document.getElementById(elementId);
if (!element) return;
let formattedValue;
switch (format) {
case 'percentage':
formattedValue = value.toFixed(1) + '%';
break;
case 'currency':
formattedValue = '$' + Math.round(value).toLocaleString();
break;
case 'score':
formattedValue = Math.round(value) + '/100';
break;
default:
formattedValue = value.toFixed(1) + suffix;
}
element.textContent = formattedValue;
// Update benchmark
const benchmarkElement = document.getElementById(elementId + '-benchmark');
if (benchmarkElement && benchmarkKey) {
updateBenchmark(benchmarkElement, value, benchmarkKey);
}
}
function updateBenchmark(element, value, benchmarkKey) {
const benchmark = getBenchmark(benchmarkKey);
if (!benchmark) {
element.textContent = 'No benchmark';
return;
}
if (value >= benchmark.healthy.min && value <= benchmark.healthy.max) {
element.textContent = `Healthy: ${benchmark.healthy.min}-${benchmark.healthy.max}`;
element.className = 'benchmark healthy';
} else if (value >= benchmark.warning.min && value <= benchmark.warning.max) {
element.textContent = `Warning: ${benchmark.warning.min}-${benchmark.warning.max}`;
element.className = 'benchmark warning';
} else {
element.textContent = `Critical: ${benchmark.critical.min}-${benchmark.critical.max}`;
element.className = 'benchmark critical';
}
}
function getBenchmark(key) {
const keys = key.split('.');
let result = benchmarks;
for (const k of keys) {
result = result[k];
if (!result) return null;
}
return result;
}
}