From 953cea2f0b70cb9878458b60f7f2cca21abac433 Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Mon, 13 Jul 2026 16:09:58 +0200 Subject: [PATCH] fix(cli): reject partially numeric line and hunk values --- .changeset/strict-cli-integers.md | 5 +++ src/core/cli.test.ts | 56 ++++++++++++++++++++++--------- src/core/cli.ts | 8 +++-- 3 files changed, 52 insertions(+), 17 deletions(-) create mode 100644 .changeset/strict-cli-integers.md diff --git a/.changeset/strict-cli-integers.md b/.changeset/strict-cli-integers.md new file mode 100644 index 00000000..44612d39 --- /dev/null +++ b/.changeset/strict-cli-integers.md @@ -0,0 +1,5 @@ +--- +"hunkdiff": patch +--- + +Reject malformed and unsafe line and hunk numbers instead of accepting their numeric prefix. diff --git a/src/core/cli.test.ts b/src/core/cli.test.ts index 45bdbb41..41f4d045 100644 --- a/src/core/cli.test.ts +++ b/src/core/cli.test.ts @@ -1044,6 +1044,21 @@ describe("parseCli command help text", () => { }); describe("parseCli argument validation", () => { + /** Parse one numeric session-navigation flag through the public CLI parser. */ + function parseTestNavigationTarget(flag: "--hunk" | "--old-line" | "--new-line", value: string) { + return parseCli([ + "bun", + "hunk", + "session", + "navigate", + "session-1", + "--file", + "README.md", + flag, + value, + ]); + } + test("rejects an invalid layout mode and rethrows the parser error", async () => { await expect(parseCli(["bun", "hunk", "diff", "--mode", "bogus"])).rejects.toThrow( "Invalid layout mode: bogus", @@ -1056,21 +1071,32 @@ describe("parseCli argument validation", () => { ); }); - test("rejects a non-positive integer navigation target", async () => { - await expect( - parseCli([ - "bun", - "hunk", - "session", - "navigate", - "session-1", - "--file", - "README.md", - "--hunk", - "0", - ]), - ).rejects.toThrow("Invalid positive integer: 0"); - }); + test.each(["1", "42", "9007199254740991"])( + "accepts positive integer navigation target %s", + async (value) => { + await expect(parseTestNavigationTarget("--hunk", value)).resolves.toMatchObject({ + hunkNumber: Number(value), + }); + }, + ); + + test.each(["0", "-1", "1.5", "1e3", "12abc", "9007199254740992"])( + "rejects malformed positive integer navigation target %s", + async (value) => { + await expect(parseTestNavigationTarget("--hunk", value)).rejects.toThrow( + `Invalid positive integer: ${value}`, + ); + }, + ); + + test.each(["--hunk", "--old-line", "--new-line"])( + "rejects a partially numeric value for %s", + async (flag) => { + await expect(parseTestNavigationTarget(flag, "12abc")).rejects.toThrow( + "Invalid positive integer: 12abc", + ); + }, + ); test("rejects ambiguous diff input that is neither a single target nor a file pair", async () => { await expect(parseCli(["bun", "hunk", "diff", "--staged", "left", "right"])).rejects.toThrow( diff --git a/src/core/cli.ts b/src/core/cli.ts index 043ed265..6a6ec14a 100644 --- a/src/core/cli.ts +++ b/src/core/cli.ts @@ -26,8 +26,12 @@ function parseLayoutMode(value: string): LayoutMode { /** Parse one required positive integer CLI value. */ function parsePositiveInt(value: string) { - const parsed = Number.parseInt(value, 10); - if (!Number.isInteger(parsed) || parsed <= 0) { + if (!/^[1-9]\d*$/.test(value)) { + throw new Error(`Invalid positive integer: ${value}`); + } + + const parsed = Number(value); + if (!Number.isSafeInteger(parsed)) { throw new Error(`Invalid positive integer: ${value}`); }