recker 1.0.15-next.eb07368 → 1.0.16

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.
Files changed (108) hide show
  1. package/README.md +10 -1
  2. package/dist/ai/providers/anthropic.d.ts.map +1 -1
  3. package/dist/ai/providers/anthropic.js +4 -1
  4. package/dist/ai/providers/base.d.ts.map +1 -1
  5. package/dist/ai/providers/base.js +7 -2
  6. package/dist/ai/rate-limiter.d.ts.map +1 -1
  7. package/dist/ai/rate-limiter.js +4 -1
  8. package/dist/bench/generator.d.ts.map +1 -1
  9. package/dist/bench/generator.js +7 -3
  10. package/dist/bench/stats.d.ts.map +1 -1
  11. package/dist/bench/stats.js +43 -10
  12. package/dist/cache/memory-storage.d.ts.map +1 -1
  13. package/dist/cache/memory-storage.js +3 -2
  14. package/dist/cli/handler.js +14 -14
  15. package/dist/cli/index.js +533 -79
  16. package/dist/cli/presets.js +5 -5
  17. package/dist/cli/tui/ai-chat.js +10 -10
  18. package/dist/cli/tui/load-dashboard.d.ts.map +1 -1
  19. package/dist/cli/tui/load-dashboard.js +96 -55
  20. package/dist/cli/tui/shell.d.ts +3 -0
  21. package/dist/cli/tui/shell.d.ts.map +1 -1
  22. package/dist/cli/tui/shell.js +163 -1
  23. package/dist/cli/tui/websocket.js +17 -17
  24. package/dist/core/client.d.ts.map +1 -1
  25. package/dist/core/client.js +18 -26
  26. package/dist/core/errors.d.ts +109 -1
  27. package/dist/core/errors.d.ts.map +1 -1
  28. package/dist/core/errors.js +214 -1
  29. package/dist/core/request-promise.d.ts.map +1 -1
  30. package/dist/core/request-promise.js +5 -6
  31. package/dist/core/response.d.ts.map +1 -1
  32. package/dist/core/response.js +5 -6
  33. package/dist/dns/propagation.d.ts +3 -1
  34. package/dist/dns/propagation.d.ts.map +1 -1
  35. package/dist/dns/propagation.js +99 -59
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -0
  39. package/dist/mcp/client.d.ts.map +1 -1
  40. package/dist/mcp/client.js +10 -11
  41. package/dist/mcp/embeddings-loader.d.ts.map +1 -1
  42. package/dist/mcp/embeddings-loader.js +12 -2
  43. package/dist/mcp/geoip-loader.d.ts +11 -0
  44. package/dist/mcp/geoip-loader.d.ts.map +1 -0
  45. package/dist/mcp/geoip-loader.js +107 -0
  46. package/dist/mcp/ip-intel.d.ts +28 -0
  47. package/dist/mcp/ip-intel.d.ts.map +1 -0
  48. package/dist/mcp/ip-intel.js +209 -0
  49. package/dist/mcp/search/hybrid-search.d.ts.map +1 -1
  50. package/dist/mcp/search/hybrid-search.js +5 -1
  51. package/dist/mcp/search/math.d.ts.map +1 -1
  52. package/dist/mcp/search/math.js +5 -1
  53. package/dist/mcp/server.d.ts +4 -0
  54. package/dist/mcp/server.d.ts.map +1 -1
  55. package/dist/mcp/server.js +114 -1
  56. package/dist/plugins/compression.js +4 -2
  57. package/dist/plugins/har-player.d.ts.map +1 -1
  58. package/dist/plugins/har-player.js +8 -11
  59. package/dist/plugins/odata.d.ts.map +1 -1
  60. package/dist/plugins/odata.js +5 -2
  61. package/dist/protocols/ftp.d.ts.map +1 -1
  62. package/dist/protocols/ftp.js +69 -16
  63. package/dist/protocols/sftp.d.ts.map +1 -1
  64. package/dist/protocols/sftp.js +13 -3
  65. package/dist/protocols/telnet.d.ts.map +1 -1
  66. package/dist/protocols/telnet.js +25 -6
  67. package/dist/transport/base-udp.d.ts.map +1 -1
  68. package/dist/transport/base-udp.js +7 -4
  69. package/dist/transport/udp-response.d.ts.map +1 -1
  70. package/dist/transport/udp-response.js +10 -3
  71. package/dist/transport/udp.d.ts.map +1 -1
  72. package/dist/transport/udp.js +5 -1
  73. package/dist/transport/undici.d.ts.map +1 -1
  74. package/dist/transport/undici.js +3 -11
  75. package/dist/utils/agent-manager.d.ts +1 -0
  76. package/dist/utils/agent-manager.d.ts.map +1 -1
  77. package/dist/utils/agent-manager.js +11 -0
  78. package/dist/utils/client-pool.d.ts.map +1 -1
  79. package/dist/utils/client-pool.js +4 -1
  80. package/dist/utils/dns-toolkit.d.ts +88 -1
  81. package/dist/utils/dns-toolkit.d.ts.map +1 -1
  82. package/dist/utils/dns-toolkit.js +704 -6
  83. package/dist/utils/doh.d.ts.map +1 -1
  84. package/dist/utils/doh.js +13 -16
  85. package/dist/utils/download.d.ts.map +1 -1
  86. package/dist/utils/download.js +10 -11
  87. package/dist/utils/rdap.d.ts +9 -0
  88. package/dist/utils/rdap.d.ts.map +1 -1
  89. package/dist/utils/rdap.js +78 -9
  90. package/dist/utils/security-grader.d.ts +33 -0
  91. package/dist/utils/security-grader.d.ts.map +1 -1
  92. package/dist/utils/security-grader.js +548 -43
  93. package/dist/utils/sparkline.d.ts +18 -0
  94. package/dist/utils/sparkline.d.ts.map +1 -0
  95. package/dist/utils/sparkline.js +55 -0
  96. package/dist/utils/sse.d.ts.map +1 -1
  97. package/dist/utils/sse.js +5 -6
  98. package/dist/utils/system-metrics.d.ts +26 -0
  99. package/dist/utils/system-metrics.d.ts.map +1 -0
  100. package/dist/utils/system-metrics.js +81 -0
  101. package/dist/webrtc/index.d.ts.map +1 -1
  102. package/dist/webrtc/index.js +21 -7
  103. package/dist/websocket/client.d.ts.map +1 -1
  104. package/dist/websocket/client.js +13 -16
  105. package/package.json +3 -2
  106. package/dist/utils/ip-intel.d.ts +0 -15
  107. package/dist/utils/ip-intel.d.ts.map +0 -1
  108. package/dist/utils/ip-intel.js +0 -30
