Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sentinel-dos-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"node-version": patch
---

Security: Add input length limit to version comparison to prevent DoS.
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
10 changes: 10 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export const EOL_DATES: Record<string, string> = {
"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.
*/
Expand Down Expand Up @@ -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;
}
Expand Down
22 changes: 22 additions & 0 deletions src/security.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});