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.
Files changed (42) hide show
  1. package/dist/bench/generator.d.ts.map +1 -1
  2. package/dist/bench/generator.js +2 -1
  3. package/dist/bench/stats.d.ts +15 -1
  4. package/dist/bench/stats.d.ts.map +1 -1
  5. package/dist/bench/stats.js +84 -5
  6. package/dist/cli/index.js +100 -0
  7. package/dist/cli/tui/load-dashboard.d.ts.map +1 -1
  8. package/dist/cli/tui/load-dashboard.js +62 -8
  9. package/dist/cli/tui/scroll-buffer.d.ts +43 -0
  10. package/dist/cli/tui/scroll-buffer.d.ts.map +1 -0
  11. package/dist/cli/tui/scroll-buffer.js +162 -0
  12. package/dist/cli/tui/search-panel.d.ts +41 -0
  13. package/dist/cli/tui/search-panel.d.ts.map +1 -0
  14. package/dist/cli/tui/search-panel.js +419 -0
  15. package/dist/cli/tui/shell.d.ts +11 -0
  16. package/dist/cli/tui/shell.d.ts.map +1 -1
  17. package/dist/cli/tui/shell.js +262 -46
  18. package/dist/contract/index.js +3 -2
  19. package/dist/dns/index.d.ts +1 -0
  20. package/dist/dns/index.d.ts.map +1 -1
  21. package/dist/dns/index.js +1 -0
  22. package/dist/dns/propagation.d.ts +19 -0
  23. package/dist/dns/propagation.d.ts.map +1 -0
  24. package/dist/dns/propagation.js +129 -0
  25. package/dist/mcp/server.d.ts +2 -0
  26. package/dist/mcp/server.d.ts.map +1 -1
  27. package/dist/mcp/server.js +8 -1
  28. package/dist/transport/undici.d.ts.map +1 -1
  29. package/dist/transport/undici.js +72 -52
  30. package/dist/utils/colors.d.ts +16 -0
  31. package/dist/utils/colors.d.ts.map +1 -1
  32. package/dist/utils/colors.js +16 -0
  33. package/dist/utils/ip-intel.d.ts +15 -0
  34. package/dist/utils/ip-intel.d.ts.map +1 -0
  35. package/dist/utils/ip-intel.js +30 -0
  36. package/dist/utils/security-grader.d.ts +14 -0
  37. package/dist/utils/security-grader.d.ts.map +1 -0
  38. package/dist/utils/security-grader.js +132 -0
  39. package/dist/utils/tls-inspector.d.ts +6 -0
  40. package/dist/utils/tls-inspector.d.ts.map +1 -1
  41. package/dist/utils/tls-inspector.js +35 -1
  42. package/package.json +1 -1
@@ -303,7 +303,7 @@ export class UndiciTransport {
303
303
  connectTimeout: timeouts.connectTimeout,
304
304
  headersTimeout: timeouts.headersTimeout,
305
305
  bodyTimeout: timeouts.bodyTimeout,
306
- maxRedirections: handleRedirectsManually ? 0 : this.options.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 (handleRedirectsManually && isRedirect && followRedirects && redirectCount < maxRedirects) {
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
- const responseHeaders = new Headers();
356
- for (const [key, value] of Object.entries(undiciResponse.headers)) {
357
- if (value !== undefined) {
358
- if (Array.isArray(value)) {
359
- value.forEach(v => responseHeaders.append(key, v));
360
- }
361
- else {
362
- responseHeaders.set(key, value);
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
- const redirectInfo = {
367
- from: currentUrl,
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: handleRedirectsManually ? 0 : this.options.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 (handleRedirectsManually && isRedirect && followRedirects && redirectCount < maxRedirects) {
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
- const responseHeaders = new Headers();
571
- for (const [key, value] of Object.entries(undiciResponse.headers)) {
572
- if (value !== undefined) {
573
- if (Array.isArray(value)) {
574
- value.forEach(v => responseHeaders.append(key, v));
575
- }
576
- else {
577
- responseHeaders.set(key, value);
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
- const redirectInfo = {
582
- from: currentUrl,
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;
@@ -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;eAfyB,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;cAAf,MAAM,GAAG,MAAM;CA2C3C,CAAC;AAEF,eAAe,MAAM,CAAC"}
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"}
@@ -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;AAEjE,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;CAC5B;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAY,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,OAAO,CAAC,CA4C9G"}
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "recker",
3
- "version": "1.0.15-next.dac946a",
3
+ "version": "1.0.15-next.eb07368",
4
4
  "description": "AI & DevX focused HTTP client for Node.js 18+",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",