@navinjoseph/web-perf-core 1.0.0-beta.3

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 (77) hide show
  1. package/dist/audits/accessibility.d.ts +10 -0
  2. package/dist/audits/accessibility.d.ts.map +1 -0
  3. package/dist/audits/accessibility.js +258 -0
  4. package/dist/audits/accessibility.js.map +1 -0
  5. package/dist/audits/best-practices.d.ts +10 -0
  6. package/dist/audits/best-practices.d.ts.map +1 -0
  7. package/dist/audits/best-practices.js +696 -0
  8. package/dist/audits/best-practices.js.map +1 -0
  9. package/dist/audits/constants.d.ts +8 -0
  10. package/dist/audits/constants.d.ts.map +1 -0
  11. package/dist/audits/constants.js +278 -0
  12. package/dist/audits/constants.js.map +1 -0
  13. package/dist/audits/performance.d.ts +11 -0
  14. package/dist/audits/performance.d.ts.map +1 -0
  15. package/dist/audits/performance.js +497 -0
  16. package/dist/audits/performance.js.map +1 -0
  17. package/dist/audits/pwa.d.ts +10 -0
  18. package/dist/audits/pwa.d.ts.map +1 -0
  19. package/dist/audits/pwa.js +396 -0
  20. package/dist/audits/pwa.js.map +1 -0
  21. package/dist/audits/security.d.ts +10 -0
  22. package/dist/audits/security.d.ts.map +1 -0
  23. package/dist/audits/security.js +249 -0
  24. package/dist/audits/security.js.map +1 -0
  25. package/dist/audits/seo.d.ts +10 -0
  26. package/dist/audits/seo.d.ts.map +1 -0
  27. package/dist/audits/seo.js +471 -0
  28. package/dist/audits/seo.js.map +1 -0
  29. package/dist/browser.d.ts +21 -0
  30. package/dist/browser.d.ts.map +1 -0
  31. package/dist/browser.js +178 -0
  32. package/dist/browser.js.map +1 -0
  33. package/dist/dom-collector.d.ts +45 -0
  34. package/dist/dom-collector.d.ts.map +1 -0
  35. package/dist/dom-collector.js +173 -0
  36. package/dist/dom-collector.js.map +1 -0
  37. package/dist/formatter.d.ts +60 -0
  38. package/dist/formatter.d.ts.map +1 -0
  39. package/dist/formatter.js +164 -0
  40. package/dist/formatter.js.map +1 -0
  41. package/dist/id-generator.d.ts +22 -0
  42. package/dist/id-generator.d.ts.map +1 -0
  43. package/dist/id-generator.js +29 -0
  44. package/dist/id-generator.js.map +1 -0
  45. package/dist/index.d.ts +17 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +23 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/orchestrator.d.ts +41 -0
  50. package/dist/orchestrator.d.ts.map +1 -0
  51. package/dist/orchestrator.js +194 -0
  52. package/dist/orchestrator.js.map +1 -0
  53. package/dist/reporters/html.d.ts +6 -0
  54. package/dist/reporters/html.d.ts.map +1 -0
  55. package/dist/reporters/html.js +688 -0
  56. package/dist/reporters/html.js.map +1 -0
  57. package/dist/reporters/index.d.ts +4 -0
  58. package/dist/reporters/index.d.ts.map +1 -0
  59. package/dist/reporters/index.js +17 -0
  60. package/dist/reporters/index.js.map +1 -0
  61. package/dist/reporters/json.d.ts +6 -0
  62. package/dist/reporters/json.d.ts.map +1 -0
  63. package/dist/reporters/json.js +7 -0
  64. package/dist/reporters/json.js.map +1 -0
  65. package/dist/reporters/terminal.d.ts +6 -0
  66. package/dist/reporters/terminal.d.ts.map +1 -0
  67. package/dist/reporters/terminal.js +180 -0
  68. package/dist/reporters/terminal.js.map +1 -0
  69. package/dist/reporters/types.d.ts +2 -0
  70. package/dist/reporters/types.d.ts.map +1 -0
  71. package/dist/reporters/types.js +2 -0
  72. package/dist/reporters/types.js.map +1 -0
  73. package/dist/types.d.ts +80 -0
  74. package/dist/types.d.ts.map +1 -0
  75. package/dist/types.js +36 -0
  76. package/dist/types.js.map +1 -0
  77. package/package.json +54 -0
@@ -0,0 +1,396 @@
1
+ /**
2
+ * Check web app manifest
3
+ */
4
+ async function checkManifest(page) {
5
+ const issues = [];
6
+ const document = page.document;
7
+ const manifestLink = document.querySelector('link[rel="manifest"]');
8
+ if (!manifestLink) {
9
+ issues.push({
10
+ id: `pwa-no-manifest-${Date.now()}`,
11
+ ruleId: "manifest-missing",
12
+ severity: "serious",
13
+ category: "document",
14
+ message: "Web app manifest not found",
15
+ description: "A web app manifest is required for Progressive Web Apps. It defines how your app appears to users and enables installation.",
16
+ helpUrl: "https://web.dev/add-manifest/",
17
+ wcag: {
18
+ id: "N/A",
19
+ level: "AAA",
20
+ name: "PWA Requirement",
21
+ description: "Web app manifest must be present",
22
+ },
23
+ element: {
24
+ selector: "head",
25
+ html: '<link rel="manifest" href="/manifest.json">',
26
+ failureSummary: "No manifest link found",
27
+ },
28
+ fix: {
29
+ description: "Add a manifest link in the head section",
30
+ code: '<head>\n <link rel="manifest" href="/manifest.json">\n</head>',
31
+ learnMoreUrl: "https://web.dev/add-manifest/",
32
+ },
33
+ });
34
+ return issues; // Can't check manifest properties without a manifest
35
+ }
36
+ passed.push({
37
+ id: "pwa-manifest-link",
38
+ name: "Manifest Link Present",
39
+ category: "document",
40
+ description: "Manifest link tag is present",
41
+ });
42
+ // In a real implementation, we would fetch and parse the manifest file
43
+ // For jsdom, we'll check if the link exists and provide guidance
44
+ issues.push({
45
+ id: `pwa-manifest-check-${Date.now()}`,
46
+ ruleId: "manifest-validation",
47
+ severity: "minor",
48
+ category: "document",
49
+ message: "Manifest should be validated",
50
+ description: "Ensure your manifest.json includes required properties: name, icons (192x192 and 512x512), start_url, display, and theme_color.",
51
+ helpUrl: "https://web.dev/add-manifest/",
52
+ wcag: {
53
+ id: "N/A",
54
+ level: "AAA",
55
+ name: "PWA Best Practice",
56
+ description: "Manifest should have all required properties",
57
+ },
58
+ element: {
59
+ selector: 'link[rel="manifest"]',
60
+ html: manifestLink.outerHTML,
61
+ failureSummary: "Manifest properties should be validated",
62
+ },
63
+ fix: {
64
+ description: "Ensure manifest.json has all required properties",
65
+ code: `{
66
+ "name": "My Progressive Web App",
67
+ "short_name": "MyPWA",
68
+ "start_url": "/",
69
+ "display": "standalone",
70
+ "theme_color": "#ffffff",
71
+ "background_color": "#ffffff",
72
+ "icons": [
73
+ {
74
+ "src": "/icons/icon-192x192.png",
75
+ "sizes": "192x192",
76
+ "type": "image/png"
77
+ },
78
+ {
79
+ "src": "/icons/icon-512x512.png",
80
+ "sizes": "512x512",
81
+ "type": "image/png"
82
+ }
83
+ ]
84
+ }`,
85
+ learnMoreUrl: "https://web.dev/add-manifest/",
86
+ },
87
+ });
88
+ return issues;
89
+ }
90
+ /**
91
+ * Check service worker registration
92
+ */
93
+ function checkServiceWorker(page) {
94
+ const document = page.document;
95
+ // Check for service worker registration in inline scripts
96
+ const scripts = document.querySelectorAll("script:not([src])");
97
+ let hasServiceWorker = false;
98
+ scripts.forEach((script) => {
99
+ const content = script.textContent || "";
100
+ if (content.includes("navigator.serviceWorker.register")) {
101
+ hasServiceWorker = true;
102
+ }
103
+ });
104
+ if (!hasServiceWorker) {
105
+ return {
106
+ id: `pwa-no-service-worker-${Date.now()}`,
107
+ ruleId: "service-worker-missing",
108
+ severity: "serious",
109
+ category: "technical",
110
+ message: "Service worker not registered",
111
+ description: "Service workers enable offline functionality and are a core requirement for Progressive Web Apps. They allow your app to work without a network connection.",
112
+ helpUrl: "https://web.dev/service-workers-cache-storage/",
113
+ wcag: {
114
+ id: "N/A",
115
+ level: "AAA",
116
+ name: "PWA Requirement",
117
+ description: "Service worker should be registered",
118
+ },
119
+ element: {
120
+ selector: "script",
121
+ html: "<script>",
122
+ failureSummary: "No service worker registration found",
123
+ },
124
+ fix: {
125
+ description: "Register a service worker",
126
+ code: `// In your main JavaScript file
127
+ if ('serviceWorker' in navigator) {
128
+ window.addEventListener('load', () => {
129
+ navigator.serviceWorker.register('/service-worker.js')
130
+ .then(registration => {
131
+ console.log('SW registered:', registration);
132
+ })
133
+ .catch(error => {
134
+ console.log('SW registration failed:', error);
135
+ });
136
+ });
137
+ }`,
138
+ learnMoreUrl: "https://web.dev/service-workers-cache-storage/",
139
+ },
140
+ };
141
+ }
142
+ return null;
143
+ }
144
+ /**
145
+ * Check HTTPS
146
+ */
147
+ function checkHTTPS(page) {
148
+ const url = page.url;
149
+ if (!url.startsWith("https://")) {
150
+ return {
151
+ id: `pwa-not-https-${Date.now()}`,
152
+ ruleId: "pwa-requires-https",
153
+ severity: "critical",
154
+ category: "technical",
155
+ message: "PWA requires HTTPS",
156
+ description: "Progressive Web Apps require HTTPS to ensure security. Service workers and many modern web APIs only work over secure connections.",
157
+ helpUrl: "https://web.dev/why-https-matters/",
158
+ wcag: {
159
+ id: "N/A",
160
+ level: "AAA",
161
+ name: "PWA Requirement",
162
+ description: "Must be served over HTTPS",
163
+ },
164
+ element: {
165
+ selector: "html",
166
+ html: url,
167
+ failureSummary: "Page is not served over HTTPS",
168
+ },
169
+ fix: {
170
+ description: "Serve your site over HTTPS",
171
+ code: "// Use a hosting provider that supports HTTPS\n// Or use Let's Encrypt for free SSL certificates\n// https://letsencrypt.org/",
172
+ learnMoreUrl: "https://web.dev/why-https-matters/",
173
+ },
174
+ };
175
+ }
176
+ return null;
177
+ }
178
+ /**
179
+ * Check viewport meta tag
180
+ */
181
+ function checkViewport(page) {
182
+ const document = page.document;
183
+ const viewport = document.querySelector('meta[name="viewport"]');
184
+ if (!viewport) {
185
+ return {
186
+ id: `pwa-no-viewport-${Date.now()}`,
187
+ ruleId: "viewport-missing",
188
+ severity: "serious",
189
+ category: "document",
190
+ message: "Viewport meta tag missing",
191
+ description: "The viewport meta tag is essential for responsive design and PWAs. It ensures your app displays correctly on mobile devices.",
192
+ helpUrl: "https://web.dev/viewport/",
193
+ wcag: {
194
+ id: "N/A",
195
+ level: "AAA",
196
+ name: "PWA Requirement",
197
+ description: "Viewport meta tag must be present",
198
+ },
199
+ element: {
200
+ selector: "head",
201
+ html: '<meta name="viewport" content="width=device-width, initial-scale=1">',
202
+ failureSummary: "No viewport meta tag found",
203
+ },
204
+ fix: {
205
+ description: "Add viewport meta tag to the head",
206
+ code: '<head>\n <meta name="viewport" content="width=device-width, initial-scale=1">\n</head>',
207
+ learnMoreUrl: "https://web.dev/viewport/",
208
+ },
209
+ };
210
+ }
211
+ const content = viewport.getAttribute("content") || "";
212
+ if (!content.includes("width=device-width")) {
213
+ return {
214
+ id: `pwa-viewport-invalid-${Date.now()}`,
215
+ ruleId: "viewport-invalid",
216
+ severity: "moderate",
217
+ category: "document",
218
+ message: "Viewport meta tag missing width=device-width",
219
+ description: "The viewport should include width=device-width to ensure proper responsive behavior on mobile devices.",
220
+ helpUrl: "https://web.dev/viewport/",
221
+ wcag: {
222
+ id: "N/A",
223
+ level: "AAA",
224
+ name: "PWA Best Practice",
225
+ description: "Viewport should be properly configured",
226
+ },
227
+ element: {
228
+ selector: 'meta[name="viewport"]',
229
+ html: viewport.outerHTML,
230
+ failureSummary: "Viewport missing width=device-width",
231
+ },
232
+ fix: {
233
+ description: "Update viewport content attribute",
234
+ code: '<meta name="viewport" content="width=device-width, initial-scale=1">',
235
+ learnMoreUrl: "https://web.dev/viewport/",
236
+ },
237
+ };
238
+ }
239
+ return null;
240
+ }
241
+ /**
242
+ * Check Apple touch icon
243
+ */
244
+ function checkAppleTouchIcon(page) {
245
+ const document = page.document;
246
+ const appleTouchIcon = document.querySelector('link[rel="apple-touch-icon"]');
247
+ if (!appleTouchIcon) {
248
+ return {
249
+ id: `pwa-no-apple-icon-${Date.now()}`,
250
+ ruleId: "apple-touch-icon-missing",
251
+ severity: "minor",
252
+ category: "document",
253
+ message: "Apple touch icon not specified",
254
+ description: "Apple touch icons ensure your PWA looks good when added to iOS home screens. Recommended size is 180x180px.",
255
+ helpUrl: "https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html",
256
+ wcag: {
257
+ id: "N/A",
258
+ level: "AAA",
259
+ name: "PWA Best Practice",
260
+ description: "Provide Apple touch icon",
261
+ },
262
+ element: {
263
+ selector: "head",
264
+ html: '<link rel="apple-touch-icon" href="/icons/apple-touch-icon-180x180.png">',
265
+ failureSummary: "No apple-touch-icon link found",
266
+ },
267
+ fix: {
268
+ description: "Add apple-touch-icon link",
269
+ code: '<head>\n <link rel="apple-touch-icon" sizes="180x180" href="/icons/apple-touch-icon-180x180.png">\n</head>',
270
+ learnMoreUrl: "https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html",
271
+ },
272
+ };
273
+ }
274
+ return null;
275
+ }
276
+ /**
277
+ * Check theme color
278
+ */
279
+ function checkThemeColor(page) {
280
+ const document = page.document;
281
+ const themeColor = document.querySelector('meta[name="theme-color"]');
282
+ if (!themeColor) {
283
+ return {
284
+ id: `pwa-no-theme-color-${Date.now()}`,
285
+ ruleId: "theme-color-missing",
286
+ severity: "minor",
287
+ category: "document",
288
+ message: "Theme color not specified",
289
+ description: "The theme-color meta tag customizes the browser UI color on mobile devices, providing a more native app-like experience.",
290
+ helpUrl: "https://web.dev/themed-omnibox/",
291
+ wcag: {
292
+ id: "N/A",
293
+ level: "AAA",
294
+ name: "PWA Best Practice",
295
+ description: "Provide theme color",
296
+ },
297
+ element: {
298
+ selector: "head",
299
+ html: '<meta name="theme-color" content="#ffffff">',
300
+ failureSummary: "No theme-color meta tag found",
301
+ },
302
+ fix: {
303
+ description: "Add theme-color meta tag",
304
+ code: '<head>\n <meta name="theme-color" content="#1a73e8">\n</head>',
305
+ learnMoreUrl: "https://web.dev/themed-omnibox/",
306
+ },
307
+ };
308
+ }
309
+ return null;
310
+ }
311
+ const passed = [];
312
+ /**
313
+ * Run PWA (Progressive Web App) audit
314
+ */
315
+ export async function auditPWA(page) {
316
+ const issues = [];
317
+ const passed = [];
318
+ // Web app manifest
319
+ const manifestIssues = await checkManifest(page);
320
+ manifestIssues.forEach((issue) => issues.push(issue));
321
+ if (manifestIssues.length === 0) {
322
+ passed.push({
323
+ id: "pwa-manifest",
324
+ name: "Web App Manifest",
325
+ category: "document",
326
+ description: "Valid web app manifest is present",
327
+ });
328
+ }
329
+ // Service worker
330
+ const swIssue = checkServiceWorker(page);
331
+ if (swIssue) {
332
+ issues.push(swIssue);
333
+ }
334
+ else {
335
+ passed.push({
336
+ id: "pwa-service-worker",
337
+ name: "Service Worker",
338
+ category: "technical",
339
+ description: "Service worker is registered",
340
+ });
341
+ }
342
+ // HTTPS
343
+ const httpsIssue = checkHTTPS(page);
344
+ if (httpsIssue) {
345
+ issues.push(httpsIssue);
346
+ }
347
+ else {
348
+ passed.push({
349
+ id: "pwa-https",
350
+ name: "HTTPS",
351
+ category: "technical",
352
+ description: "Page is served over HTTPS",
353
+ });
354
+ }
355
+ // Viewport
356
+ const viewportIssue = checkViewport(page);
357
+ if (viewportIssue) {
358
+ issues.push(viewportIssue);
359
+ }
360
+ else {
361
+ passed.push({
362
+ id: "pwa-viewport",
363
+ name: "Viewport Meta Tag",
364
+ category: "document",
365
+ description: "Viewport meta tag is properly configured",
366
+ });
367
+ }
368
+ // Apple touch icon
369
+ const appleTouchIconIssue = checkAppleTouchIcon(page);
370
+ if (appleTouchIconIssue) {
371
+ issues.push(appleTouchIconIssue);
372
+ }
373
+ else {
374
+ passed.push({
375
+ id: "pwa-apple-icon",
376
+ name: "Apple Touch Icon",
377
+ category: "document",
378
+ description: "Apple touch icon is specified",
379
+ });
380
+ }
381
+ // Theme color
382
+ const themeColorIssue = checkThemeColor(page);
383
+ if (themeColorIssue) {
384
+ issues.push(themeColorIssue);
385
+ }
386
+ else {
387
+ passed.push({
388
+ id: "pwa-theme-color",
389
+ name: "Theme Color",
390
+ category: "document",
391
+ description: "Theme color is specified",
392
+ });
393
+ }
394
+ return { issues, passed };
395
+ }
396
+ //# sourceMappingURL=pwa.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pwa.js","sourceRoot":"","sources":["../../src/audits/pwa.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { Issue, PendingCheck } from "../types.js";
2
+ import { BrowserPage } from "../browser.js";
3
+ /**
4
+ * Run security audit
5
+ */
6
+ export declare function auditSecurity(page: BrowserPage): Promise<{
7
+ issues: Issue[];
8
+ passed: PendingCheck[];
9
+ }>;
10
+ //# sourceMappingURL=security.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/audits/security.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C;;GAEG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC;IAC9D,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,MAAM,EAAE,YAAY,EAAE,CAAC;CACxB,CAAC,CAqDD"}
@@ -0,0 +1,249 @@
1
+ import fetch from "node-fetch";
2
+ /**
3
+ * Run security audit
4
+ */
5
+ export async function auditSecurity(page) {
6
+ const issues = [];
7
+ const passed = [];
8
+ // Check HTTPS
9
+ const httpsCheck = checkHTTPS(page);
10
+ if (httpsCheck)
11
+ issues.push(httpsCheck);
12
+ else
13
+ passed.push({
14
+ id: "sec-https",
15
+ name: "HTTPS Enabled",
16
+ category: "technical",
17
+ description: "Page is served over HTTPS",
18
+ });
19
+ // Check mixed content
20
+ const mixedCheck = checkMixedContent(page);
21
+ mixedCheck.forEach((issue) => issues.push(issue));
22
+ if (mixedCheck.length === 0)
23
+ passed.push({
24
+ id: "sec-mixed",
25
+ name: "No Mixed Content",
26
+ category: "technical",
27
+ description: "No HTTP resources on HTTPS page",
28
+ });
29
+ // Check security headers
30
+ const headerChecks = await checkSecurityHeaders(page);
31
+ headerChecks.forEach((issue) => issues.push(issue));
32
+ // Check for password fields over HTTP
33
+ const pwCheck = checkPasswordFieldsSecurity(page);
34
+ if (pwCheck)
35
+ issues.push(pwCheck);
36
+ else
37
+ passed.push({
38
+ id: "sec-password",
39
+ name: "Password Fields Secure",
40
+ category: "technical",
41
+ description: "Password fields are on HTTPS",
42
+ });
43
+ // Check for external links
44
+ const extLinkCheck = checkExternalLinks(page);
45
+ if (extLinkCheck)
46
+ issues.push(extLinkCheck);
47
+ else
48
+ passed.push({
49
+ id: "sec-ext-links",
50
+ name: "External Links Secure",
51
+ category: "technical",
52
+ description: "External links have security attributes",
53
+ });
54
+ return { issues, passed };
55
+ }
56
+ function checkHTTPS(page) {
57
+ if (!page.url.startsWith("https://")) {
58
+ return {
59
+ id: `sec-https-${Date.now()}`,
60
+ ruleId: "sec-https",
61
+ severity: "critical",
62
+ category: "technical",
63
+ message: "Page is not served over HTTPS",
64
+ description: "HTTPS encrypts data between browser and server",
65
+ helpUrl: "https://developers.google.com/web/fundamentals/security/encrypt-in-transit",
66
+ wcag: {
67
+ id: "4.1.1",
68
+ level: "A",
69
+ name: "Parsing",
70
+ description: "Security is maintained",
71
+ },
72
+ element: {
73
+ selector: "body",
74
+ html: "<body>...</body>",
75
+ failureSummary: `URL: ${page.url}`,
76
+ },
77
+ fix: {
78
+ description: "Enable HTTPS on your web server",
79
+ code: "RewriteEngine On\nRewriteCond %{HTTPS} off\nRewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]",
80
+ learnMoreUrl: "https://developers.google.com/web/fundamentals/security/encrypt-in-transit",
81
+ },
82
+ };
83
+ }
84
+ return null;
85
+ }
86
+ function checkMixedContent(page) {
87
+ const issues = [];
88
+ const mixedElements = [
89
+ ...page.document.querySelectorAll('script[src^="http://"]'),
90
+ ...page.document.querySelectorAll('link[href^="http://"]'),
91
+ ...page.document.querySelectorAll('img[src^="http://"]'),
92
+ ...page.document.querySelectorAll('iframe[src^="http://"]'),
93
+ ];
94
+ if (page.url.startsWith("https://") && mixedElements.length > 0) {
95
+ issues.push({
96
+ id: `sec-mixed-${Date.now()}`,
97
+ ruleId: "sec-mixed",
98
+ severity: "serious",
99
+ category: "technical",
100
+ message: `Found ${mixedElements.length} HTTP resource(s) on HTTPS page`,
101
+ description: "Mixed content weakens HTTPS security",
102
+ helpUrl: "https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content",
103
+ wcag: {
104
+ id: "4.1.1",
105
+ level: "A",
106
+ name: "Parsing",
107
+ description: "Security is maintained",
108
+ },
109
+ element: {
110
+ selector: "body",
111
+ html: "<body>...</body>",
112
+ failureSummary: `${mixedElements.length} mixed content resources`,
113
+ },
114
+ fix: {
115
+ description: "Update all resource URLs to use HTTPS",
116
+ code: '<!-- Change from -->\n<script src="http://example.com/script.js"></script>\n<!-- To -->\n<script src="https://example.com/script.js"></script>',
117
+ learnMoreUrl: "https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content",
118
+ },
119
+ });
120
+ }
121
+ return issues;
122
+ }
123
+ async function checkSecurityHeaders(page) {
124
+ const issues = [];
125
+ const headers = [
126
+ { name: "Content-Security-Policy", severity: "serious" },
127
+ { name: "Strict-Transport-Security", severity: "serious" },
128
+ { name: "X-Frame-Options", severity: "serious" },
129
+ { name: "X-Content-Type-Options", severity: "moderate" },
130
+ ];
131
+ try {
132
+ const response = await fetch(page.url, { method: "HEAD" });
133
+ for (const header of headers) {
134
+ if (!response.headers.get(header.name)) {
135
+ issues.push({
136
+ id: `sec-header-${header.name.toLowerCase()}-${Date.now()}`,
137
+ ruleId: `sec-header-${header.name.toLowerCase()}`,
138
+ severity: header.severity,
139
+ category: "technical",
140
+ message: `Missing ${header.name} header`,
141
+ description: `The ${header.name} header helps protect against security vulnerabilities`,
142
+ helpUrl: "https://owasp.org/www-project-secure-headers/",
143
+ wcag: {
144
+ id: "4.1.1",
145
+ level: "A",
146
+ name: "Parsing",
147
+ description: "Security headers are set",
148
+ },
149
+ element: {
150
+ selector: "body",
151
+ html: "<body>...</body>",
152
+ failureSummary: `${header.name} not found`,
153
+ },
154
+ fix: {
155
+ description: `Add the ${header.name} header to your server configuration`,
156
+ code: getHeaderFixCode(header.name),
157
+ learnMoreUrl: "https://owasp.org/www-project-secure-headers/",
158
+ },
159
+ });
160
+ }
161
+ }
162
+ }
163
+ catch (error) {
164
+ // Silent fail - can't check headers
165
+ }
166
+ return issues;
167
+ }
168
+ function getHeaderFixCode(headerName) {
169
+ switch (headerName) {
170
+ case "Content-Security-Policy":
171
+ return `Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'`;
172
+ case "Strict-Transport-Security":
173
+ return `Strict-Transport-Security: max-age=31536000; includeSubDomains`;
174
+ case "X-Frame-Options":
175
+ return `X-Frame-Options: DENY`;
176
+ case "X-Content-Type-Options":
177
+ return `X-Content-Type-Options: nosniff`;
178
+ default:
179
+ return "";
180
+ }
181
+ }
182
+ function checkPasswordFieldsSecurity(page) {
183
+ const passwordFields = page.document.querySelectorAll('input[type="password"]');
184
+ if (passwordFields.length > 0 && page.url.startsWith("http://")) {
185
+ return {
186
+ id: `sec-password-http-${Date.now()}`,
187
+ ruleId: "sec-password-http",
188
+ severity: "critical",
189
+ category: "technical",
190
+ message: "Password fields on non-HTTPS page",
191
+ description: "Password fields should never be on HTTP pages",
192
+ helpUrl: "https://owasp.org/www-project-web-security-testing-guide/",
193
+ wcag: {
194
+ id: "4.1.1",
195
+ level: "A",
196
+ name: "Parsing",
197
+ description: "Security is maintained",
198
+ },
199
+ element: {
200
+ selector: 'input[type="password"]',
201
+ html: passwordFields[0].outerHTML,
202
+ failureSummary: "Password field over HTTP",
203
+ },
204
+ fix: {
205
+ description: "Enable HTTPS for all pages with password fields",
206
+ code: "RewriteEngine On\nRewriteCond %{HTTPS} off\nRewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]",
207
+ learnMoreUrl: "https://owasp.org/www-project-web-security-testing-guide/",
208
+ },
209
+ };
210
+ }
211
+ return null;
212
+ }
213
+ function checkExternalLinks(page) {
214
+ const externalLinks = Array.from(page.document.querySelectorAll('a[target="_blank"]')).filter((link) => {
215
+ if (!(link instanceof page.window.HTMLAnchorElement))
216
+ return false;
217
+ const rel = link.getAttribute("rel") || "";
218
+ return !rel.includes("noopener") || !rel.includes("noreferrer");
219
+ });
220
+ if (externalLinks.length > 0) {
221
+ return {
222
+ id: `sec-ext-links-${Date.now()}`,
223
+ ruleId: "sec-ext-links",
224
+ severity: "moderate",
225
+ category: "technical",
226
+ message: `${externalLinks.length} external link(s) missing rel="noopener noreferrer"`,
227
+ description: 'Links with target="_blank" should prevent reverse tabnabbing',
228
+ helpUrl: "https://owasp.org/www-community/attacks/Reverse_Tabnabbing",
229
+ wcag: {
230
+ id: "4.1.2",
231
+ level: "A",
232
+ name: "Name, Role, Value",
233
+ description: "Links are safe",
234
+ },
235
+ element: {
236
+ selector: 'a[target="_blank"]',
237
+ html: externalLinks[0]?.outerHTML || "",
238
+ failureSummary: `${externalLinks.length} unsafe external links`,
239
+ },
240
+ fix: {
241
+ description: 'Add rel="noopener noreferrer" to all target="_blank" links',
242
+ code: '<a href="https://example.com" target="_blank" rel="noopener noreferrer">Link</a>',
243
+ learnMoreUrl: "https://owasp.org/www-community/attacks/Reverse_Tabnabbing",
244
+ },
245
+ };
246
+ }
247
+ return null;
248
+ }
249
+ //# sourceMappingURL=security.js.map