Skip to content

[finding] os serve --port 3e3 silently binds port 3 — parseInt's tolerance means a port can be accepted as a value the operator never named #12674

Description

@os-litant

Filed unassigned and ungraded by the #12662 dev, session session_01UjujZN219uFzBhSYfMykCd. ⛔ Not graded, not routed. Deliberately NOT folded into #12662: that card repairs values which reach listen() and die; this is about values which reach listen() and succeed, on the wrong port. Fixing it narrows a published CLI's accepted input, which is a contract decision and not that card's to take.

Measured

os serve reads its port with parseInt(flags.port) — before #12662 and after it, deliberately. parseInt stops at the first character it cannot use and returns what it has:

input parseInt binds today
3e3 3 port 3
1e10 1 port 1
3000abc 3000 port 3000
0x0BB8 3000 port 3000 (hex, read as 3000)
3000.0 3000 port 3000
+3000 3000 port 3000
" 3000" 3000 port 3000 (leading whitespace)

Measured with node -e on this checkout, Node v22.22.2, against both parseInt and a real net.createServer().listen().

The first two rows are the interesting ones: an operator who writes PORT=3e3 meaning 3000 gets a server on port 3, and nothing anywhere says so. On a non-root host that surfaces as EACCES (still not naming the coercion); as root, measured here, it binds successfully and the operator is left with a server on a port they never named.

The rest are harmless in effect but are the same mechanism — an input accepted as something other than what it says.

Why #12662 left it

#12662's ruling was that no value which boots successfully today may be refused by the new validation — a falsifiable criterion, and the reason parseInt was kept as the reader instead of moving to Flags.integer (whose /^-?\d+$/ refuses every row above). serve-port-validation.test.ts pins that accept set as a table, so any tightening reds it deliberately and has to argue there.

Not established here

Re-check

node -e "console.log(parseInt('3e3'), parseInt('0x0BB8'), parseInt('3000abc'))"
git grep -n "parseRequestedPort" origin/main -- packages/cli/src/commands/serve.ts

Dedup

Searched the port family: #12662 is the only open card (this finding's parent, and it explicitly leaves this undecided); #12543, #12620, #12525, #12526, #12548, #12441, #11113, #11669 are closed and all about port conflict, contention or the arg separator. Nothing covers parseInt coercion of a valid-looking port.

Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions