Skip to content

SmtpTransport accepts a non-integer port its own message promises to reject, then fails at connect time under an internal name #13189

Description

@os-elon

Filed unassigned and ungraded by the #12993 dev seat while repairing the SMTP port drift surface. Not the same defect class as #12993 (that card is duplication; this is the accept set), so it was deliberately NOT repaired there — narrowing the accept set inside a refactor whose whole claim is behaviour preservation would have been a behaviour change wearing a cleanup's clothes.

Measured

SmtpTransport's port guard is a finite-and-in-range check with no integrality test, so a fractional port passes construction and is refused three layers later by the kernel:

SmtpTransport({ host: 'smtp.x', port: 587.5 }) -> ACCEPTED at construction
net.connect({ port: 587.5 }) -> ERR_SOCKET_BAD_PORT
"Port should be >= 0 and < 65536.
Received type number (587.5)."

Measured on origin/main@dc729f358 through the built dist/index.js, both lines above are real output.

Why it is worth a card

The refusal this transport does emit says (expected 1-65535). 587.5is in 1-65535 on any reading of that sentence, so the transport states a rule it does not enforce, and the value it waves through cannot ever connect. What the operator finally sees is ERR_SOCKET_BAD_PORT ... options.port ... at send time — an error naming an internal option, thrown from a code path with no connection to the Settings field they typed in.

That is the same defect shape #12673 was opened for and PR #12898 repaired for the CLI listen port, one layer down: a door that refuses late, under the name of the channel rather than the spelling the operator used.

Not established here

  • Whether the fix is Number.isInteger at the guard, or a shared reader that normalises. The CLI's port-contract.ts deliberately keeps a WIDE accept set (parseInt tolerance: 3e3, 0x0BB8, 3000.0 all boot), and its module header is explicit that a door may not narrow it — so "make SMTP strict" is a real decision with a precedent pointing the other way, not an obvious repair.
  • Severity not judged. Reaching it requires an operator to type a fractional port.

Re-check

node -e "const m=require('./packages/plugins/plugin-email/dist/index.js'); new m.SmtpTransport({host:'smtp.x',port:587.5}); console.log('accepted')"

⚠️ The current accept set is pinned deliberately in packages/plugins/plugin-email/src/transports/smtp-port-contract.test.ts (refactors the enforcement without narrowing what it accepts), where 587.5 sits in the table as MEASURED, not endorsed. A fix here must update that table in the same edit — the pin exists to make a behaviour change visible, not to prevent one.

Refs

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions