mpx-scan 1.1.0 → 1.2.1
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/package.json +1 -1
- package/src/index.js +1 -1
- package/src/scanners/cookies.js +13 -1
- package/src/scanners/exposed-files.js +1 -1
- package/src/scanners/fingerprint.js +9 -2
- package/src/scanners/headers.js +72 -46
- package/src/scanners/redirects.js +1 -1
- package/src/scanners/server.js +16 -5
- package/src/scanners/sri.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mpx-scan",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Professional website security scanner CLI. Check headers, SSL, cookies, DNS, and get actionable fix suggestions. Part of the Mesaplex developer toolchain.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
package/src/index.js
CHANGED
|
@@ -58,7 +58,7 @@ async function scan(url, options = {}) {
|
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
const allScanners = [
|
|
61
|
-
{ name: 'headers', fn: scanHeaders, weight:
|
|
61
|
+
{ name: 'headers', fn: scanHeaders, weight: 15 },
|
|
62
62
|
{ name: 'ssl', fn: scanSSL, weight: 20 },
|
|
63
63
|
{ name: 'cookies', fn: scanCookies, weight: 10 },
|
|
64
64
|
{ name: 'server', fn: scanServer, weight: 8 },
|
package/src/scanners/cookies.js
CHANGED
|
@@ -78,13 +78,25 @@ function fetchCookies(parsedUrl, options = {}) {
|
|
|
78
78
|
const req = protocol.request(parsedUrl.href, {
|
|
79
79
|
method: 'GET',
|
|
80
80
|
timeout,
|
|
81
|
-
headers: { 'User-Agent': '
|
|
81
|
+
headers: { 'User-Agent': 'mpx-scan/1.2.1 Security Scanner (https://github.com/mesaplexdev/mpx-scan)' },
|
|
82
82
|
rejectUnauthorized: false,
|
|
83
83
|
}, (res) => {
|
|
84
84
|
// Consume body
|
|
85
85
|
res.on('data', () => {});
|
|
86
86
|
res.on('end', () => {});
|
|
87
87
|
|
|
88
|
+
// Follow redirects to get cookies from final destination
|
|
89
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
90
|
+
if ((options._redirectCount || 0) >= 5) {
|
|
91
|
+
resolve([]);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const redirectUrl = new URL(res.headers.location, parsedUrl.href);
|
|
95
|
+
fetchCookies(redirectUrl, { ...options, _redirectCount: (options._redirectCount || 0) + 1 })
|
|
96
|
+
.then(resolve).catch(() => resolve([]));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
88
100
|
const setCookies = res.headers['set-cookie'] || [];
|
|
89
101
|
const parsed = setCookies.map(parseCookie);
|
|
90
102
|
resolve(parsed);
|
|
@@ -151,7 +151,7 @@ function checkPath(parsedUrl, pathStr, options = {}) {
|
|
|
151
151
|
const req = protocol.request(url.href, {
|
|
152
152
|
method: 'GET',
|
|
153
153
|
timeout,
|
|
154
|
-
headers: { 'User-Agent': '
|
|
154
|
+
headers: { 'User-Agent': 'mpx-scan/1.2.1 Security Scanner (https://github.com/mesaplexdev/mpx-scan)' },
|
|
155
155
|
rejectUnauthorized: false,
|
|
156
156
|
}, (res) => {
|
|
157
157
|
let body = '';
|
|
@@ -297,16 +297,18 @@ async function scanFingerprint(parsedUrl, options = {}) {
|
|
|
297
297
|
function fetchHeaders(parsedUrl, options = {}) {
|
|
298
298
|
return new Promise((resolve) => {
|
|
299
299
|
const timeout = options.timeout || 10000;
|
|
300
|
+
const method = options._useGet ? 'GET' : 'HEAD';
|
|
300
301
|
const protocol = parsedUrl.protocol === 'https:' ? https : http;
|
|
301
302
|
let resolved = false;
|
|
302
303
|
const done = (val) => { if (!resolved) { resolved = true; resolve(val); } };
|
|
303
304
|
const timer = setTimeout(() => done(null), timeout + 2000);
|
|
304
305
|
|
|
305
306
|
const req = protocol.request(parsedUrl.href, {
|
|
306
|
-
method
|
|
307
|
-
headers: { 'User-Agent': '
|
|
307
|
+
method, timeout,
|
|
308
|
+
headers: { 'User-Agent': 'mpx-scan/1.2.1 Security Scanner (https://github.com/mesaplexdev/mpx-scan)' },
|
|
308
309
|
rejectUnauthorized: false,
|
|
309
310
|
}, (res) => {
|
|
311
|
+
if (method === 'GET') { res.resume(); }
|
|
310
312
|
clearTimeout(timer);
|
|
311
313
|
// Follow redirects
|
|
312
314
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
@@ -314,6 +316,11 @@ function fetchHeaders(parsedUrl, options = {}) {
|
|
|
314
316
|
fetchHeaders(redirectUrl, options).then(done);
|
|
315
317
|
return;
|
|
316
318
|
}
|
|
319
|
+
// HEAD returned error — retry with GET
|
|
320
|
+
if (!options._useGet && res.statusCode >= 400) {
|
|
321
|
+
fetchHeaders(parsedUrl, { ...options, _useGet: true }).then(done);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
317
324
|
done(res.headers);
|
|
318
325
|
});
|
|
319
326
|
req.on('error', () => { clearTimeout(timer); done(null); });
|
package/src/scanners/headers.js
CHANGED
|
@@ -23,8 +23,12 @@ async function scanHeaders(parsedUrl, options = {}) {
|
|
|
23
23
|
let score = 0;
|
|
24
24
|
let maxScore = 0;
|
|
25
25
|
|
|
26
|
-
//
|
|
27
|
-
|
|
26
|
+
// ==============================================
|
|
27
|
+
// CRITICAL HEADERS (fail if missing, full points)
|
|
28
|
+
// ==============================================
|
|
29
|
+
|
|
30
|
+
// --- Strict-Transport-Security (4 pts) ---
|
|
31
|
+
maxScore += 4;
|
|
28
32
|
const hsts = headers['strict-transport-security'];
|
|
29
33
|
if (hsts) {
|
|
30
34
|
const maxAge = parseInt((hsts.match(/max-age=(\d+)/) || [])[1] || '0');
|
|
@@ -33,10 +37,10 @@ async function scanHeaders(parsedUrl, options = {}) {
|
|
|
33
37
|
|
|
34
38
|
if (maxAge >= 31536000 && includesSubs && preload) {
|
|
35
39
|
checks.push({ name: 'Strict-Transport-Security', status: 'pass', message: `Excellent. max-age=${maxAge}, includeSubDomains, preload`, value: hsts });
|
|
36
|
-
score +=
|
|
40
|
+
score += 4;
|
|
37
41
|
} else if (maxAge >= 31536000) {
|
|
38
42
|
checks.push({ name: 'Strict-Transport-Security', status: 'pass', message: `Good. max-age=${maxAge}${includesSubs ? ', includeSubDomains' : ''}${preload ? ', preload' : ''}`, value: hsts });
|
|
39
|
-
score +=
|
|
43
|
+
score += 3;
|
|
40
44
|
} else if (maxAge > 0) {
|
|
41
45
|
checks.push({ name: 'Strict-Transport-Security', status: 'warn', message: `max-age=${maxAge} is low. Recommend >= 31536000 (1 year)`, value: hsts });
|
|
42
46
|
score += 1;
|
|
@@ -45,78 +49,88 @@ async function scanHeaders(parsedUrl, options = {}) {
|
|
|
45
49
|
checks.push({ name: 'Strict-Transport-Security', status: 'fail', message: 'Missing. Allows downgrade attacks to HTTP.', recommendation: 'Add: Strict-Transport-Security: max-age=31536000; includeSubDomains; preload' });
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
// --- Content-
|
|
52
|
+
// --- X-Content-Type-Options (3 pts) ---
|
|
49
53
|
maxScore += 3;
|
|
50
|
-
const csp = headers['content-security-policy'];
|
|
51
|
-
if (csp) {
|
|
52
|
-
const hasDefaultSrc = /default-src/i.test(csp);
|
|
53
|
-
const hasUnsafeInline = /unsafe-inline/i.test(csp);
|
|
54
|
-
const hasUnsafeEval = /unsafe-eval/i.test(csp);
|
|
55
|
-
|
|
56
|
-
if (hasDefaultSrc && !hasUnsafeInline && !hasUnsafeEval) {
|
|
57
|
-
checks.push({ name: 'Content-Security-Policy', status: 'pass', message: 'Strong policy without unsafe directives', value: csp.substring(0, 200) + (csp.length > 200 ? '...' : '') });
|
|
58
|
-
score += 3;
|
|
59
|
-
} else if (hasDefaultSrc) {
|
|
60
|
-
const issues = [];
|
|
61
|
-
if (hasUnsafeInline) issues.push('unsafe-inline');
|
|
62
|
-
if (hasUnsafeEval) issues.push('unsafe-eval');
|
|
63
|
-
checks.push({ name: 'Content-Security-Policy', status: 'warn', message: `Present but uses ${issues.join(', ')}`, value: csp.substring(0, 200) });
|
|
64
|
-
score += 1.5;
|
|
65
|
-
} else {
|
|
66
|
-
checks.push({ name: 'Content-Security-Policy', status: 'warn', message: 'Present but missing default-src directive', value: csp.substring(0, 200) });
|
|
67
|
-
score += 1;
|
|
68
|
-
}
|
|
69
|
-
} else {
|
|
70
|
-
checks.push({ name: 'Content-Security-Policy', status: 'fail', message: 'Missing. Vulnerable to XSS and data injection attacks.', recommendation: "Add: Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'" });
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// --- X-Content-Type-Options ---
|
|
74
|
-
maxScore += 1;
|
|
75
54
|
const xcto = headers['x-content-type-options'];
|
|
76
55
|
if (xcto && xcto.toLowerCase() === 'nosniff') {
|
|
77
56
|
checks.push({ name: 'X-Content-Type-Options', status: 'pass', message: 'nosniff — prevents MIME-type sniffing', value: xcto });
|
|
78
|
-
score +=
|
|
57
|
+
score += 3;
|
|
79
58
|
} else {
|
|
80
59
|
checks.push({ name: 'X-Content-Type-Options', status: 'fail', message: 'Missing or incorrect. Browsers may MIME-sniff responses.', recommendation: 'Add: X-Content-Type-Options: nosniff' });
|
|
81
60
|
}
|
|
82
61
|
|
|
83
|
-
//
|
|
84
|
-
|
|
62
|
+
// ==============================================
|
|
63
|
+
// IMPORTANT HEADERS (fail if missing, fewer pts)
|
|
64
|
+
// ==============================================
|
|
65
|
+
|
|
66
|
+
// --- X-Frame-Options (2 pts) ---
|
|
67
|
+
maxScore += 2;
|
|
68
|
+
const csp = headers['content-security-policy'];
|
|
85
69
|
const xfo = headers['x-frame-options'];
|
|
86
70
|
if (xfo) {
|
|
87
71
|
const val = xfo.toUpperCase();
|
|
88
72
|
if (val === 'DENY' || val === 'SAMEORIGIN') {
|
|
89
73
|
checks.push({ name: 'X-Frame-Options', status: 'pass', message: `${val} — prevents clickjacking`, value: xfo });
|
|
90
|
-
score +=
|
|
74
|
+
score += 2;
|
|
91
75
|
} else {
|
|
92
76
|
checks.push({ name: 'X-Frame-Options', status: 'warn', message: `Unusual value: ${xfo}`, value: xfo });
|
|
93
|
-
score +=
|
|
77
|
+
score += 1;
|
|
94
78
|
}
|
|
95
79
|
} else {
|
|
96
80
|
// Check if CSP has frame-ancestors
|
|
97
81
|
if (csp && /frame-ancestors/i.test(csp)) {
|
|
98
82
|
checks.push({ name: 'X-Frame-Options', status: 'pass', message: 'Not set, but CSP frame-ancestors provides equivalent protection', value: 'via CSP' });
|
|
99
|
-
score +=
|
|
83
|
+
score += 2;
|
|
100
84
|
} else {
|
|
101
85
|
checks.push({ name: 'X-Frame-Options', status: 'fail', message: 'Missing. Page can be embedded in iframes (clickjacking risk).', recommendation: 'Add: X-Frame-Options: DENY (or SAMEORIGIN)' });
|
|
102
86
|
}
|
|
103
87
|
}
|
|
104
88
|
|
|
105
|
-
// --- Referrer-Policy ---
|
|
106
|
-
maxScore +=
|
|
89
|
+
// --- Referrer-Policy (2 pts) ---
|
|
90
|
+
maxScore += 2;
|
|
107
91
|
const rp = headers['referrer-policy'];
|
|
108
92
|
const goodPolicies = ['no-referrer', 'strict-origin-when-cross-origin', 'strict-origin', 'same-origin', 'no-referrer-when-downgrade'];
|
|
109
93
|
if (rp && goodPolicies.some(p => rp.toLowerCase().includes(p))) {
|
|
110
94
|
checks.push({ name: 'Referrer-Policy', status: 'pass', message: `${rp}`, value: rp });
|
|
111
|
-
score +=
|
|
95
|
+
score += 2;
|
|
112
96
|
} else if (rp) {
|
|
113
97
|
checks.push({ name: 'Referrer-Policy', status: 'warn', message: `Set to "${rp}" — may leak referrer data`, value: rp });
|
|
114
|
-
score +=
|
|
98
|
+
score += 1;
|
|
99
|
+
} else {
|
|
100
|
+
checks.push({ name: 'Referrer-Policy', status: 'fail', message: 'Missing. Browser defaults may leak URL paths in referrer.', recommendation: 'Add: Referrer-Policy: strict-origin-when-cross-origin' });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ==============================================
|
|
104
|
+
// NICE-TO-HAVE HEADERS (warn if missing, no pts)
|
|
105
|
+
// ==============================================
|
|
106
|
+
|
|
107
|
+
// --- Content-Security-Policy (bonus: 2 pts if present, warn if missing, no deduction) ---
|
|
108
|
+
maxScore += 2;
|
|
109
|
+
if (csp) {
|
|
110
|
+
const hasDefaultSrc = /default-src/i.test(csp);
|
|
111
|
+
const hasUnsafeInline = /unsafe-inline/i.test(csp);
|
|
112
|
+
const hasUnsafeEval = /unsafe-eval/i.test(csp);
|
|
113
|
+
|
|
114
|
+
if (hasDefaultSrc && !hasUnsafeInline && !hasUnsafeEval) {
|
|
115
|
+
checks.push({ name: 'Content-Security-Policy', status: 'pass', message: 'Strong policy without unsafe directives', value: csp.substring(0, 200) + (csp.length > 200 ? '...' : '') });
|
|
116
|
+
score += 2;
|
|
117
|
+
} else if (hasDefaultSrc) {
|
|
118
|
+
const issues = [];
|
|
119
|
+
if (hasUnsafeInline) issues.push('unsafe-inline');
|
|
120
|
+
if (hasUnsafeEval) issues.push('unsafe-eval');
|
|
121
|
+
checks.push({ name: 'Content-Security-Policy', status: 'warn', message: `Present but uses ${issues.join(', ')}`, value: csp.substring(0, 200) });
|
|
122
|
+
score += 1;
|
|
123
|
+
} else {
|
|
124
|
+
checks.push({ name: 'Content-Security-Policy', status: 'warn', message: 'Present but missing default-src directive', value: csp.substring(0, 200) });
|
|
125
|
+
score += 1;
|
|
126
|
+
}
|
|
115
127
|
} else {
|
|
116
|
-
|
|
128
|
+
// Warn instead of fail — CSP is hard to implement correctly
|
|
129
|
+
checks.push({ name: 'Content-Security-Policy', status: 'warn', message: 'Missing. Consider adding to protect against XSS and data injection.', recommendation: "Add: Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'" });
|
|
130
|
+
score += 0; // No deduction for missing CSP
|
|
117
131
|
}
|
|
118
132
|
|
|
119
|
-
// --- Permissions-Policy ---
|
|
133
|
+
// --- Permissions-Policy (bonus: 1 pt if present) ---
|
|
120
134
|
maxScore += 1;
|
|
121
135
|
const pp = headers['permissions-policy'] || headers['feature-policy'];
|
|
122
136
|
if (pp) {
|
|
@@ -126,7 +140,7 @@ async function scanHeaders(parsedUrl, options = {}) {
|
|
|
126
140
|
checks.push({ name: 'Permissions-Policy', status: 'warn', message: 'Missing. Browser features (camera, mic, geolocation) unrestricted.', recommendation: 'Add: Permissions-Policy: camera=(), microphone=(), geolocation=()' });
|
|
127
141
|
}
|
|
128
142
|
|
|
129
|
-
// --- Cross-Origin-Opener-Policy ---
|
|
143
|
+
// --- Cross-Origin-Opener-Policy (bonus: 0.5 pts if present) ---
|
|
130
144
|
maxScore += 0.5;
|
|
131
145
|
const coop = headers['cross-origin-opener-policy'];
|
|
132
146
|
if (coop) {
|
|
@@ -136,7 +150,7 @@ async function scanHeaders(parsedUrl, options = {}) {
|
|
|
136
150
|
checks.push({ name: 'Cross-Origin-Opener-Policy', status: 'info', message: 'Not set. Consider adding for cross-origin isolation.' });
|
|
137
151
|
}
|
|
138
152
|
|
|
139
|
-
// --- Cross-Origin-Resource-Policy ---
|
|
153
|
+
// --- Cross-Origin-Resource-Policy (bonus: 0.5 pts if present) ---
|
|
140
154
|
maxScore += 0.5;
|
|
141
155
|
const corp = headers['cross-origin-resource-policy'];
|
|
142
156
|
if (corp) {
|
|
@@ -170,16 +184,20 @@ async function scanHeaders(parsedUrl, options = {}) {
|
|
|
170
184
|
function fetchHeaders(parsedUrl, options = {}) {
|
|
171
185
|
return new Promise((resolve, reject) => {
|
|
172
186
|
const timeout = options.timeout || 10000;
|
|
187
|
+
const method = options._useGet ? 'GET' : 'HEAD';
|
|
173
188
|
const protocol = parsedUrl.protocol === 'https:' ? https : http;
|
|
174
189
|
|
|
175
190
|
const req = protocol.request(parsedUrl.href, {
|
|
176
|
-
method
|
|
191
|
+
method,
|
|
177
192
|
timeout,
|
|
178
193
|
headers: {
|
|
179
|
-
'User-Agent': 'mpx-scan/1.
|
|
194
|
+
'User-Agent': 'mpx-scan/1.2.1 Security Scanner (https://github.com/mesaplexdev/mpx-scan)'
|
|
180
195
|
},
|
|
181
196
|
rejectUnauthorized: false // We check SSL separately
|
|
182
197
|
}, (res) => {
|
|
198
|
+
// Consume body for GET requests
|
|
199
|
+
if (method === 'GET') { res.resume(); }
|
|
200
|
+
|
|
183
201
|
// Follow redirects (up to 5)
|
|
184
202
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
185
203
|
const redirectUrl = new URL(res.headers.location, parsedUrl.href);
|
|
@@ -191,6 +209,14 @@ function fetchHeaders(parsedUrl, options = {}) {
|
|
|
191
209
|
.then(resolve).catch(reject);
|
|
192
210
|
return;
|
|
193
211
|
}
|
|
212
|
+
|
|
213
|
+
// If HEAD returned 4xx/5xx, retry with GET (some servers reject HEAD)
|
|
214
|
+
if (!options._useGet && res.statusCode >= 400) {
|
|
215
|
+
fetchHeaders(parsedUrl, { ...options, _useGet: true })
|
|
216
|
+
.then(resolve).catch(reject);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
194
220
|
resolve(res.headers);
|
|
195
221
|
});
|
|
196
222
|
|
|
@@ -86,7 +86,7 @@ function followRedirect(testUrl, options = {}) {
|
|
|
86
86
|
const req = protocol.request(testUrl.href, {
|
|
87
87
|
method: 'GET',
|
|
88
88
|
timeout,
|
|
89
|
-
headers: { 'User-Agent': '
|
|
89
|
+
headers: { 'User-Agent': 'mpx-scan/1.2.1 Security Scanner (https://github.com/mesaplexdev/mpx-scan)' },
|
|
90
90
|
rejectUnauthorized: false,
|
|
91
91
|
}, (res) => {
|
|
92
92
|
res.resume(); // Consume body
|
package/src/scanners/server.js
CHANGED
|
@@ -84,14 +84,18 @@ async function scanServer(parsedUrl, options = {}) {
|
|
|
84
84
|
function checkRedirectToHttps(httpUrl, options = {}) {
|
|
85
85
|
return new Promise((resolve, reject) => {
|
|
86
86
|
const timeout = options.timeout || 5000;
|
|
87
|
+
const method = options._useGet ? 'GET' : 'HEAD';
|
|
87
88
|
const req = http.request(httpUrl.href, {
|
|
88
|
-
method
|
|
89
|
+
method,
|
|
89
90
|
timeout,
|
|
90
|
-
headers: { 'User-Agent': '
|
|
91
|
+
headers: { 'User-Agent': 'mpx-scan/1.2.1 Security Scanner (https://github.com/mesaplexdev/mpx-scan)' },
|
|
91
92
|
}, (res) => {
|
|
93
|
+
if (method === 'GET') { res.resume(); }
|
|
92
94
|
if (res.statusCode >= 300 && res.statusCode < 400) {
|
|
93
95
|
const location = res.headers.location || '';
|
|
94
96
|
resolve(location.startsWith('https://'));
|
|
97
|
+
} else if (!options._useGet && res.statusCode >= 400) {
|
|
98
|
+
checkRedirectToHttps(httpUrl, { ...options, _useGet: true }).then(resolve).catch(reject);
|
|
95
99
|
} else {
|
|
96
100
|
resolve(false);
|
|
97
101
|
}
|
|
@@ -105,16 +109,23 @@ function checkRedirectToHttps(httpUrl, options = {}) {
|
|
|
105
109
|
function fetchWithOrigin(parsedUrl, options = {}) {
|
|
106
110
|
return new Promise((resolve, reject) => {
|
|
107
111
|
const timeout = options.timeout || 5000;
|
|
112
|
+
const method = options._useGet ? 'GET' : 'HEAD';
|
|
108
113
|
const protocol = parsedUrl.protocol === 'https:' ? https : http;
|
|
109
114
|
const req = protocol.request(parsedUrl.href, {
|
|
110
|
-
method
|
|
115
|
+
method,
|
|
111
116
|
timeout,
|
|
112
117
|
headers: {
|
|
113
|
-
'User-Agent': '
|
|
118
|
+
'User-Agent': 'mpx-scan/1.2.1 Security Scanner (https://github.com/mesaplexdev/mpx-scan)',
|
|
114
119
|
'Origin': 'https://evil.example.com'
|
|
115
120
|
},
|
|
116
121
|
rejectUnauthorized: false,
|
|
117
122
|
}, (res) => {
|
|
123
|
+
if (method === 'GET') { res.resume(); }
|
|
124
|
+
// HEAD returned error — retry with GET
|
|
125
|
+
if (!options._useGet && res.statusCode >= 400) {
|
|
126
|
+
fetchWithOrigin(parsedUrl, { ...options, _useGet: true }).then(resolve).catch(reject);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
118
129
|
resolve(res.headers);
|
|
119
130
|
});
|
|
120
131
|
req.on('error', reject);
|
|
@@ -130,7 +141,7 @@ function checkMethods(parsedUrl, options = {}) {
|
|
|
130
141
|
const req = protocol.request(parsedUrl.href, {
|
|
131
142
|
method: 'OPTIONS',
|
|
132
143
|
timeout,
|
|
133
|
-
headers: { 'User-Agent': '
|
|
144
|
+
headers: { 'User-Agent': 'mpx-scan/1.2.1 Security Scanner (https://github.com/mesaplexdev/mpx-scan)' },
|
|
134
145
|
rejectUnauthorized: false,
|
|
135
146
|
}, (res) => {
|
|
136
147
|
const allow = res.headers.allow || '';
|
package/src/scanners/sri.js
CHANGED
|
@@ -125,7 +125,7 @@ function fetchBody(parsedUrl, options = {}) {
|
|
|
125
125
|
method: 'GET',
|
|
126
126
|
timeout,
|
|
127
127
|
headers: {
|
|
128
|
-
'User-Agent': '
|
|
128
|
+
'User-Agent': 'mpx-scan/1.2.1 Security Scanner (https://github.com/mesaplexdev/mpx-scan)',
|
|
129
129
|
'Accept': 'text/html'
|
|
130
130
|
},
|
|
131
131
|
rejectUnauthorized: false,
|