@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.
- package/dist/audits/accessibility.d.ts +10 -0
- package/dist/audits/accessibility.d.ts.map +1 -0
- package/dist/audits/accessibility.js +258 -0
- package/dist/audits/accessibility.js.map +1 -0
- package/dist/audits/best-practices.d.ts +10 -0
- package/dist/audits/best-practices.d.ts.map +1 -0
- package/dist/audits/best-practices.js +696 -0
- package/dist/audits/best-practices.js.map +1 -0
- package/dist/audits/constants.d.ts +8 -0
- package/dist/audits/constants.d.ts.map +1 -0
- package/dist/audits/constants.js +278 -0
- package/dist/audits/constants.js.map +1 -0
- package/dist/audits/performance.d.ts +11 -0
- package/dist/audits/performance.d.ts.map +1 -0
- package/dist/audits/performance.js +497 -0
- package/dist/audits/performance.js.map +1 -0
- package/dist/audits/pwa.d.ts +10 -0
- package/dist/audits/pwa.d.ts.map +1 -0
- package/dist/audits/pwa.js +396 -0
- package/dist/audits/pwa.js.map +1 -0
- package/dist/audits/security.d.ts +10 -0
- package/dist/audits/security.d.ts.map +1 -0
- package/dist/audits/security.js +249 -0
- package/dist/audits/security.js.map +1 -0
- package/dist/audits/seo.d.ts +10 -0
- package/dist/audits/seo.d.ts.map +1 -0
- package/dist/audits/seo.js +471 -0
- package/dist/audits/seo.js.map +1 -0
- package/dist/browser.d.ts +21 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +178 -0
- package/dist/browser.js.map +1 -0
- package/dist/dom-collector.d.ts +45 -0
- package/dist/dom-collector.d.ts.map +1 -0
- package/dist/dom-collector.js +173 -0
- package/dist/dom-collector.js.map +1 -0
- package/dist/formatter.d.ts +60 -0
- package/dist/formatter.d.ts.map +1 -0
- package/dist/formatter.js +164 -0
- package/dist/formatter.js.map +1 -0
- package/dist/id-generator.d.ts +22 -0
- package/dist/id-generator.d.ts.map +1 -0
- package/dist/id-generator.js +29 -0
- package/dist/id-generator.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator.d.ts +41 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +194 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/reporters/html.d.ts +6 -0
- package/dist/reporters/html.d.ts.map +1 -0
- package/dist/reporters/html.js +688 -0
- package/dist/reporters/html.js.map +1 -0
- package/dist/reporters/index.d.ts +4 -0
- package/dist/reporters/index.d.ts.map +1 -0
- package/dist/reporters/index.js +17 -0
- package/dist/reporters/index.js.map +1 -0
- package/dist/reporters/json.d.ts +6 -0
- package/dist/reporters/json.d.ts.map +1 -0
- package/dist/reporters/json.js +7 -0
- package/dist/reporters/json.js.map +1 -0
- package/dist/reporters/terminal.d.ts +6 -0
- package/dist/reporters/terminal.d.ts.map +1 -0
- package/dist/reporters/terminal.js +180 -0
- package/dist/reporters/terminal.js.map +1 -0
- package/dist/reporters/types.d.ts +2 -0
- package/dist/reporters/types.d.ts.map +1 -0
- package/dist/reporters/types.js +2 -0
- package/dist/reporters/types.js.map +1 -0
- package/dist/types.d.ts +80 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +36 -0
- package/dist/types.js.map +1 -0
- 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
|