@@ -1,91 +1,556 @@
1
+ const CSP_FETCH_DIRECTIVES = [
2
+ 'default-src', 'script-src', 'style-src', 'img-src', 'font-src',
3
+ 'connect-src', 'media-src', 'object-src', 'frame-src', 'child-src',
4
+ 'worker-src', 'manifest-src', 'prefetch-src'
5
+ ];
6
+ const CSP_DOCUMENT_DIRECTIVES = [
7
+ 'base-uri', 'sandbox', 'form-action', 'frame-ancestors',
8
+ 'navigate-to'
9
+ ];
10
+ const CSP_REPORTING_DIRECTIVES = [
11
+ 'report-uri', 'report-to'
12
+ ];
13
+ const CSP_SPECIAL_DIRECTIVES = [
14
+ 'upgrade-insecure-requests', 'block-all-mixed-content',
15
+ 'require-trusted-types-for', 'trusted-types'
16
+ ];
17
+ const DANGEROUS_CSP_VALUES = {
18
+ "'unsafe-inline'": 'Allows inline scripts/styles. Use nonces or hashes instead.',
19
+ "'unsafe-eval'": 'Allows eval() and similar. Major XSS risk.',
20
+ "'unsafe-hashes'": 'Allows specific inline event handlers. Prefer nonces.',
21
+ '*': 'Wildcard allows loading from any source.',
22
+ 'data:': 'Data URIs can be used for XSS attacks.',
23
+ 'blob:': 'Blob URLs can bypass CSP in some cases.',
24
+ 'http:': 'Allows insecure HTTP sources. Use HTTPS only.',
25
+ };
26
+ const SAFE_CSP_VALUES = new Set([
27
+ "'self'", "'none'", "'strict-dynamic'", "'report-sample'",
28
+ 'https:', "'wasm-unsafe-eval'"
29
+ ]);
30
+ export function analyzeCSP(cspValue) {
31
+ const directives = [];
32
+ const issues = [];
33
+ let score = 100;
34
+ let hasUnsafeInline = false;
35
+ let hasUnsafeEval = false;
36
+ let hasWildcard = false;
37
+ const parts = cspValue.split(';').map(p => p.trim()).filter(Boolean);
38
+ const directiveNames = new Set();
39
+ for (const part of parts) {
40
+ const [name, ...values] = part.split(/\s+/);
41
+ const directiveName = name.toLowerCase();
42
+ directiveNames.add(directiveName);
43
+ const directive = {
44
+ name: directiveName,
45
+ values,
46
+ issues: [],
47
+ severity: 'safe'
48
+ };
49
+ for (const val of values) {
50
+ const lowerVal = val.toLowerCase();
51
+ if (lowerVal === "'unsafe-inline'") {
52
+ hasUnsafeInline = true;
53
+ directive.issues.push(DANGEROUS_CSP_VALUES["'unsafe-inline'"]);
54
+ directive.severity = 'dangerous';
55
+ }
56
+ else if (lowerVal === "'unsafe-eval'") {
57
+ hasUnsafeEval = true;
58
+ directive.issues.push(DANGEROUS_CSP_VALUES["'unsafe-eval'"]);
59
+ directive.severity = 'dangerous';
60
+ }
61
+ else if (val === '*') {
62
+ hasWildcard = true;
63
+ directive.issues.push(DANGEROUS_CSP_VALUES['*']);
64
+ directive.severity = 'dangerous';
65
+ }
66
+ else if (lowerVal === 'data:' && ['script-src', 'default-src'].includes(directiveName)) {
67
+ directive.issues.push(DANGEROUS_CSP_VALUES['data:']);
68
+ directive.severity = 'warn';
69
+ }
70
+ else if (lowerVal === 'http:') {
71
+ directive.issues.push(DANGEROUS_CSP_VALUES['http:']);
72
+ directive.severity = 'warn';
73
+ }
74
+ else if (val.startsWith('*.') || val.includes('*')) {
75
+ directive.issues.push(`Wildcard domain "${val}" is overly permissive.`);
76
+ directive.severity = directive.severity === 'dangerous' ? 'dangerous' : 'warn';
77
+ }
78
+ }
79
+ directives.push(directive);
80
+ }
81
+ const missingDirectives = [];
82
+ if (!directiveNames.has('default-src')) {
83
+ missingDirectives.push('default-src');
84
+ issues.push('Missing default-src directive. Fallback behavior is unpredictable.');
85
+ score -= 10;
86
+ }
87
+ if (!directiveNames.has('object-src') && !directiveNames.has('default-src')) {
88
+ missingDirectives.push('object-src');
89
+ issues.push("Missing object-src. Consider adding object-src 'none' to block plugins.");
90
+ score -= 5;
91
+ }
92
+ if (!directiveNames.has('base-uri')) {
93
+ missingDirectives.push('base-uri');
94
+ issues.push("Missing base-uri. Consider adding base-uri 'self' to prevent base tag injection.");
95
+ score -= 5;
96
+ }
97
+ if (!directiveNames.has('form-action')) {
98
+ missingDirectives.push('form-action');
99
+ issues.push('Missing form-action. Form submissions can be hijacked.');
100
+ score -= 5;
101
+ }
102
+ if (!directiveNames.has('frame-ancestors')) {
103
+ missingDirectives.push('frame-ancestors');
104
+ issues.push('Missing frame-ancestors. Use this instead of X-Frame-Options for modern browsers.');
105
+ score -= 5;
106
+ }
107
+ if (hasUnsafeInline) {
108
+ issues.push("'unsafe-inline' allows inline scripts. Major XSS vulnerability.");
109
+ score -= 20;
110
+ }
111
+ if (hasUnsafeEval) {
112
+ issues.push("'unsafe-eval' allows eval(). Dangerous for code injection.");
113
+ score -= 20;
114
+ }
115
+ if (hasWildcard) {
116
+ issues.push('Wildcard (*) source allows loading from any origin.');
117
+ score -= 15;
118
+ }
119
+ return {
120
+ raw: cspValue,
121
+ directives,
122
+ issues,
123
+ score: Math.max(0, score),
124
+ hasUnsafeInline,
125
+ hasUnsafeEval,
126
+ hasWildcard,
127
+ missingDirectives
128
+ };
129
+ }
130
+ export function generateRecommendedCSP(options = {}) {
131
+ const { strictMode = true, allowInlineStyles = false, trustedDomains = [] } = options;
132
+ const directives = [];
133
+ directives.push("default-src 'self'");
134
+ if (strictMode) {
135
+ directives.push("script-src 'self' 'strict-dynamic'");
136
+ }
137
+ else {
138
+ directives.push("script-src 'self'" + (trustedDomains.length ? ' ' + trustedDomains.join(' ') : ''));
139
+ }
140
+ if (allowInlineStyles) {
141
+ directives.push("style-src 'self' 'unsafe-inline'");
142
+ }
143
+ else {
144
+ directives.push("style-src 'self'");
145
+ }
146
+ directives.push("img-src 'self' data: https:");
147
+ directives.push("font-src 'self'");
148
+ directives.push("connect-src 'self'" + (trustedDomains.length ? ' ' + trustedDomains.join(' ') : ''));
149
+ directives.push("object-src 'none'");
150
+ directives.push("base-uri 'self'");
151
+ directives.push("form-action 'self'");
152
+ directives.push("frame-ancestors 'none'");
153
+ directives.push('upgrade-insecure-requests');
154
+ return directives.join('; ');
155
+ }
1
156
  const HEADERS_CHECKS = [
2
157
  {
3
158
  header: 'strict-transport-security',
4
159
  weight: 25,
160
+ category: 'transport',
5
161
  check: (val) => {
6
- if (!val)
7
- return { status: 'fail', message: 'HSTS not enabled. Vulnerable to SSL stripping.' };
8
- if (!val.includes('max-age='))
9
- return { status: 'fail', message: 'Invalid HSTS: missing max-age.' };
10
- const maxAge = parseInt(val.match(/max-age=(\d+)/)?.[1] || '0');
11
- if (maxAge < 15552000)
12
- return { status: 'warn', message: 'HSTS max-age is less than 6 months.' };
13
- if (!val.includes('includeSubDomains'))
14
- return { status: 'warn', message: 'HSTS does not include subdomains.' };
15
- return { status: 'pass', message: 'HSTS enabled with long duration.' };
162
+ if (!val) {
163
+ return {
164
+ status: 'fail',
165
+ message: 'HSTS not enabled. Vulnerable to SSL stripping attacks.',
166
+ recommendation: 'Add: Strict-Transport-Security: max-age=31536000; includeSubDomains; preload'
167
+ };
168
+ }
169
+ const maxAge = parseInt(val.match(/max-age=(\d+)/i)?.[1] || '0');
170
+ const hasSubDomains = val.toLowerCase().includes('includesubdomains');
171
+ const hasPreload = val.toLowerCase().includes('preload');
172
+ if (maxAge < 86400) {
173
+ return {
174
+ status: 'fail',
175
+ message: `HSTS max-age too short (${maxAge}s). Minimum 1 day recommended.`,
176
+ recommendation: 'Set max-age to at least 31536000 (1 year)'
177
+ };
178
+ }
179
+ if (maxAge < 15552000) {
180
+ return {
181
+ status: 'warn',
182
+ message: `HSTS max-age is ${Math.floor(maxAge / 86400)} days. 6+ months recommended.`,
183
+ recommendation: 'Increase max-age to 31536000 (1 year) for better security'
184
+ };
185
+ }
186
+ if (!hasSubDomains) {
187
+ return {
188
+ status: 'warn',
189
+ message: 'HSTS does not include subdomains.',
190
+ recommendation: 'Add includeSubDomains to protect all subdomains'
191
+ };
192
+ }
193
+ if (hasPreload) {
194
+ return { status: 'pass', message: 'HSTS enabled with preload. Excellent!' };
195
+ }
196
+ return { status: 'pass', message: 'HSTS enabled with good configuration.' };
16
197
  }
17
198
  },
18
199
  {
19
200
  header: 'content-security-policy',
20
201
  weight: 25,
202
+ category: 'content',
21
203
  check: (val) => {
22
- if (!val)
23
- return { status: 'fail', message: 'CSP is missing. Vulnerable to XSS.' };
24
- if (val.includes("'unsafe-inline'") || val.includes("'unsafe-eval'"))
25
- return { status: 'warn', message: 'CSP includes unsafe directives.' };
26
- if (val.includes('default-src *') || val.includes('script-src *'))
27
- return { status: 'warn', message: 'CSP too permissive (*).' };
28
- return { status: 'pass', message: 'CSP enabled.' };
204
+ if (!val) {
205
+ return {
206
+ status: 'fail',
207
+ message: 'CSP is missing. Site is vulnerable to XSS attacks.',
208
+ recommendation: "Add a CSP. Start with: Content-Security-Policy: default-src 'self'"
209
+ };
210
+ }
211
+ const analysis = analyzeCSP(val);
212
+ if (analysis.hasUnsafeEval && analysis.hasUnsafeInline) {
213
+ return {
214
+ status: 'fail',
215
+ message: 'CSP has both unsafe-inline and unsafe-eval. Provides minimal protection.',
216
+ recommendation: 'Remove unsafe directives. Use nonces or hashes for inline scripts.'
217
+ };
218
+ }
219
+ if (analysis.hasWildcard) {
220
+ return {
221
+ status: 'warn',
222
+ message: 'CSP contains wildcard (*). Too permissive.',
223
+ recommendation: 'Replace wildcards with specific trusted domains.'
224
+ };
225
+ }
226
+ if (analysis.hasUnsafeInline) {
227
+ return {
228
+ status: 'warn',
229
+ message: "CSP has 'unsafe-inline'. Consider using nonces or hashes.",
230
+ recommendation: "Use 'strict-dynamic' with nonces for script-src."
231
+ };
232
+ }
233
+ if (analysis.hasUnsafeEval) {
234
+ return {
235
+ status: 'warn',
236
+ message: "CSP has 'unsafe-eval'. Allows eval() which is dangerous.",
237
+ recommendation: 'Remove unsafe-eval. Refactor code to avoid eval().'
238
+ };
239
+ }
240
+ if (analysis.missingDirectives.length > 2) {
241
+ return {
242
+ status: 'warn',
243
+ message: `CSP missing important directives: ${analysis.missingDirectives.join(', ')}`,
244
+ recommendation: `Add: ${analysis.missingDirectives.map(d => `${d} 'self'`).join('; ')}`
245
+ };
246
+ }
247
+ return { status: 'pass', message: 'CSP is well configured.' };
29
248
  }
30
249
  },
31
250
  {
32
251
  header: 'x-frame-options',
33
- weight: 15,
252
+ weight: 10,
253
+ category: 'framing',
34
254
  check: (val) => {
35
- if (!val)
36
- return { status: 'fail', message: 'Missing X-Frame-Options. Vulnerable to Clickjacking.' };
37
- if (val.toUpperCase() === 'DENY' || val.toUpperCase() === 'SAMEORIGIN')
38
- return { status: 'pass', message: 'Clickjacking protection enabled.' };
39
- return { status: 'warn', message: 'X-Frame-Options set but might be permissive.' };
255
+ if (!val) {
256
+ return {
257
+ status: 'warn',
258
+ message: 'Missing X-Frame-Options. Use CSP frame-ancestors instead.',
259
+ recommendation: 'Add X-Frame-Options: DENY or use CSP frame-ancestors'
260
+ };
261
+ }
262
+ const upper = val.toUpperCase();
263
+ if (upper === 'DENY') {
264
+ return { status: 'pass', message: 'Clickjacking protection: DENY (strictest).' };
265
+ }
266
+ if (upper === 'SAMEORIGIN') {
267
+ return { status: 'pass', message: 'Clickjacking protection: SAMEORIGIN.' };
268
+ }
269
+ if (upper.startsWith('ALLOW-FROM')) {
270
+ return {
271
+ status: 'warn',
272
+ message: 'ALLOW-FROM is deprecated and not supported in modern browsers.',
273
+ recommendation: 'Use CSP frame-ancestors instead of ALLOW-FROM'
274
+ };
275
+ }
276
+ return { status: 'warn', message: `Unknown X-Frame-Options value: ${val}` };
40
277
  }
41
278
  },
42
279
  {
43
280
  header: 'x-content-type-options',
44
281
  weight: 10,
282
+ category: 'content',
45
283
  check: (val) => {
46
- if (!val)
47
- return { status: 'fail', message: 'Missing X-Content-Type-Options.' };
48
- if (val.toLowerCase() === 'nosniff')
284
+ if (!val) {
285
+ return {
286
+ status: 'fail',
287
+ message: 'Missing X-Content-Type-Options. MIME sniffing attacks possible.',
288
+ recommendation: 'Add: X-Content-Type-Options: nosniff'
289
+ };
290
+ }
291
+ if (val.toLowerCase() === 'nosniff') {
49
292
  return { status: 'pass', message: 'MIME sniffing disabled.' };
50
- return { status: 'fail', message: 'Value must be "nosniff".' };
293
+ }
294
+ return {
295
+ status: 'fail',
296
+ message: `Invalid value "${val}". Must be "nosniff".`,
297
+ recommendation: 'Set value to exactly: nosniff'
298
+ };
51
299
  }
52
300
  },
53
301
  {
54
302
  header: 'referrer-policy',
55
303
  weight: 10,
304
+ category: 'content',
56
305
  check: (val) => {
57
- if (!val)
58
- return { status: 'warn', message: 'Missing Referrer-Policy.' };
59
- if (val.includes('no-referrer') || val.includes('same-origin') || val.includes('strict-origin'))
60
- return { status: 'pass', message: 'Referrer leakage limited.' };
61
- return { status: 'warn', message: 'Referrer-Policy might leak information.' };
306
+ if (!val) {
307
+ return {
308
+ status: 'warn',
309
+ message: 'Missing Referrer-Policy. URL may leak to third parties.',
310
+ recommendation: 'Add: Referrer-Policy: strict-origin-when-cross-origin'
311
+ };
312
+ }
313
+ const policies = val.toLowerCase().split(',').map(p => p.trim());
314
+ const safePolicies = ['no-referrer', 'same-origin', 'strict-origin', 'strict-origin-when-cross-origin'];
315
+ const unsafePolicies = ['unsafe-url', 'no-referrer-when-downgrade'];
316
+ for (const policy of policies) {
317
+ if (unsafePolicies.includes(policy)) {
318
+ return {
319
+ status: 'warn',
320
+ message: `"${policy}" may leak referrer to third parties.`,
321
+ recommendation: 'Use strict-origin-when-cross-origin for balanced privacy'
322
+ };
323
+ }
324
+ }
325
+ if (policies.some(p => safePolicies.includes(p))) {
326
+ return { status: 'pass', message: 'Referrer leakage properly restricted.' };
327
+ }
328
+ return { status: 'warn', message: `Unknown policy: ${val}` };
62
329
  }
63
330
  },
64
331
  {
65
332
  header: 'permissions-policy',
66
333
  weight: 10,
334
+ category: 'content',
67
335
  check: (val) => {
68
- if (!val)
69
- return { status: 'warn', message: 'Missing Permissions-Policy (Feature-Policy).' };
336
+ if (!val) {
337
+ return {
338
+ status: 'warn',
339
+ message: 'Missing Permissions-Policy. Browser features unrestricted.',
340
+ recommendation: 'Add: Permissions-Policy: geolocation=(), camera=(), microphone=()'
341
+ };
342
+ }
343
+ if (val.includes('*')) {
344
+ return {
345
+ status: 'warn',
346
+ message: 'Permissions-Policy allows all origins for some features.',
347
+ recommendation: 'Restrict features to self or specific origins'
348
+ };
349
+ }
350
+ const features = val.split(',').length;
351
+ if (features >= 5) {
352
+ return { status: 'pass', message: `Permissions-Policy restricts ${features} features.` };
353
+ }
70
354
  return { status: 'pass', message: 'Permissions-Policy enabled.' };
71
355
  }
72
356
  },
73
357
  {
74
- header: 'server',
358
+ header: 'cross-origin-opener-policy',
359
+ weight: 8,
360
+ category: 'isolation',
361
+ check: (val) => {
362
+ if (!val) {
363
+ return {
364
+ status: 'warn',
365
+ message: 'Missing COOP. Site may be vulnerable to cross-origin attacks.',
366
+ recommendation: 'Add: Cross-Origin-Opener-Policy: same-origin'
367
+ };
368
+ }
369
+ const lower = val.toLowerCase();
370
+ if (lower === 'same-origin') {
371
+ return { status: 'pass', message: 'COOP: same-origin. Strong isolation enabled.' };
372
+ }
373
+ if (lower === 'same-origin-allow-popups') {
374
+ return { status: 'pass', message: 'COOP allows popups but maintains isolation.' };
375
+ }
376
+ if (lower === 'unsafe-none') {
377
+ return {
378
+ status: 'warn',
379
+ message: 'COOP is unsafe-none. No isolation.',
380
+ recommendation: 'Use same-origin for better security'
381
+ };
382
+ }
383
+ return { status: 'warn', message: `Unknown COOP value: ${val}` };
384
+ }
385
+ },
386
+ {
387
+ header: 'cross-origin-embedder-policy',
388
+ weight: 8,
389
+ category: 'isolation',
390
+ check: (val) => {
391
+ if (!val) {
392
+ return {
393
+ status: 'warn',
394
+ message: 'Missing COEP. Required for SharedArrayBuffer and high-resolution timers.',
395
+ recommendation: 'Add: Cross-Origin-Embedder-Policy: require-corp'
396
+ };
397
+ }
398
+ const lower = val.toLowerCase();
399
+ if (lower === 'require-corp') {
400
+ return { status: 'pass', message: 'COEP: require-corp. Cross-origin isolation enabled.' };
401
+ }
402
+ if (lower === 'credentialless') {
403
+ return { status: 'pass', message: 'COEP: credentialless. Good isolation with flexibility.' };
404
+ }
405
+ if (lower === 'unsafe-none') {
406
+ return {
407
+ status: 'warn',
408
+ message: 'COEP is unsafe-none. No cross-origin isolation.',
409
+ recommendation: 'Use require-corp or credentialless'
410
+ };
411
+ }
412
+ return { status: 'warn', message: `Unknown COEP value: ${val}` };
413
+ }
414
+ },
415
+ {
416
+ header: 'cross-origin-resource-policy',
417
+ weight: 5,
418
+ category: 'isolation',
419
+ check: (val) => {
420
+ if (!val) {
421
+ return {
422
+ status: 'warn',
423
+ message: 'Missing CORP. Resources can be embedded by any site.',
424
+ recommendation: 'Add: Cross-Origin-Resource-Policy: same-origin'
425
+ };
426
+ }
427
+ const lower = val.toLowerCase();
428
+ if (lower === 'same-origin') {
429
+ return { status: 'pass', message: 'CORP: same-origin. Strictest embedding restriction.' };
430
+ }
431
+ if (lower === 'same-site') {
432
+ return { status: 'pass', message: 'CORP: same-site. Allows same-site embedding.' };
433
+ }
434
+ if (lower === 'cross-origin') {
435
+ return {
436
+ status: 'warn',
437
+ message: 'CORP allows cross-origin embedding.',
438
+ recommendation: 'Use same-origin or same-site for sensitive resources'
439
+ };
440
+ }
441
+ return { status: 'warn', message: `Unknown CORP value: ${val}` };
442
+ }
443
+ },
444
+ {
445
+ header: 'x-xss-protection',
75
446
  weight: 0,
447
+ category: 'content',
76
448
  check: (val) => {
77
- if (val)
78
- return { status: 'warn', message: 'Server header exposes technology stack.' };
79
- return { status: 'pass', message: 'Server info hidden.' };
449
+ if (!val) {
450
+ return { status: 'pass', message: 'X-XSS-Protection not set (deprecated header).' };
451
+ }
452
+ if (val === '0') {
453
+ return { status: 'pass', message: 'XSS filter disabled (recommended for CSP sites).' };
454
+ }
455
+ if (val.includes('1') && val.includes('mode=block')) {
456
+ return {
457
+ status: 'warn',
458
+ message: 'X-XSS-Protection is deprecated. Use CSP instead.',
459
+ recommendation: 'Remove this header and rely on CSP'
460
+ };
461
+ }
462
+ return {
463
+ status: 'warn',
464
+ message: 'X-XSS-Protection is deprecated.',
465
+ recommendation: 'Remove this header. Use Content-Security-Policy instead.'
466
+ };
467
+ }
468
+ },
469
+ {
470
+ header: 'server',
471
+ weight: 2,
472
+ category: 'info-leak',
473
+ check: (val) => {
474
+ if (!val) {
475
+ return { status: 'pass', message: 'Server header hidden.' };
476
+ }
477
+ if (/\d+\.\d+/.test(val)) {
478
+ return {
479
+ status: 'warn',
480
+ message: `Server header exposes version: "${val}"`,
481
+ recommendation: 'Remove version numbers from Server header'
482
+ };
483
+ }
484
+ return {
485
+ status: 'warn',
486
+ message: `Server header reveals: "${val}"`,
487
+ recommendation: 'Consider hiding or minimizing Server header'
488
+ };
80
489
  }
81
490
  },
82
491
  {
83
492
  header: 'x-powered-by',
84
493
  weight: 5,
494
+ category: 'info-leak',
495
+ check: (val) => {
496
+ if (!val) {
497
+ return { status: 'pass', message: 'Technology stack hidden.' };
498
+ }
499
+ return {
500
+ status: 'fail',
501
+ message: `X-Powered-By exposes: "${val}"`,
502
+ recommendation: 'Remove X-Powered-By header (e.g., app.disable("x-powered-by") in Express)'
503
+ };
504
+ }
505
+ },
506
+ {
507
+ header: 'x-dns-prefetch-control',
508
+ weight: 2,
509
+ category: 'content',
510
+ check: (val) => {
511
+ if (!val) {
512
+ return { status: 'pass', message: 'DNS prefetch uses browser default.' };
513
+ }
514
+ if (val.toLowerCase() === 'off') {
515
+ return { status: 'pass', message: 'DNS prefetching disabled for privacy.' };
516
+ }
517
+ if (val.toLowerCase() === 'on') {
518
+ return {
519
+ status: 'warn',
520
+ message: 'DNS prefetching enabled. May leak browsing intent.',
521
+ recommendation: 'Set to "off" for privacy-sensitive sites'
522
+ };
523
+ }
524
+ return { status: 'warn', message: `Unknown value: ${val}` };
525
+ }
526
+ },
527
+ {
528
+ header: 'cache-control',
529
+ weight: 5,
530
+ category: 'content',
85
531
  check: (val) => {
86
- if (val)
87
- return { status: 'fail', message: 'X-Powered-By exposes technology stack (e.g. Express/PHP).' };
88
- return { status: 'pass', message: 'Technology stack hidden.' };
532
+ if (!val) {
533
+ return {
534
+ status: 'warn',
535
+ message: 'No Cache-Control header. Browser may cache sensitive content.',
536
+ recommendation: 'Add: Cache-Control: no-store for sensitive pages'
537
+ };
538
+ }
539
+ const lower = val.toLowerCase();
540
+ if (lower.includes('no-store')) {
541
+ return { status: 'pass', message: 'Cache-Control prevents caching of sensitive data.' };
542
+ }
543
+ if (lower.includes('private')) {
544
+ return { status: 'pass', message: 'Cache-Control set to private.' };
545
+ }
546
+ if (lower.includes('public') && !lower.includes('no-cache')) {
547
+ return {
548
+ status: 'warn',
549
+ message: 'Public caching enabled. May expose sensitive data.',
550
+ recommendation: 'Use no-store for sensitive content'
551
+ };
552
+ }
553
+ return { status: 'pass', message: 'Cache-Control configured.' };
89
554
  }
90
555
  }
91
556
  ];
