From 9bec866380e756e6eaa3b78c3117d9c8a564f830 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 18:41:51 +0000 Subject: [PATCH] feat: add input length limit to version comparison to prevent DoS Added MAX_VERSION_LENGTH constant (256) and enforced it in comparison logic. Updated security tests to verify the fix. --- .changeset/sentinel-dos-fix.md | 5 +++++ .jules/sentinel.md | 5 +++++ src/index.ts | 10 ++++++++++ src/security.test.ts | 22 ++++++++++++++++++++++ 4 files changed, 42 insertions(+) create mode 100644 .changeset/sentinel-dos-fix.md diff --git a/.changeset/sentinel-dos-fix.md b/.changeset/sentinel-dos-fix.md new file mode 100644 index 00000000..0cdf9e3d --- /dev/null +++ b/.changeset/sentinel-dos-fix.md @@ -0,0 +1,5 @@ +--- +"node-version": patch +--- + +Security: Add input length limit to version comparison to prevent DoS. diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 356c7f03..175d8c07 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-03 - [DoS in Version Parsing] +**Vulnerability:** The version comparison logic split strings and iterated over them without length limits. A malicious input with millions of characters could cause high CPU and memory usage (DoS). +**Learning:** String operations like `split` and iterations on user-controlled input must be bounded. Even simple logic can be a DoS vector if input size is unchecked. +**Prevention:** Enforce maximum length limits on string inputs before processing. Fail fast and securely (e.g. return NaN or throw) if limits are exceeded. diff --git a/src/index.ts b/src/index.ts index 625ca808..46be64f2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,6 +19,11 @@ export const EOL_DATES: Record = { "24": "2028-04-30", }; +/** + * Maximum length for a version string to prevent DoS. + */ +const MAX_VERSION_LENGTH = 256; + /** * Check if a major version is EOL. */ @@ -52,6 +57,11 @@ export const getVersion = (): NodeVersion => { * Compare the current node version with a target version string. */ const compareTo = (target: string): number => { + // Security: Prevent DoS by limiting input length + 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..f9722cb8 100644 --- a/src/security.test.ts +++ b/src/security.test.ts @@ -63,4 +63,26 @@ describe("security fixes", () => { const v = getVersion(); expect(v.isAtLeast("10.0.0")).toBe(true); }); + + test("should reject extremely long version strings (DoS prevention)", () => { + const v = getVersion(); + const hugeVersion = `${"1.".repeat(200)}1`; // > 256 chars + expect(hugeVersion.length).toBeGreaterThan(256); + expect(v.isAtLeast(hugeVersion)).toBe(false); + expect(v.isAbove(hugeVersion)).toBe(false); + expect(v.isBelow(hugeVersion)).toBe(false); + expect(v.isAtMost(hugeVersion)).toBe(false); + }); + + test("should accept valid long version strings within limit", () => { + const v = getVersion(); + // 50 segments of "1" is 100 chars approx + const longVersion = Array(50).fill("1").join("."); + expect(longVersion.length).toBeLessThan(256); + + // Current mock is 20.0.0 (see top of file) + // 1.1.1... < 20.0.0 + expect(v.isAtLeast(longVersion)).toBe(true); + expect(v.isBelow(longVersion)).toBe(false); + }); });