diff --git a/.changeset/sentinel-dos-fix.md b/.changeset/sentinel-dos-fix.md new file mode 100644 index 00000000..8974adc7 --- /dev/null +++ b/.changeset/sentinel-dos-fix.md @@ -0,0 +1,5 @@ +--- +"node-version": patch +--- + +Fix: Add DoS protection by limiting version string length in comparison methods. diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 356c7f03..d7f38753 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -2,3 +2,8 @@ **Vulnerability:** The version comparison logic in `src/index.ts` failed to correctly parse version strings prefixed with `v` (e.g., `"v10.0.0"`). The `Number()` function would return `NaN` for segments like `"v10"`, leading to incorrect comparison results (often evaluating as equal due to `NaN` comparison behavior). **Learning:** Naive number parsing of version strings can be dangerous. Standard semver libraries handle this, but custom implementations must be careful. Specifically, `NaN` in comparisons can lead to "fail open" scenarios where a lower version is considered "at least" a higher version because the check returns false for both `<` and `>`, falling through to equality or default cases. **Prevention:** Always sanitize version strings (strip non-numeric prefixes) before parsing. When implementing custom version comparison, handle `NaN` explicitly or use a robust library. Ensure inputs are validated or normalized. + +## 2026-01-15 - [DoS via Long Version Strings] +**Vulnerability:** The `compareTo` function lacked input length validation, exposing the application to Denial of Service (DoS) attacks via excessively long version strings that consume CPU and memory during parsing (e.g., `trim`, `split`). +**Learning:** Seemingly harmless string operations can become vectors for resource exhaustion if input size is unchecked. +**Prevention:** Enforce strict maximum length limits (e.g., `MAX_VERSION_LENGTH`) on all user-controlled inputs before processing them. diff --git a/src/index.ts b/src/index.ts index 625ca808..8dc26027 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,11 @@ import type { NodeVersion } from "./types.js"; export type { NodeVersion }; +/** + * Maximum length for a version string to prevent ReDoS/DoS attacks. + */ +const MAX_VERSION_LENGTH = 256; + /** * End-of-Life dates for Node.js major versions. */ @@ -52,6 +57,10 @@ export const getVersion = (): NodeVersion => { * Compare the current node version with a target version string. */ const compareTo = (target: string): number => { + if (target.length > MAX_VERSION_LENGTH) { + return NaN; + } + if (target !== target.trim() || target.length === 0) { return NaN; } diff --git a/src/security.test.ts b/src/security.test.ts index b5942fd7..b9e97b45 100644 --- a/src/security.test.ts +++ b/src/security.test.ts @@ -63,4 +63,10 @@ describe("security fixes", () => { const v = getVersion(); expect(v.isAtLeast("10.0.0")).toBe(true); }); + + test("should reject excessively long version strings (DoS prevention)", () => { + const v = getVersion(); + const longVersion = `1.${"0.".repeat(200)}1`; + expect(v.isAtLeast(longVersion)).toBe(false); + }); });