@@ -93,15 +558,27 @@ export function analyzeSecurityHeaders(headers) {
93
558
  let totalScore = 100;
94
559
  let penalty = 0;
95
560
  const details = [];
561
+ let passed = 0;
562
+ let warnings = 0;
563
+ let failed = 0;
564
+ let cspAnalysis;
96
565
  for (const check of HEADERS_CHECKS) {
97
566
  const value = headers.get(check.header);
98
567
  const result = check.check(value || undefined);
568
+ if (check.header === 'content-security-policy' && value) {
569
+ cspAnalysis = analyzeCSP(value);
570
+ }
99
571
  let itemPenalty = 0;
100
572
  if (result.status === 'fail') {
101
573
  itemPenalty = check.weight;
574
+ failed++;
102
575
  }
103
576
  else if (result.status === 'warn') {
104
577
  itemPenalty = Math.ceil(check.weight / 2);
578
+ warnings++;
579
+ }
580
+ else {
581
+ passed++;
105
582
  }
106
583
  penalty += itemPenalty;
107
584
  details.push({
@@ -109,7 +586,8 @@ export function analyzeSecurityHeaders(headers) {
109
586
  value: value || undefined,
110
587
  status: result.status,
111
588
  score: -itemPenalty,
112
- message: result.message
589
+ message: result.message,
590
+ recommendation: result.recommendation
113
591
  });
114
592
  }
115
593
  const finalScore = Math.max(0, totalScore - penalty);
@@ -127,6 +605,33 @@ export function analyzeSecurityHeaders(headers) {
127
605
  return {
128
606
  grade,
129
607
  score: finalScore,
130
- details
608
+ details,
609
+ csp: cspAnalysis,
610
+ summary: { passed, warnings, failed }
611
+ };
612
+ }
613
+ export function quickSecurityCheck(headers) {
614
+ const criticalIssues = [];
615
+ if (!headers.get('strict-transport-security')) {
616
+ criticalIssues.push('No HSTS - vulnerable to SSL stripping');
617
+ }
618
+ const csp = headers.get('content-security-policy');
619
+ if (!csp) {
620
+ criticalIssues.push('No CSP - vulnerable to XSS');
621
+ }
622
+ else if (csp.includes("'unsafe-inline'") && csp.includes("'unsafe-eval'")) {
623
+ criticalIssues.push('CSP too permissive (unsafe-inline + unsafe-eval)');
624
+ }
625
+ const xfo = headers.get('x-frame-options');
626
+ const frameAncestors = csp?.includes('frame-ancestors');
627
+ if (!xfo && !frameAncestors) {
628
+ criticalIssues.push('No clickjacking protection');
629
+ }
630
+ if (!headers.get('x-content-type-options')) {
631
+ criticalIssues.push('MIME sniffing not disabled');
632
+ }
633
+ return {
634
+ secure: criticalIssues.length === 0,
635
+ criticalIssues
131
636
  };
132
637
  }