recker 1.0.15-next.c7370be → 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/cli/index.js CHANGED
@@ -279,6 +279,85 @@ complete -F _rek_completions rek
279
279
  const { openSearchPanel } = await import('./tui/search-panel.js');
280
280
  await openSearchPanel(query || undefined);
281
281
  });
282
+ program
283
+ .command('security')
284
+ .alias('headers')
285
+ .description('Analyze HTTP response headers for security best practices')
286
+ .argument('<url>', 'URL to analyze')
287
+ .action(async (url) => {
288
+ if (!url.startsWith('http'))
289
+ url = `https://${url}`;
290
+ const { createClient } = await import('../core/client.js');
291
+ const { analyzeSecurityHeaders } = await import('../utils/security-grader.js');
292
+ console.log(pc.gray(`Analyzing security headers for ${url}...`));
293
+ try {
294
+ const origin = new URL(url).origin;
295
+ const client = createClient({ baseUrl: origin });
296
+ const res = await client.get(url);
297
+ const report = analyzeSecurityHeaders(res.headers);
298
+ let gradeColor = pc.red;
299
+ if (report.grade.startsWith('A'))
300
+ gradeColor = pc.green;
301
+ if (report.grade.startsWith('B'))
302
+ gradeColor = pc.blue;
303
+ if (report.grade.startsWith('C'))
304
+ gradeColor = pc.yellow;
305
+ console.log(`
306
+ ${pc.bold(pc.cyan('šŸ›”ļø Security Headers Report'))}
307
+ Grade: ${gradeColor(pc.bold(report.grade))} (${report.score}/100)
308
+
309
+ ${pc.bold('Details:')}`);
310
+ report.details.forEach(item => {
311
+ const icon = item.status === 'pass' ? pc.green('āœ”') : item.status === 'warn' ? pc.yellow('⚠') : pc.red('āœ–');
312
+ const headerName = pc.bold(item.header);
313
+ const value = item.value ? pc.gray(`= ${item.value.length > 50 ? item.value.slice(0, 47) + '...' : item.value}`) : pc.gray('(missing)');
314
+ console.log(` ${icon} ${headerName} ${value}`);
315
+ if (item.status !== 'pass') {
316
+ console.log(` ${pc.red('→')} ${item.message}`);
317
+ }
318
+ });
319
+ console.log('');
320
+ }
321
+ catch (error) {
322
+ console.error(pc.red(`Analysis failed: ${error.message}`));
323
+ process.exit(1);
324
+ }
325
+ });
326
+ program
327
+ .command('ip')
328
+ .description('Get IP address intelligence (Geo, ASN, ISP)')
329
+ .argument('<address>', 'IP address to lookup')
330
+ .action(async (address) => {
331
+ const { getIpInfo } = await import('../utils/ip-intel.js');
332
+ console.log(pc.gray(`Fetching intelligence for ${address}...`));
333
+ try {
334
+ const info = await getIpInfo(address);
335
+ if (info.bogon) {
336
+ console.log(pc.yellow(`\n⚠ ${address} is a Bogon/Private IP.`));
337
+ return;
338
+ }
339
+ console.log(`
340
+ ${pc.bold(pc.cyan('šŸŒ IP Intelligence Report'))}
341
+
342
+ ${pc.bold('Location:')}
343
+ ${pc.gray('City:')} ${info.city || 'N/A'}
344
+ ${pc.gray('Region:')} ${info.region || 'N/A'}
345
+ ${pc.gray('Country:')} ${info.country || 'N/A'}
346
+ ${pc.gray('Timezone:')} ${info.timezone || 'N/A'}
347
+ ${pc.gray('Coords:')} ${info.loc ? pc.cyan(info.loc) : 'N/A'}
348
+
349
+ ${pc.bold('Network:')}
350
+ ${pc.gray('IP:')} ${info.ip}
351
+ ${pc.gray('Hostname:')} ${info.hostname || 'N/A'}
352
+ ${pc.gray('ASN/Org:')} ${info.org || 'N/A'}
353
+ ${pc.gray('Anycast:')} ${info.anycast ? pc.green('Yes') : pc.gray('No')}
354
+ `);
355
+ }
356
+ catch (err) {
357
+ console.error(pc.red(`IP Lookup Failed: ${err.message}`));
358
+ process.exit(1);
359
+ }
360
+ });
282
361
  const dns = program.command('dns').description('DNS tools and diagnostics');
