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 +79 -0
- package/dist/cli/tui/scroll-buffer.d.ts +1 -1
- package/dist/cli/tui/scroll-buffer.d.ts.map +1 -1
- package/dist/cli/tui/scroll-buffer.js +2 -2
- package/dist/cli/tui/shell.d.ts.map +1 -1
- package/dist/cli/tui/shell.js +86 -29
- package/dist/transport/undici.d.ts.map +1 -1
- package/dist/transport/undici.js +72 -52
- 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/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' | '
|
|
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,
|
|
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 === '
|
|
135
|
-
return '
|
|
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;
|
|
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"}
|
package/dist/cli/tui/shell.js
CHANGED
|
@@ -152,18 +152,44 @@ export class RekShell {
|
|
|
152
152
|
setupScrollKeyHandler() {
|
|
153
153
|
enableMouseReporting();
|
|
154
154
|
if (process.stdin.isTTY) {
|
|
155
|
-
process.stdin.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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 '
|
|
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 ā¢
|
|
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(
|
|
817
|
-
console.log(colors.
|
|
818
|
-
console.log(
|
|
819
|
-
console.log(`
|
|
820
|
-
|
|
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(`
|
|
879
|
+
console.log(` ${colors.gray('Auth Error:')} ${colors.red(String(info.authorizationError))}`);
|
|
823
880
|
}
|
|
824
|
-
console.log(colors.bold('\
|
|
825
|
-
console.log(`
|
|
826
|
-
console.log(`
|
|
827
|
-
console.log(`
|
|
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;
|
|
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"}
|
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;
|
|
@@ -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);
|