From ca7655f310758804e75743ec4600b57adfcbe465 Mon Sep 17 00:00:00 2001 From: neverm1ndthat Date: Tue, 6 Oct 2026 11:25:52 +0800 Subject: [PATCH] fix: reject NaN sensitivityThreshold in AnomalyDetector NaN <= 0 and NaN > 1 are both false, so the existing range check silently accepted NaN and classified every finite score as normal. Add Number.isFinite() guard before the range check and add a regression test covering both NaN rejection and upper-boundary acceptance. Fixes #1008 --- src/anomalyDetector.ts | 6 +++++- test/anomalyDetector.nan.test.ts | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 test/anomalyDetector.nan.test.ts diff --git a/src/anomalyDetector.ts b/src/anomalyDetector.ts index 7f69bbbf..3df8f5bd 100644 --- a/src/anomalyDetector.ts +++ b/src/anomalyDetector.ts @@ -77,7 +77,11 @@ export class AnomalyDetector { this.rapidCycleSeconds = options.rapidCycleSeconds ?? 300; this.maxAmountVariance = options.maxAmountVariance ?? 0.8; this.sensitivityThreshold = options.sensitivityThreshold ?? 0.8; - if (this.sensitivityThreshold <= 0 || this.sensitivityThreshold > 1) { + if ( + !Number.isFinite(this.sensitivityThreshold) || + this.sensitivityThreshold <= 0 || + this.sensitivityThreshold > 1 + ) { throw new RangeError("sensitivityThreshold must be in the range (0, 1]"); } this.now = options.now ?? (() => Math.floor(Date.now() / 1000)); diff --git a/test/anomalyDetector.nan.test.ts b/test/anomalyDetector.nan.test.ts new file mode 100644 index 00000000..73781533 --- /dev/null +++ b/test/anomalyDetector.nan.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from "vitest"; +import { AnomalyDetector } from "../src/anomalyDetector.js"; + +describe("AnomalyDetector sensitivityThreshold validation", () => { + it("rejects NaN instead of silently disabling score alerts", () => { + expect(() => new AnomalyDetector({ sensitivityThreshold: Number.NaN })).toThrow( + RangeError + ); + }); + + it("still accepts finite values at the upper boundary", () => { + expect(() => new AnomalyDetector({ sensitivityThreshold: 1 })).not.toThrow(); + }); +});