From 36df080acd3482b03c8768c427271337c5cb8807 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Jun 2026 05:04:51 +0000 Subject: [PATCH] fix: guard against NaN/non-positive --timeout causing immediate request abort When `--timeout abc` is passed, parseInt returns NaN. Since NaN is not nullish, `NaN ?? 10000` stays NaN, and setTimeout(abort, NaN) fires immediately in JS (NaN coerces to 0), aborting every request. cli.ts: validate the parsed value and exit(1) with a clear message. fetch.ts: treat any non-finite or non-positive timeoutMs as the default 10000 ms so the public API is safe even when called directly with bad input. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_011hppUsheemA66AhrzS5kDT --- src/cli.ts | 7 ++++++- src/fetch.ts | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index a6d2e8b..9864f79 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -81,7 +81,12 @@ async function main() { const jsonMode = args.includes('--json'); const timeoutArg = args.find((a, i) => a === '--timeout' && args[i + 1]); - const timeoutMs = timeoutArg ? parseInt(args[args.indexOf('--timeout') + 1], 10) : undefined; + const timeoutRaw = timeoutArg ? parseInt(args[args.indexOf('--timeout') + 1], 10) : undefined; + if (timeoutRaw !== undefined && (isNaN(timeoutRaw) || timeoutRaw <= 0)) { + console.error('Error: --timeout must be a positive integer (milliseconds), e.g. --timeout 5000'); + process.exit(1); + } + const timeoutMs = timeoutRaw; const url = args.find(a => !a.startsWith('--') && a !== String(timeoutMs)); if (!url) { console.error('Usage: security-headers [--json] [--timeout ms] [--help] [--version]'); diff --git a/src/fetch.ts b/src/fetch.ts index 22dc35c..1d47069 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -3,7 +3,9 @@ export interface FetchOptions { } export async function fetchHeaders(url: string, options?: FetchOptions): Promise> { - const timeoutMs = options?.timeoutMs ?? 10000; + const timeoutMs = (options?.timeoutMs != null && Number.isFinite(options.timeoutMs) && options.timeoutMs > 0) + ? options.timeoutMs + : 10000; const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); try {