[GH-ISSUE #1012] vinext dev/start CLI arg parser silently accepts missing values for --port / --hostname (NaN, undefined, swallows next flag) #223

Closed
opened 2026-05-06 12:38:20 +02:00 by BreizhHardware · 0 comments

Originally created by @eashish93 on GitHub (May 2, 2026).
Original GitHub issue: https://github.com/cloudflare/vinext/issues/1012

vinext dev and vinext start accept --port / --hostname (and their short aliases -p / -H) without validating that the next argv slot actually contains a value. The parser blindly consumes args[++i], so:

  1. vinext dev --port (no value) → port = NaN → server tries to listen(NaN) and either crashes or binds an OS-assigned random port silently.
  2. vinext dev --hostname (no value) → hostname = undefined.
  3. vinext dev --port --hostname 0.0.0.0 (typo: missing port value) → --hostname is consumed as the port value, parses to NaN, AND the hostname flag is lost. User loses BOTH settings with no warning.

Reproduce

The bug lives in the CLI parser at dist/cli.js:71-73. Same pattern in source (src/cli.ts):

} else if (arg === "--port" || arg === "-p") result.port = parseInt(args[++i], 10);
  else if (arg.startsWith("--port=")) result.port = parseInt(arg.split("=")[1], 10);
  else if (arg === "--hostname" || arg === "-H") result.hostname = args[++i];
  else if (arg.startsWith("--hostname=")) result.hostname = arg.split("=")[1];

Standalone repro of the parser logic (no need to install vinext):

function parseArgs(args) {
  const result = {};
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg === "--port" || arg === "-p") result.port = parseInt(args[++i], 10);
    else if (arg === "--hostname" || arg === "-H") result.hostname = args[++i];
  }
  return result;
}

console.log(parseArgs(["--port"]));
// → { port: NaN }

console.log(parseArgs(["--hostname"]));
// → { hostname: undefined }

console.log(parseArgs(["--port", "--hostname", "0.0.0.0"]));
// → { port: NaN }   ← --hostname value is silently swallowed too

End-to-end repro:

$ npx vinext@0.0.45 dev --port
# server starts; subsequent fetch on the expected port fails / hits a random port

Why it's a bug

Three concrete failure modes:

  1. Listen on NaN — Node's server.listen(NaN) falls back to a random ephemeral port. The user typed --port expecting a deterministic port; they get one assigned by the OS. The dev URL printed is correct but the user's bookmarks / proxy configs / wrangler bindings break.
  2. hostname = undefined — Vite's server.host falls back to its default (localhost), which means the dev server is unreachable from anything but the loopback interface. Common bug shape inside containers / WSL — user explicitly passes --hostname 0.0.0.0 to fix it, mistypes (case 3 above), and gets the broken default back without a warning.
  3. Argument-eating typovinext dev --port --hostname 0.0.0.0 parses as { port: NaN } (no hostname). Everything downstream of port-resolution fails, but the error message says "could not bind port NaN" rather than "you forgot the port value."

Proposed fix

Validate that args[i + 1] exists and isn't another flag before consuming it. Suggested shape:

function takeValue(name, args, i) {
  const next = args[i + 1];
  if (next === undefined || next.startsWith("-")) {
    throw new Error(`${name} requires a value (got: ${next ?? "<end of args>"})`);
  }
  return next;
}

// ...
else if (arg === "--port" || arg === "-p") {
  const v = takeValue(arg, args, i);
  i++;
  result.port = parseInt(v, 10);
  if (Number.isNaN(result.port)) throw new Error(`${arg} expects an integer (got: ${v})`);
}
else if (arg === "--hostname" || arg === "-H") {
  result.hostname = takeValue(arg, args, i);
  i++;
}

Same shape for any other future flag taking a value.

Environment

vinext: 0.0.45
node: 24
bun: 1.3.13
host: macOS arm64 (also reproduces on linux)

The bug is in the parser itself — environment-independent.

Originally created by @eashish93 on GitHub (May 2, 2026). Original GitHub issue: https://github.com/cloudflare/vinext/issues/1012 `vinext dev` and `vinext start` accept `--port` / `--hostname` (and their short aliases `-p` / `-H`) without validating that the next argv slot actually contains a value. The parser blindly consumes `args[++i]`, so: 1. `vinext dev --port` (no value) → `port = NaN` → server tries to `listen(NaN)` and either crashes or binds an OS-assigned random port silently. 2. `vinext dev --hostname` (no value) → `hostname = undefined`. 3. `vinext dev --port --hostname 0.0.0.0` (typo: missing port value) → `--hostname` is consumed as the port value, parses to `NaN`, AND the hostname flag is lost. User loses BOTH settings with no warning. ## Reproduce The bug lives in the CLI parser at `dist/cli.js:71-73`. Same pattern in source (`src/cli.ts`): ```js } else if (arg === "--port" || arg === "-p") result.port = parseInt(args[++i], 10); else if (arg.startsWith("--port=")) result.port = parseInt(arg.split("=")[1], 10); else if (arg === "--hostname" || arg === "-H") result.hostname = args[++i]; else if (arg.startsWith("--hostname=")) result.hostname = arg.split("=")[1]; ``` Standalone repro of the parser logic (no need to install vinext): ```js function parseArgs(args) { const result = {}; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg === "--port" || arg === "-p") result.port = parseInt(args[++i], 10); else if (arg === "--hostname" || arg === "-H") result.hostname = args[++i]; } return result; } console.log(parseArgs(["--port"])); // → { port: NaN } console.log(parseArgs(["--hostname"])); // → { hostname: undefined } console.log(parseArgs(["--port", "--hostname", "0.0.0.0"])); // → { port: NaN } ← --hostname value is silently swallowed too ``` End-to-end repro: ```bash $ npx vinext@0.0.45 dev --port # server starts; subsequent fetch on the expected port fails / hits a random port ``` ## Why it's a bug Three concrete failure modes: 1. **Listen on `NaN`** — Node's `server.listen(NaN)` falls back to a random ephemeral port. The user typed `--port` expecting a deterministic port; they get one assigned by the OS. The dev URL printed is correct but the user's bookmarks / proxy configs / wrangler bindings break. 2. **`hostname = undefined`** — Vite's `server.host` falls back to its default (`localhost`), which means the dev server is unreachable from anything but the loopback interface. Common bug shape inside containers / WSL — user explicitly passes `--hostname 0.0.0.0` to fix it, mistypes (case 3 above), and gets the broken default back without a warning. 3. **Argument-eating typo** — `vinext dev --port --hostname 0.0.0.0` parses as `{ port: NaN }` (no hostname). Everything downstream of port-resolution fails, but the error message says "could not bind port NaN" rather than "you forgot the port value." ## Proposed fix Validate that `args[i + 1]` exists and isn't another flag before consuming it. Suggested shape: ```js function takeValue(name, args, i) { const next = args[i + 1]; if (next === undefined || next.startsWith("-")) { throw new Error(`${name} requires a value (got: ${next ?? "<end of args>"})`); } return next; } // ... else if (arg === "--port" || arg === "-p") { const v = takeValue(arg, args, i); i++; result.port = parseInt(v, 10); if (Number.isNaN(result.port)) throw new Error(`${arg} expects an integer (got: ${v})`); } else if (arg === "--hostname" || arg === "-H") { result.hostname = takeValue(arg, args, i); i++; } ``` Same shape for any other future flag taking a value. ## Environment ``` vinext: 0.0.45 node: 24 bun: 1.3.13 host: macOS arm64 (also reproduces on linux) ``` The bug is in the parser itself — environment-independent.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#223
No description provided.