recker 1.0.15-next.dac946a → 1.0.15-next.eb07368
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/bench/generator.d.ts.map +1 -1
- package/dist/bench/generator.js +2 -1
- package/dist/bench/stats.d.ts +15 -1
- package/dist/bench/stats.d.ts.map +1 -1
- package/dist/bench/stats.js +84 -5
- package/dist/cli/index.js +100 -0
- package/dist/cli/tui/load-dashboard.d.ts.map +1 -1
- package/dist/cli/tui/load-dashboard.js +62 -8
- package/dist/cli/tui/scroll-buffer.d.ts +43 -0
- package/dist/cli/tui/scroll-buffer.d.ts.map +1 -0
- package/dist/cli/tui/scroll-buffer.js +162 -0
- package/dist/cli/tui/search-panel.d.ts +41 -0
- package/dist/cli/tui/search-panel.d.ts.map +1 -0
- package/dist/cli/tui/search-panel.js +419 -0
- package/dist/cli/tui/shell.d.ts +11 -0
- package/dist/cli/tui/shell.d.ts.map +1 -1
- package/dist/cli/tui/shell.js +262 -46
- package/dist/contract/index.js +3 -2
- package/dist/dns/index.d.ts +1 -0
- package/dist/dns/index.d.ts.map +1 -1
- package/dist/dns/index.js +1 -0
- package/dist/dns/propagation.d.ts +19 -0
- package/dist/dns/propagation.d.ts.map +1 -0
- package/dist/dns/propagation.js +129 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +8 -1
- package/dist/transport/undici.d.ts.map +1 -1
- package/dist/transport/undici.js +72 -52
- package/dist/utils/colors.d.ts +16 -0
- package/dist/utils/colors.d.ts.map +1 -1
- package/dist/utils/colors.js +16 -0
- package/dist/utils/ip-intel.d.ts +15 -0
- package/dist/utils/ip-intel.d.ts.map +1 -0
- package/dist/utils/ip-intel.js +30 -0
- package/dist/utils/security-grader.d.ts +14 -0
- package/dist/utils/security-grader.d.ts.map +1 -0
- package/dist/utils/security-grader.js +132 -0
- package/dist/utils/tls-inspector.d.ts +6 -0
- package/dist/utils/tls-inspector.d.ts.map +1 -1
- package/dist/utils/tls-inspector.js +35 -1
- package/package.json +1 -1
package/dist/transport/undici.js
CHANGED
|
@@ -303,7 +303,7 @@ export class UndiciTransport {
|
|
|
303
303
|
connectTimeout: timeouts.connectTimeout,
|
|
304
304
|
headersTimeout: timeouts.headersTimeout,
|
|
305
305
|
bodyTimeout: timeouts.bodyTimeout,
|
|
306
|
-
maxRedirections:
|
|
306
|
+
maxRedirections: 0,
|
|
307
307
|
};
|
|
308
308
|
if (finalBody && (finalBody instanceof ReadableStream ||
|
|
309
309
|
(typeof finalBody.pipe === 'function') ||
|
|
@@ -347,39 +347,49 @@ export class UndiciTransport {
|
|
|
347
347
|
}
|
|
348
348
|
const statusCode = undiciResponse.statusCode;
|
|
349
349
|
const isRedirect = statusCode >= 300 && statusCode < 400;
|
|
350
|
-
if (
|
|
350
|
+
if (isRedirect && followRedirects && redirectCount < maxRedirects) {
|
|
351
351
|
const locationHeader = undiciResponse.headers['location'];
|
|
352
352
|
const location = Array.isArray(locationHeader) ? locationHeader[0] : locationHeader;
|
|
353
353
|
if (location) {
|
|
354
354
|
const nextUrl = new URL(location, currentUrl).toString();
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if (
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
355
|
+
if (handleRedirectsManually) {
|
|
356
|
+
const responseHeaders = new Headers();
|
|
357
|
+
for (const [key, value] of Object.entries(undiciResponse.headers)) {
|
|
358
|
+
if (value !== undefined) {
|
|
359
|
+
if (Array.isArray(value)) {
|
|
360
|
+
value.forEach(v => responseHeaders.append(key, v));
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
responseHeaders.set(key, value);
|
|
364
|
+
}
|
|
363
365
|
}
|
|
364
366
|
}
|
|
367
|
+
const redirectInfo = {
|
|
368
|
+
from: currentUrl,
|
|
369
|
+
to: nextUrl,
|
|
370
|
+
status: statusCode,
|
|
371
|
+
headers: responseHeaders,
|
|
372
|
+
};
|
|
373
|
+
const hookResult = await req.beforeRedirect(redirectInfo);
|
|
374
|
+
if (hookResult === false) {
|
|
375
|
+
const finalResponse = req.onDownloadProgress
|
|
376
|
+
? wrapDownloadResponse(undiciResponse, req.onDownloadProgress)
|
|
377
|
+
: undiciResponse;
|
|
378
|
+
return new HttpResponse(finalResponse, {
|
|
379
|
+
timings: requestContext.timings,
|
|
380
|
+
connection: requestContext.connection
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
if (typeof hookResult === 'string') {
|
|
384
|
+
currentUrl = hookResult;
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
currentUrl = nextUrl;
|
|
388
|
+
}
|
|
365
389
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
to: nextUrl,
|
|
369
|
-
status: statusCode,
|
|
370
|
-
headers: responseHeaders,
|
|
371
|
-
};
|
|
372
|
-
const hookResult = await req.beforeRedirect(redirectInfo);
|
|
373
|
-
if (hookResult === false) {
|
|
374
|
-
const finalResponse = req.onDownloadProgress
|
|
375
|
-
? wrapDownloadResponse(undiciResponse, req.onDownloadProgress)
|
|
376
|
-
: undiciResponse;
|
|
377
|
-
return new HttpResponse(finalResponse, {
|
|
378
|
-
timings: requestContext.timings,
|
|
379
|
-
connection: requestContext.connection
|
|
380
|
-
});
|
|
390
|
+
else {
|
|
391
|
+
currentUrl = nextUrl;
|
|
381
392
|
}
|
|
382
|
-
currentUrl = typeof hookResult === 'string' ? hookResult : nextUrl;
|
|
383
393
|
if (statusCode === 303 || ((statusCode === 301 || statusCode === 302) && currentMethod !== 'GET' && currentMethod !== 'HEAD')) {
|
|
384
394
|
currentMethod = 'GET';
|
|
385
395
|
currentBody = null;
|
|
@@ -518,7 +528,7 @@ export class UndiciTransport {
|
|
|
518
528
|
connectTimeout: timeouts.connectTimeout,
|
|
519
529
|
headersTimeout: timeouts.headersTimeout,
|
|
520
530
|
bodyTimeout: timeouts.bodyTimeout,
|
|
521
|
-
maxRedirections:
|
|
531
|
+
maxRedirections: 0,
|
|
522
532
|
};
|
|
523
533
|
if (finalBody && (finalBody instanceof ReadableStream ||
|
|
524
534
|
(typeof finalBody.pipe === 'function') ||
|
|
@@ -562,39 +572,49 @@ export class UndiciTransport {
|
|
|
562
572
|
}
|
|
563
573
|
const statusCode = undiciResponse.statusCode;
|
|
564
574
|
const isRedirect = statusCode >= 300 && statusCode < 400;
|
|
565
|
-
if (
|
|
575
|
+
if (isRedirect && followRedirects && redirectCount < maxRedirects) {
|
|
566
576
|
const locationHeader = undiciResponse.headers['location'];
|
|
567
577
|
const location = Array.isArray(locationHeader) ? locationHeader[0] : locationHeader;
|
|
568
578
|
if (location) {
|
|
569
579
|
const nextUrl = new URL(location, currentUrl).toString();
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
if (
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
580
|
+
if (handleRedirectsManually) {
|
|
581
|
+
const responseHeaders = new Headers();
|
|
582
|
+
for (const [key, value] of Object.entries(undiciResponse.headers)) {
|
|
583
|
+
if (value !== undefined) {
|
|
584
|
+
if (Array.isArray(value)) {
|
|
585
|
+
value.forEach(v => responseHeaders.append(key, v));
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
responseHeaders.set(key, value);
|
|
589
|
+
}
|
|
578
590
|
}
|
|
579
591
|
}
|
|
592
|
+
const redirectInfo = {
|
|
593
|
+
from: currentUrl,
|
|
594
|
+
to: nextUrl,
|
|
595
|
+
status: statusCode,
|
|
596
|
+
headers: responseHeaders,
|
|
597
|
+
};
|
|
598
|
+
const hookResult = await req.beforeRedirect(redirectInfo);
|
|
599
|
+
if (hookResult === false) {
|
|
600
|
+
const finalResponse = req.onDownloadProgress
|
|
601
|
+
? wrapDownloadResponse(undiciResponse, req.onDownloadProgress)
|
|
602
|
+
: undiciResponse;
|
|
603
|
+
return new HttpResponse(finalResponse, {
|
|
604
|
+
timings: {},
|
|
605
|
+
connection: {}
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
if (typeof hookResult === 'string') {
|
|
609
|
+
currentUrl = hookResult;
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
currentUrl = nextUrl;
|
|
613
|
+
}
|
|
580
614
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
to: nextUrl,
|
|
584
|
-
status: statusCode,
|
|
585
|
-
headers: responseHeaders,
|
|
586
|
-
};
|
|
587
|
-
const hookResult = await req.beforeRedirect(redirectInfo);
|
|
588
|
-
if (hookResult === false) {
|
|
589
|
-
const finalResponse = req.onDownloadProgress
|
|
590
|
-
? wrapDownloadResponse(undiciResponse, req.onDownloadProgress)
|
|
591
|
-
: undiciResponse;
|
|
592
|
-
return new HttpResponse(finalResponse, {
|
|
593
|
-
timings: {},
|
|
594
|
-
connection: {}
|
|
595
|
-
});
|
|
615
|
+
else {
|
|
616
|
+
currentUrl = nextUrl;
|
|
596
617
|
}
|
|
597
|
-
currentUrl = typeof hookResult === 'string' ? hookResult : nextUrl;
|
|
598
618
|
if (statusCode === 303 || ((statusCode === 301 || statusCode === 302) && currentMethod !== 'GET' && currentMethod !== 'HEAD')) {
|
|
599
619
|
currentMethod = 'GET';
|
|
600
620
|
currentBody = null;
|
package/dist/utils/colors.d.ts
CHANGED
|
@@ -9,6 +9,14 @@ export declare const magenta: (s: string | number) => string;
|
|
|
9
9
|
export declare const cyan: (s: string | number) => string;
|
|
10
10
|
export declare const white: (s: string | number) => string;
|
|
11
11
|
export declare const gray: (s: string | number) => string;
|
|
12
|
+
export declare const bgBlack: (s: string | number) => string;
|
|
13
|
+
export declare const bgRed: (s: string | number) => string;
|
|
14
|
+
export declare const bgGreen: (s: string | number) => string;
|
|
15
|
+
export declare const bgYellow: (s: string | number) => string;
|
|
16
|
+
export declare const bgBlue: (s: string | number) => string;
|
|
17
|
+
export declare const bgMagenta: (s: string | number) => string;
|
|
18
|
+
export declare const bgCyan: (s: string | number) => string;
|
|
19
|
+
export declare const bgWhite: (s: string | number) => string;
|
|
12
20
|
declare const colors: {
|
|
13
21
|
reset: (s: string) => string;
|
|
14
22
|
bold: (s: string | number) => string;
|
|
@@ -21,6 +29,14 @@ declare const colors: {
|
|
|
21
29
|
cyan: (s: string | number) => string;
|
|
22
30
|
white: (s: string | number) => string;
|
|
23
31
|
gray: (s: string | number) => string;
|
|
32
|
+
bgBlack: (s: string | number) => string;
|
|
33
|
+
bgRed: (s: string | number) => string;
|
|
34
|
+
bgGreen: (s: string | number) => string;
|
|
35
|
+
bgYellow: (s: string | number) => string;
|
|
36
|
+
bgBlue: (s: string | number) => string;
|
|
37
|
+
bgMagenta: (s: string | number) => string;
|
|
38
|
+
bgCyan: (s: string | number) => string;
|
|
39
|
+
bgWhite: (s: string | number) => string;
|
|
24
40
|
grey: (s: string | number) => string;
|
|
25
41
|
};
|
|
26
42
|
export default colors;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../../src/utils/colors.ts"],"names":[],"mappings":"AA6CA,eAAO,MAAM,KAAK,MAAmB,MAAM,WAA4C,CAAC;AACxF,eAAO,MAAM,IAAI,MAfY,MAAM,GAAG,MAAM,WAeb,CAAC;AAGhC,eAAO,MAAM,KAAK,MAlBW,MAAM,GAAG,MAAM,WAkBX,CAAC;AAClC,eAAO,MAAM,GAAG,MAnBa,MAAM,GAAG,MAAM,WAmBb,CAAC;AAChC,eAAO,MAAM,KAAK,MApBW,MAAM,GAAG,MAAM,WAoBX,CAAC;AAClC,eAAO,MAAM,MAAM,MArBU,MAAM,GAAG,MAAM,WAqBV,CAAC;AACnC,eAAO,MAAM,IAAI,MAtBY,MAAM,GAAG,MAAM,WAsBZ,CAAC;AACjC,eAAO,MAAM,OAAO,MAvBS,MAAM,GAAG,MAAM,WAuBT,CAAC;AACpC,eAAO,MAAM,IAAI,MAxBY,MAAM,GAAG,MAAM,WAwBZ,CAAC;AACjC,eAAO,MAAM,KAAK,MAzBW,MAAM,GAAG,MAAM,WAyBX,CAAC;AAClC,eAAO,MAAM,IAAI,MA1BY,MAAM,GAAG,MAAM,WA0BZ,CAAC;AAGjC,QAAA,MAAM,MAAM;
|
|
1
|
+
{"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../../src/utils/colors.ts"],"names":[],"mappings":"AA6CA,eAAO,MAAM,KAAK,MAAmB,MAAM,WAA4C,CAAC;AACxF,eAAO,MAAM,IAAI,MAfY,MAAM,GAAG,MAAM,WAeb,CAAC;AAGhC,eAAO,MAAM,KAAK,MAlBW,MAAM,GAAG,MAAM,WAkBX,CAAC;AAClC,eAAO,MAAM,GAAG,MAnBa,MAAM,GAAG,MAAM,WAmBb,CAAC;AAChC,eAAO,MAAM,KAAK,MApBW,MAAM,GAAG,MAAM,WAoBX,CAAC;AAClC,eAAO,MAAM,MAAM,MArBU,MAAM,GAAG,MAAM,WAqBV,CAAC;AACnC,eAAO,MAAM,IAAI,MAtBY,MAAM,GAAG,MAAM,WAsBZ,CAAC;AACjC,eAAO,MAAM,OAAO,MAvBS,MAAM,GAAG,MAAM,WAuBT,CAAC;AACpC,eAAO,MAAM,IAAI,MAxBY,MAAM,GAAG,MAAM,WAwBZ,CAAC;AACjC,eAAO,MAAM,KAAK,MAzBW,MAAM,GAAG,MAAM,WAyBX,CAAC;AAClC,eAAO,MAAM,IAAI,MA1BY,MAAM,GAAG,MAAM,WA0BZ,CAAC;AAGjC,eAAO,MAAM,OAAO,MA7BS,MAAM,GAAG,MAAM,WA6BT,CAAC;AACpC,eAAO,MAAM,KAAK,MA9BW,MAAM,GAAG,MAAM,WA8BX,CAAC;AAClC,eAAO,MAAM,OAAO,MA/BS,MAAM,GAAG,MAAM,WA+BT,CAAC;AACpC,eAAO,MAAM,QAAQ,MAhCQ,MAAM,GAAG,MAAM,WAgCR,CAAC;AACrC,eAAO,MAAM,MAAM,MAjCU,MAAM,GAAG,MAAM,WAiCV,CAAC;AACnC,eAAO,MAAM,SAAS,MAlCO,MAAM,GAAG,MAAM,WAkCP,CAAC;AACtC,eAAO,MAAM,MAAM,MAnCU,MAAM,GAAG,MAAM,WAmCV,CAAC;AACnC,eAAO,MAAM,OAAO,MApCS,MAAM,GAAG,MAAM,WAoCT,CAAC;AAGpC,QAAA,MAAM,MAAM;eAzByB,MAAM;cAdd,MAAM,GAAG,MAAM;eAAf,MAAM,GAAG,MAAM;aAAf,MAAM,GAAG,MAAM;eAAf,MAAM,GAAG,MAAM;gBAAf,MAAM,GAAG,MAAM;cAAf,MAAM,GAAG,MAAM;iBAAf,MAAM,GAAG,MAAM;cAAf,MAAM,GAAG,MAAM;eAAf,MAAM,GAAG,MAAM;cAAf,MAAM,GAAG,MAAM;iBAAf,MAAM,GAAG,MAAM;eAAf,MAAM,GAAG,MAAM;iBAAf,MAAM,GAAG,MAAM;kBAAf,MAAM,GAAG,MAAM;gBAAf,MAAM,GAAG,MAAM;mBAAf,MAAM,GAAG,MAAM;gBAAf,MAAM,GAAG,MAAM;iBAAf,MAAM,GAAG,MAAM;cAAf,MAAM,GAAG,MAAM;CA8D3C,CAAC;AAEF,eAAe,MAAM,CAAC"}
|
package/dist/utils/colors.js
CHANGED
|
@@ -33,6 +33,14 @@ export const magenta = code(35, 39);
|
|
|
33
33
|
export const cyan = code(36, 39);
|
|
34
34
|
export const white = code(37, 39);
|
|
35
35
|
export const gray = code(90, 39);
|
|
36
|
+
export const bgBlack = code(40, 49);
|
|
37
|
+
export const bgRed = code(41, 49);
|
|
38
|
+
export const bgGreen = code(42, 49);
|
|
39
|
+
export const bgYellow = code(43, 49);
|
|
40
|
+
export const bgBlue = code(44, 49);
|
|
41
|
+
export const bgMagenta = code(45, 49);
|
|
42
|
+
export const bgCyan = code(46, 49);
|
|
43
|
+
export const bgWhite = code(47, 49);
|
|
36
44
|
const colors = {
|
|
37
45
|
reset,
|
|
38
46
|
bold,
|
|
@@ -45,6 +53,14 @@ const colors = {
|
|
|
45
53
|
cyan,
|
|
46
54
|
white,
|
|
47
55
|
gray,
|
|
56
|
+
bgBlack,
|
|
57
|
+
bgRed,
|
|
58
|
+
bgGreen,
|
|
59
|
+
bgYellow,
|
|
60
|
+
bgBlue,
|
|
61
|
+
bgMagenta,
|
|
62
|
+
bgCyan,
|
|
63
|
+
bgWhite,
|
|
48
64
|
grey: gray,
|
|
49
65
|
};
|
|
50
66
|
export default colors;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface IpInfo {
|
|
2
|
+
ip: string;
|
|
3
|
+
hostname?: string;
|
|
4
|
+
city?: string;
|
|
5
|
+
region?: string;
|
|
6
|
+
country?: string;
|
|
7
|
+
loc?: string;
|
|
8
|
+
org?: string;
|
|
9
|
+
timezone?: string;
|
|
10
|
+
postal?: string;
|
|
11
|
+
anycast?: boolean;
|
|
12
|
+
bogon?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function getIpInfo(ip: string): Promise<IpInfo>;
|
|
15
|
+
//# sourceMappingURL=ip-intel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ip-intel.d.ts","sourceRoot":"","sources":["../../src/utils/ip-intel.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAOD,wBAAsB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoC3D"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Client } from '../core/client.js';
|
|
2
|
+
export async function getIpInfo(ip) {
|
|
3
|
+
if (ip.startsWith('127.') ||
|
|
4
|
+
ip.startsWith('10.') ||
|
|
5
|
+
ip.startsWith('192.168.') ||
|
|
6
|
+
ip.startsWith('169.254.') ||
|
|
7
|
+
ip === '::1') {
|
|
8
|
+
return { ip, bogon: true, org: 'Localhost / Private Network' };
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const client = new Client();
|
|
12
|
+
const url = `https://ipinfo.io/${ip}/json`;
|
|
13
|
+
const data = await client.get(url).json();
|
|
14
|
+
return {
|
|
15
|
+
ip: data.ip,
|
|
16
|
+
hostname: data.hostname,
|
|
17
|
+
city: data.city,
|
|
18
|
+
region: data.region,
|
|
19
|
+
country: data.country,
|
|
20
|
+
loc: data.loc,
|
|
21
|
+
org: data.org,
|
|
22
|
+
timezone: data.timezone,
|
|
23
|
+
postal: data.postal,
|
|
24
|
+
anycast: data.anycast
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
throw new Error(`Failed to fetch IP info: ${error.message}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface SecurityHeaderResult {
|
|
2
|
+
header: string;
|
|
3
|
+
value?: string;
|
|
4
|
+
status: 'pass' | 'warn' | 'fail';
|
|
5
|
+
score: number;
|
|
6
|
+
message: string;
|
|
7
|
+
}
|
|
8
|
+
export interface SecurityReport {
|
|
9
|
+
grade: string;
|
|
10
|
+
score: number;
|
|
11
|
+
details: SecurityHeaderResult[];
|
|
12
|
+
}
|
|
13
|
+
export declare function analyzeSecurityHeaders(headers: Headers): SecurityReport;
|
|
14
|
+
//# sourceMappingURL=security-grader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security-grader.d.ts","sourceRoot":"","sources":["../../src/utils/security-grader.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,oBAAoB,EAAE,CAAC;CACjC;AAkFD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,cAAc,CAkDvE"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const HEADERS_CHECKS = [
|
|
2
|
+
{
|
|
3
|
+
header: 'strict-transport-security',
|
|
4
|
+
weight: 25,
|
|
5
|
+
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.' };
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
header: 'content-security-policy',
|
|
20
|
+
weight: 25,
|
|
21
|
+
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.' };
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
header: 'x-frame-options',
|
|
33
|
+
weight: 15,
|
|
34
|
+
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.' };
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
header: 'x-content-type-options',
|
|
44
|
+
weight: 10,
|
|
45
|
+
check: (val) => {
|
|
46
|
+
if (!val)
|
|
47
|
+
return { status: 'fail', message: 'Missing X-Content-Type-Options.' };
|
|
48
|
+
if (val.toLowerCase() === 'nosniff')
|
|
49
|
+
return { status: 'pass', message: 'MIME sniffing disabled.' };
|
|
50
|
+
return { status: 'fail', message: 'Value must be "nosniff".' };
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
header: 'referrer-policy',
|
|
55
|
+
weight: 10,
|
|
56
|
+
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.' };
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
header: 'permissions-policy',
|
|
66
|
+
weight: 10,
|
|
67
|
+
check: (val) => {
|
|
68
|
+
if (!val)
|
|
69
|
+
return { status: 'warn', message: 'Missing Permissions-Policy (Feature-Policy).' };
|
|
70
|
+
return { status: 'pass', message: 'Permissions-Policy enabled.' };
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
header: 'server',
|
|
75
|
+
weight: 0,
|
|
76
|
+
check: (val) => {
|
|
77
|
+
if (val)
|
|
78
|
+
return { status: 'warn', message: 'Server header exposes technology stack.' };
|
|
79
|
+
return { status: 'pass', message: 'Server info hidden.' };
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
header: 'x-powered-by',
|
|
84
|
+
weight: 5,
|
|
85
|
+
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.' };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
];
|
|
92
|
+
export function analyzeSecurityHeaders(headers) {
|
|
93
|
+
let totalScore = 100;
|
|
94
|
+
let penalty = 0;
|
|
95
|
+
const details = [];
|
|
96
|
+
for (const check of HEADERS_CHECKS) {
|
|
97
|
+
const value = headers.get(check.header);
|
|
98
|
+
const result = check.check(value || undefined);
|
|
99
|
+
let itemPenalty = 0;
|
|
100
|
+
if (result.status === 'fail') {
|
|
101
|
+
itemPenalty = check.weight;
|
|
102
|
+
}
|
|
103
|
+
else if (result.status === 'warn') {
|
|
104
|
+
itemPenalty = Math.ceil(check.weight / 2);
|
|
105
|
+
}
|
|
106
|
+
penalty += itemPenalty;
|
|
107
|
+
details.push({
|
|
108
|
+
header: check.header,
|
|
109
|
+
value: value || undefined,
|
|
110
|
+
status: result.status,
|
|
111
|
+
score: -itemPenalty,
|
|
112
|
+
message: result.message
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
const finalScore = Math.max(0, totalScore - penalty);
|
|
116
|
+
let grade = 'F';
|
|
117
|
+
if (finalScore >= 95)
|
|
118
|
+
grade = 'A+';
|
|
119
|
+
else if (finalScore >= 90)
|
|
120
|
+
grade = 'A';
|
|
121
|
+
else if (finalScore >= 80)
|
|
122
|
+
grade = 'B';
|
|
123
|
+
else if (finalScore >= 70)
|
|
124
|
+
grade = 'C';
|
|
125
|
+
else if (finalScore >= 60)
|
|
126
|
+
grade = 'D';
|
|
127
|
+
return {
|
|
128
|
+
grade,
|
|
129
|
+
score: finalScore,
|
|
130
|
+
details
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -16,6 +16,12 @@ export interface TLSInfo {
|
|
|
16
16
|
} | null;
|
|
17
17
|
authorized: boolean;
|
|
18
18
|
authorizationError?: Error;
|
|
19
|
+
altNames?: string[];
|
|
20
|
+
pubkey: {
|
|
21
|
+
algo: string;
|
|
22
|
+
size: number;
|
|
23
|
+
} | null;
|
|
24
|
+
extKeyUsage?: string[];
|
|
19
25
|
}
|
|
20
26
|
export declare function inspectTLS(host: string, port?: number, options?: ConnectionOptions): Promise<TLSInfo>;
|
|
21
27
|
//# sourceMappingURL=tls-inspector.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tls-inspector.d.ts","sourceRoot":"","sources":["../../src/utils/tls-inspector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,iBAAiB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"tls-inspector.d.ts","sourceRoot":"","sources":["../../src/utils/tls-inspector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAGjE,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,EAAE,IAAI,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,GAAG,IAAI,CAAC;IACT,UAAU,EAAE,OAAO,CAAC;IACpB,kBAAkB,CAAC,EAAE,KAAK,CAAC;IAE3B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9C,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAY,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,OAAO,CAAC,CAkF9G"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { connect } from 'node:tls';
|
|
2
|
+
import * as crypto from 'node:crypto';
|
|
2
3
|
export function inspectTLS(host, port = 443, options = {}) {
|
|
3
4
|
return new Promise((resolve, reject) => {
|
|
4
5
|
const socket = connect(port, host, { ...options, servername: host }, () => {
|
|
@@ -11,6 +12,36 @@ export function inspectTLS(host, port = 443, options = {}) {
|
|
|
11
12
|
const validTo = new Date(cert.valid_to);
|
|
12
13
|
const now = new Date();
|
|
13
14
|
const daysRemaining = Math.floor((validTo.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
|
|
15
|
+
const altNames = cert.subjectaltname
|
|
16
|
+
? cert.subjectaltname.split(', ').map(s => s.replace(/^DNS:|^IP Address:/, '')).filter(Boolean)
|
|
17
|
+
: [];
|
|
18
|
+
let pubkey = null;
|
|
19
|
+
if (cert.pubkey) {
|
|
20
|
+
try {
|
|
21
|
+
const keyObject = crypto.createPublicKey(cert.pubkey);
|
|
22
|
+
let keySize;
|
|
23
|
+
const keyAlgo = keyObject.asymmetricKeyType || 'unknown';
|
|
24
|
+
if (keyObject.asymmetricKeyDetails) {
|
|
25
|
+
if (keyObject.asymmetricKeyDetails.modulusLength) {
|
|
26
|
+
keySize = keyObject.asymmetricKeyDetails.modulusLength;
|
|
27
|
+
}
|
|
28
|
+
else if (keyObject.asymmetricKeyDetails.namedCurve) {
|
|
29
|
+
const curve = keyObject.asymmetricKeyDetails.namedCurve;
|
|
30
|
+
if (curve.includes('256') || curve.includes('p256'))
|
|
31
|
+
keySize = 256;
|
|
32
|
+
else if (curve.includes('384') || curve.includes('p384'))
|
|
33
|
+
keySize = 384;
|
|
34
|
+
else if (curve.includes('521') || curve.includes('p521'))
|
|
35
|
+
keySize = 521;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (keySize) {
|
|
39
|
+
pubkey = { algo: keyAlgo, size: keySize };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
14
45
|
const info = {
|
|
15
46
|
valid: now >= validFrom && now <= validTo,
|
|
16
47
|
validFrom,
|
|
@@ -24,7 +55,10 @@ export function inspectTLS(host, port = 443, options = {}) {
|
|
|
24
55
|
protocol: socket.getProtocol(),
|
|
25
56
|
cipher: socket.getCipher(),
|
|
26
57
|
authorized: socket.authorized,
|
|
27
|
-
authorizationError: socket.authorizationError
|
|
58
|
+
authorizationError: socket.authorizationError,
|
|
59
|
+
altNames,
|
|
60
|
+
pubkey,
|
|
61
|
+
extKeyUsage: cert.ext_key_usage || []
|
|
28
62
|
};
|
|
29
63
|
socket.end();
|
|
30
64
|
resolve(info);
|