283
362
  dns
284
363
  .command('propagate')
@@ -36,7 +36,7 @@ export declare class ScrollBuffer extends EventEmitter {
36
36
  percent: number;
37
37
  };
38
38
  }
39
- export declare function parseScrollKey(data: Buffer): 'pageUp' | 'pageDown' | 'scrollUp' | 'scrollDown' | 'home' | 'end' | 'escape' | null;
39
+ export declare function parseScrollKey(data: Buffer): 'pageUp' | 'pageDown' | 'scrollUp' | 'scrollDown' | 'home' | 'end' | 'quit' | null;
40
40
  export declare function parseMouseScroll(data: Buffer): 'scrollUp' | 'scrollDown' | null;
41
41
  export declare function enableMouseReporting(): void;
42
42
  export declare function disableMouseReporting(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"scroll-buffer.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/scroll-buffer.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,YAAa,SAAQ,YAAY;IAC5C,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,aAAa,CAA4C;IACjE,OAAO,CAAC,aAAa,CAAc;gBAEvB,OAAO,GAAE,mBAAwB;IAS7C,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAuB5B,KAAK,IAAI,IAAI;IAUb,IAAI,SAAS,IAAI,MAAM,CAEtB;IAKD,IAAI,QAAQ,IAAI,MAAM,CAErB;IAKD,IAAI,YAAY,IAAI,OAAO,CAE1B;IAKD,QAAQ,CAAC,KAAK,GAAE,MAAU,GAAG,OAAO;IAcpC,UAAU,CAAC,KAAK,GAAE,MAAU,GAAG,OAAO;IAatC,MAAM,IAAI,OAAO;IAOjB,QAAQ,IAAI,OAAO;IAOnB,WAAW,IAAI,IAAI;IAOnB,cAAc,IAAI,IAAI;IAOtB,eAAe,IAAI,MAAM,EAAE;IAS3B,MAAM,IAAI,MAAM;IAsBhB,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAOrC,eAAe,IAAI,IAAI;IAcvB,cAAc,IAAI,IAAI;IAWtB,IAAI,YAAY,IAAI,OAAO,CAE1B;IAKD,KAAK,IAAI,IAAI;IASb,aAAa,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;CAOrE;AAKD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,IAAI,CAyBjI;AAOD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,YAAY,GAAG,IAAI,CAqB/E;AAKD,wBAAgB,oBAAoB,IAAI,IAAI,CAK3C;AAKD,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C"}
