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:

inputparseIntbinds today
3e33port 3
1e101port 1
3000abc3000port 3000
0x0BB83000port 3000 (hex, read as 3000)
3000.03000port 3000
+30003000port 3000
" 3000"3000port 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

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions