diff --git a/src/cli/command-options.test.ts b/src/cli/command-options.test.ts index d685cfd0dbd..89d40ab7164 100644 --- a/src/cli/command-options.test.ts +++ b/src/cli/command-options.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest"; import { inheritOptionFromParent } from "./command-options.js"; describe("inheritOptionFromParent", () => { - it("inherits only from direct parent by default", async () => { + it("inherits from grandparent when parent does not define the option", async () => { const program = new Command().option("--token ", "Root token"); const gateway = program.command("gateway"); let inherited: string | undefined; @@ -15,22 +15,6 @@ describe("inheritOptionFromParent", () => { inherited = inheritOptionFromParent(command, "token"); }); - await program.parseAsync(["--token", "root-token", "gateway", "run"], { from: "user" }); - expect(inherited).toBeUndefined(); - }); - - it("walks ancestor chain when explicitly configured", async () => { - const program = new Command().option("--token ", "Root token"); - const gateway = program.command("gateway"); - let inherited: string | undefined; - - gateway - .command("run") - .option("--token ", "Run token") - .action((_opts, command) => { - inherited = inheritOptionFromParent(command, "token", { maxDepth: 3 }); - }); - await program.parseAsync(["--token", "root-token", "gateway", "run"], { from: "user" }); expect(inherited).toBe("root-token"); }); @@ -44,7 +28,7 @@ describe("inheritOptionFromParent", () => { .command("run") .option("--token ", "Run token") .action((_opts, command) => { - inherited = inheritOptionFromParent(command, "token", { maxDepth: 3 }); + inherited = inheritOptionFromParent(command, "token"); }); await program.parseAsync( @@ -65,4 +49,23 @@ describe("inheritOptionFromParent", () => { expect(inheritOptionFromParent(run, "token")).toBeUndefined(); }); + + it("does not inherit from ancestors beyond the bounded traversal depth", async () => { + const program = new Command().option("--token ", "Root token"); + const level1 = program.command("level1"); + const level2 = level1.command("level2"); + let inherited: string | undefined; + + level2 + .command("run") + .option("--token ", "Run token") + .action((_opts, command) => { + inherited = inheritOptionFromParent(command, "token"); + }); + + await program.parseAsync(["--token", "root-token", "level1", "level2", "run"], { + from: "user", + }); + expect(inherited).toBeUndefined(); + }); }); diff --git a/src/cli/command-options.ts b/src/cli/command-options.ts index 07af2e6c387..0f1830c95c1 100644 --- a/src/cli/command-options.ts +++ b/src/cli/command-options.ts @@ -14,15 +14,12 @@ function getOptionSource(command: Command, name: string): string | undefined { return command.getOptionValueSource(name); } -type InheritOptionConfig = { - // Defensive default: only direct-parent inheritance unless callers opt into deeper traversal. - maxDepth?: number; -}; +// Defensive guardrail: allow expected parent/grandparent inheritance without unbounded deep traversal. +const MAX_INHERIT_DEPTH = 2; export function inheritOptionFromParent( command: Command | undefined, name: string, - config?: InheritOptionConfig, ): T | undefined { if (!command) { return undefined; @@ -33,15 +30,9 @@ export function inheritOptionFromParent( return undefined; } - const rawMaxDepth = config?.maxDepth; - const maxDepth = - typeof rawMaxDepth === "number" && Number.isFinite(rawMaxDepth) - ? Math.max(1, Math.floor(rawMaxDepth)) - : 1; - let depth = 0; let ancestor = command.parent; - while (ancestor && depth < maxDepth) { + while (ancestor && depth < MAX_INHERIT_DEPTH) { const source = getOptionSource(ancestor, name); if (source && source !== "default") { return ancestor.opts>()[name] as T | undefined;