1
+ {"version":3,"file":"scroll-buffer.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/scroll-buffer.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,YAAa,SAAQ,YAAY;IAC5C,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,aAAa,CAA4C;IACjE,OAAO,CAAC,aAAa,CAAc;gBAEvB,OAAO,GAAE,mBAAwB;IAS7C,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAuB5B,KAAK,IAAI,IAAI;IAUb,IAAI,SAAS,IAAI,MAAM,CAEtB;IAKD,IAAI,QAAQ,IAAI,MAAM,CAErB;IAKD,IAAI,YAAY,IAAI,OAAO,CAE1B;IAKD,QAAQ,CAAC,KAAK,GAAE,MAAU,GAAG,OAAO;IAcpC,UAAU,CAAC,KAAK,GAAE,MAAU,GAAG,OAAO;IAatC,MAAM,IAAI,OAAO;IAOjB,QAAQ,IAAI,OAAO;IAOnB,WAAW,IAAI,IAAI;IAOnB,cAAc,IAAI,IAAI;IAOtB,eAAe,IAAI,MAAM,EAAE;IAS3B,MAAM,IAAI,MAAM;IAsBhB,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAOrC,eAAe,IAAI,IAAI;IAcvB,cAAc,IAAI,IAAI;IAWtB,IAAI,YAAY,IAAI,OAAO,CAE1B;IAKD,KAAK,IAAI,IAAI;IASb,aAAa,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;CAOrE;AAKD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,CAyB/H;AAOD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,YAAY,GAAG,IAAI,CAqB/E;AAKD,wBAAgB,oBAAoB,IAAI,IAAI,CAK3C;AAKD,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C"}
@@ -131,8 +131,8 @@ export function parseScrollKey(data) {
131
131
  return 'home';
132
132
  if (str === '\x1b[F' || str === '\x1b[4~' || str === '\x1bOF')
133
133
  return 'end';
134
- if (str === '\x1b' || str === '\x1b\x1b')
135
- return 'escape';
134
+ if (str === 'q' || str === 'Q')
135
+ return 'quit';
136
136
  return null;
137
137
  }
138
138
  export function parseMouseScroll(data) {
@@ -1 +1 @@
1
- {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/shell.ts"],"names":[],"mappings":"AAsCA,qBAAa,QAAQ;IACnB,OAAO,CAAC,EAAE,CAAsB;IAChC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,mBAAmB,CAA4C;IACvE,OAAO,CAAC,YAAY,CAAkB;;YAgBxB,iBAAiB;IAe/B,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,aAAa;IAiBrB,OAAO,CAAC,SAAS;IAcJ,KAAK;IA8ClB,OAAO,CAAC,kBAAkB;IA2B1B,OAAO,CAAC,oBAAoB;IAW5B,OAAO,CAAC,qBAAqB;IA2B7B,OAAO,CAAC,eAAe;IAmEvB,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,gBAAgB;IA8BxB,OAAO,CAAC,MAAM;YAKA,aAAa;YAoMb,kBAAkB;YAkBlB,SAAS;YAkBT,WAAW;IA0DzB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,QAAQ;IAoCV,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM;IA6CnC,OAAO,CAAC,gBAAgB;IAqBxB,OAAO,CAAC,UAAU;YAeJ,cAAc;YAyEd,QAAQ;YA8GR,MAAM;YA2DN,MAAM;YA2EN,iBAAiB;YAuBjB,OAAO;YA+DP,OAAO;YA0CP,QAAQ;YAoER,SAAS;YAsCT,aAAa;YA8Bb,aAAa;YA+Bb,aAAa;YA6Bb,cAAc;YAkCd,eAAe;YA+Ef,gBAAgB;YAmEhB,YAAY;YAiEZ,mBAAmB;YAsFnB,QAAQ;YA0FR,YAAY;YAoCZ,YAAY;YA6CZ,WAAW;IA6CzB,OAAO,CAAC,UAAU;IA4GlB,OAAO,CAAC,WAAW;YAgFL,eAAe;YAkBf,cAAc;YAgDd,SAAS;YAgBT,UAAU;YAuBV,UAAU;IAwBxB,OAAO,CAAC,aAAa;IAuCrB,OAAO,CAAC,SAAS;CA8ElB"}
1
+ {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/shell.ts"],"names":[],"mappings":"AAsCA,qBAAa,QAAQ;IACnB,OAAO,CAAC,EAAE,CAAsB;IAChC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,mBAAmB,CAA4C;IACvE,OAAO,CAAC,YAAY,CAAkB;;YAgBxB,iBAAiB;IAe/B,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,aAAa;IAiBrB,OAAO,CAAC,SAAS;IAcJ,KAAK;IA8ClB,OAAO,CAAC,kBAAkB;IA2B1B,OAAO,CAAC,oBAAoB;IAW5B,OAAO,CAAC,qBAAqB;IAqE7B,OAAO,CAAC,eAAe;IAmEvB,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,gBAAgB;IA8BxB,OAAO,CAAC,MAAM;YAKA,aAAa;YAoMb,kBAAkB;YAkBlB,SAAS;YAkBT,WAAW;IA0DzB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,QAAQ;IAoCV,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM;IA6CnC,OAAO,CAAC,gBAAgB;IAqBxB,OAAO,CAAC,UAAU;YAeJ,cAAc;YAyEd,QAAQ;YA8GR,MAAM;YA4FN,MAAM;YA2EN,iBAAiB;YAuBjB,OAAO;YA+DP,OAAO;YA0CP,QAAQ;YAoER,SAAS;YAsCT,aAAa;YA8Bb,aAAa;YA+Bb,aAAa;YA6Bb,cAAc;YAkCd,eAAe;YA+Ef,gBAAgB;YAmEhB,YAAY;YAiEZ,mBAAmB;YAsFnB,QAAQ;YA0FR,YAAY;YAoCZ,YAAY;YA6CZ,WAAW;IA6CzB,OAAO,CAAC,UAAU;IA4GlB,OAAO,CAAC,WAAW;YAgFL,eAAe;YAkBf,cAAc;YAgDd,SAAS;YAgBT,UAAU;YAuBV,UAAU;IAwBxB,OAAO,CAAC,aAAa;IAuCrB,OAAO,CAAC,SAAS;CA8ElB"}
@@ -152,18 +152,44 @@ export class RekShell {
152
152
  setupScrollKeyHandler() {
153
153
  enableMouseReporting();
154
154
  if (process.stdin.isTTY) {
155
- process.stdin.on('data', (data) => {
156
- const scrollKey = parseScrollKey(data);
157
- if (scrollKey) {
158
- this.handleScrollKey(scrollKey);
159
- return;
160
- }
161
- const mouseScroll = parseMouseScroll(data);
162
- if (mouseScroll) {
163
- this.handleScrollKey(mouseScroll);
164
- return;
155
+ const originalEmit = process.stdin.emit.bind(process.stdin);
156
+ const self = this;
157
+ process.stdin.emit = function (event, ...args) {
158
+ if (event === 'data') {
159
+ const data = args[0];
160
+ const str = data.toString();
161
+ if (str.includes('\x1b[<')) {
162
+ const mouseScroll = parseMouseScroll(data);
163
+ if (mouseScroll) {
164
+ self.handleScrollKey(mouseScroll);
165
+ }
166
+ return true;
167
+ }
168
+ if (data.length >= 6 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x4d) {
169
+ const mouseScroll = parseMouseScroll(data);
170
+ if (mouseScroll) {
171
+ self.handleScrollKey(mouseScroll);
172
+ }
173
+ return true;
174
+ }
175
+ const scrollKey = parseScrollKey(data);
176
+ if (scrollKey) {
177
+ if (scrollKey === 'quit') {
178
+ if (self.inScrollMode) {
179
+ self.exitScrollMode();
180
+ return true;
181
+ }
182
+ return originalEmit(event, ...args);
183
+ }
184
+ self.handleScrollKey(scrollKey);
185
+ return true;
186
+ }
187
+ if (self.inScrollMode) {
188
+ return true;
189
+ }
165
190
  }
166
- });
191
+ return originalEmit(event, ...args);
192
+ };
167
193
  }
168
194
  }
169
195
  handleScrollKey(key) {
@@ -209,7 +235,7 @@ export class RekShell {
209
235
  return;
210
236
  }
211
237
  break;
212
- case 'escape':
238
+ case 'quit':
213
239
  if (this.inScrollMode) {
214
240
  this.exitScrollMode();
215
241
  return;
@@ -261,7 +287,7 @@ export class RekShell {
261
287
  const scrollInfo = this.scrollBuffer.isScrolledUp
262
288
  ? colors.yellow(`↑ ${this.scrollBuffer.position} lines | ${info.percent}% | `)
263
289
  : '';
264
- const helpText = colors.gray('Page Up/Down • Home/End • Esc to exit');
290
+ const helpText = colors.gray('Page Up/Down • Home/End • Q to exit');
265
291
  const statusBar = `\x1b[${rows};1H\x1b[7m ${scrollInfo}${helpText} \x1b[0m`;
266
292
  this.originalStdoutWrite(statusBar);
267
293
  }
@@ -806,25 +832,56 @@ export class RekShell {
806
832
  const duration = Math.round(performance.now() - startTime);
807
833
  const statusIcon = info.valid ? colors.green('āœ”') : colors.red('āœ–');
808
834
  const statusText = info.valid ? colors.green('Valid') : colors.red('Invalid/Expired');
809
- console.log(`${statusIcon} Certificate ${statusText}` + colors.gray(` (${duration}ms)\n`));
810
- console.log(colors.bold(' Certificate:'));
811
- console.log(` ${colors.cyan('Subject')}: ${info.subject?.CN || info.subject?.O || 'N/A'}`);
812
- console.log(` ${colors.cyan('Issuer')}: ${info.issuer?.CN || info.issuer?.O || 'N/A'}`);
813
- console.log(` ${colors.cyan('Valid From')}: ${info.validFrom.toISOString()}`);
814
- console.log(` ${colors.cyan('Valid To')}: ${info.validTo.toISOString()}`);
815
835
  const daysColor = info.daysRemaining < 30 ? colors.red : info.daysRemaining < 90 ? colors.yellow : colors.green;
816
- console.log(` ${colors.cyan('Days Remaining')}: ${daysColor(String(info.daysRemaining))}`);
817
- console.log(colors.bold('\n Connection:'));
818
- console.log(` ${colors.cyan('Protocol')}: ${info.protocol || 'N/A'}`);
819
- console.log(` ${colors.cyan('Cipher')}: ${info.cipher?.name || 'N/A'}`);
820
- console.log(` ${colors.cyan('Authorized')}: ${info.authorized ? colors.green('Yes') : colors.red('No')}`);
836
+ console.log(`\n${colors.bold(colors.cyan('šŸ”’ TLS/SSL Report'))}`);
837
+ console.log(`${statusIcon} Certificate ${statusText}` + colors.gray(` (${duration}ms)\n`));
838
+ console.log(colors.bold('Certificate:'));
839
+ console.log(` ${colors.gray('Subject:')}`);
840
+ for (const key of Object.keys(info.subject || {})) {
841
+ console.log(` ${colors.gray(key.padEnd(10))}: ${info.subject[key]}`);
842
+ }
843
+ console.log(` ${colors.gray('Issuer:')}`);
844
+ for (const key of Object.keys(info.issuer || {})) {
845
+ console.log(` ${colors.gray(key.padEnd(10))}: ${info.issuer[key]}`);
846
+ }
847
+ console.log(` ${colors.gray('Expires:')} ${daysColor(info.daysRemaining + ' days')} (${info.validTo.toISOString().split('T')[0]})`);
848
+ console.log(` ${colors.gray('Valid From:')} ${info.validFrom.toISOString().split('T')[0]}`);
849
+ console.log(` ${colors.gray('Valid To:')} ${info.validTo.toISOString().split('T')[0]}`);
850
+ console.log(` ${colors.gray('Valid:')} ${info.valid ? colors.green('Yes') : colors.red('No')}`);
851
+ if (info.altNames && info.altNames.length > 0) {
852
+ console.log(colors.bold('\nSubject Alternative Names (SANs):'));
853
+ for (const name of info.altNames) {
854
+ console.log(` ${colors.cyan('→')} ${name}`);
855
+ }
856
+ }
857
+ console.log(colors.bold('\nPublic Key:'));
858
+ if (info.pubkey) {
859
+ console.log(` ${colors.gray('Algorithm:')} ${info.pubkey.algo}`);
860
+ console.log(` ${colors.gray('Size:')} ${info.pubkey.size} bits`);
861
+ }
862
+ else {
863
+ console.log(' Not available');
864
+ }
865
+ console.log(colors.bold('\nExtended Key Usage:'));
866
+ if (info.extKeyUsage && info.extKeyUsage.length > 0) {
867
+ for (const oid of info.extKeyUsage) {
868
+ console.log(` ${colors.cyan('→')} ${oid}`);
869
+ }
870
+ }
871
+ else {
872
+ console.log(' None');
873
+ }
874
+ console.log(colors.bold('\nConnection:'));
875
+ console.log(` ${colors.gray('Protocol:')} ${info.protocol || 'N/A'}`);
876
+ console.log(` ${colors.gray('Cipher:')} ${info.cipher?.name || 'N/A'}`);
877
+ console.log(` ${colors.gray('Auth:')} ${info.authorized ? colors.green('Trusted') : colors.red('Untrusted')}`);
821
878
  if (info.authorizationError) {
822
- console.log(` ${colors.cyan('Auth Error')}: ${colors.red(String(info.authorizationError))}`);
879
+ console.log(` ${colors.gray('Auth Error:')} ${colors.red(String(info.authorizationError))}`);
823
880
  }
824
- console.log(colors.bold('\n Fingerprints:'));
825
- console.log(` ${colors.cyan('SHA1')}: ${info.fingerprint}`);
826
- console.log(` ${colors.cyan('SHA256')}: ${info.fingerprint256}`);
827
- console.log(` ${colors.cyan('Serial')}: ${info.serialNumber}`);
881
+ console.log(colors.bold('\nFingerprints:'));
882
+ console.log(` ${colors.gray('SHA1:')} ${info.fingerprint}`);
883
+ console.log(` ${colors.gray('SHA256:')} ${info.fingerprint256}`);
884
+ console.log(` ${colors.gray('Serial:')} ${info.serialNumber}`);
828
885
  this.lastResponse = info;
829
886
  }
830
887
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"undici.d.ts","sourceRoot":"","sources":["../../src/transport/undici.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,aAAa,EAAE,cAAc,EAAW,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAgB,UAAU,EAAkD,MAAM,mBAAmB,CAAC;AAOxN,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAwNzD,UAAU,sBAAsB;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;IAC9B,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IAMtB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAqDD,qBAAa,eAAgB,YAAW,SAAS;IAC/C,OAAO,CAAC,MAAM,CAAC,cAAc,CAAK;IAElC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,QAAQ,CAAC,CAAQ;IACzB,OAAO,CAAC,YAAY,CAAC,CAAe;IACpC,OAAO,CAAC,eAAe,CAAC,CAAW;IACnC,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,aAAa,CAAU;gBAEnB,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B;IAmG3D,QAAQ,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;YA4U7C,YAAY;CAkQ3B"}
1
+ {"version":3,"file":"undici.d.ts","sourceRoot":"","sources":["../../src/transport/undici.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,aAAa,EAAE,cAAc,EAAW,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAgB,UAAU,EAAkD,MAAM,mBAAmB,CAAC;AAOxN,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAwNzD,UAAU,sBAAsB;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;IAC9B,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IAMtB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAqDD,qBAAa,eAAgB,YAAW,SAAS;IAC/C,OAAO,CAAC,MAAM,CAAC,cAAc,CAAK;IAElC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,QAAQ,CAAC,CAAQ;IACzB,OAAO,CAAC,YAAY,CAAC,CAAe;IACpC,OAAO,CAAC,eAAe,CAAC,CAAW;IACnC,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,aAAa,CAAU;gBAEnB,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B;IAmG3D,QAAQ,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;YAqV7C,YAAY;CA0Q3B"}
@@ -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;
@@ -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.c7370be",
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",