Repository navigation
Description
id(directory_mail): SmtpSink's reply reader has no total deadline and no line bound — a trickling or never-terminating relay reply hangs the directory request forever and buffers without limit
Evidence (verified this run, main d2cb60fe)
pkgs/id/src/directory_mail.rs:
:115RELAY_TIMEOUT = 10sis applied at:164-165as the socket read/write timeouts — per-read inactivity timeouts, not a deadline for a reply or for the exchange.:233-252expect(reader, code)loops onread_line(:238): every line whose first 3 chars parse to the expected code and whose 4th byte is-continues the loop. There is no cap on the number of continuation lines, no cap on one line's length, and no overall deadline.read_linebuffers a whole line beforeexpectcan inspect any of it.- The caller awaits the send:
pkgs/id/src/world_session/mod.rs:108runssink.send(&mail)inspawn_blockingand.awaits it before the directory action returns, so a hung reply hangs the directory request (and permanently occupies a blocking-pool thread per attempt).
This is the sibling of #1788 (CommandSink has no timeout at all). #1788's framing called SmtpSink "10s-bounded" — it is only inactivity-bounded, as the probe shows.
Probe (laptop, verbatim logic)
Probe crate ~/git/fleet/probe-2026-10-28-laneA-smtpexpect/ (laptop): expect() copied verbatim from directory_mail.rs at main d2cb60fe (file sha256 dc6f7935…, identical on laptop and sandbox), same 10s socket timeouts, fake relays on loopback:
- CONTROL: ordinary multiline reply (
250-fake/250 OK) →Ok(())in ~0.3 ms. - TRICKLE: relay sends a valid
250-…continuation line every 2 s forever → expect still blocked after 25 s; the 10s timeout never fires because every read completes in-window. - GIANT: relay sends
250-then floods bytes with no CRLF → expect still blocked after the relay sent up to 32 MiB;read_linekeeps buffering the unterminated line without limit.
Why it matters
The relay is loopback and operator-chosen, but it is still a separate process that can be buggy, wedged, or compromised — and the failure mode is the worst kind: not an error, but a directory request (e.g. an email-code sign-in) that never returns, plus unbounded memory growth in the server for the giant-line case. A stalled local SMTP daemon or a misbehaving spam-filter milter in front of it turns into a silent sign-in outage.
Acceptance criteria
- A reply (and/or the whole
send) has a total deadline: a relay that keeps every read in-window but never finishes a reply causessendto fail with a clear timeout error in bounded time. - One reply line's buffered length is capped (a named constant); a line past the cap fails the send instead of buffering without limit.
- The number of continuation lines per reply is capped, or the deadline demonstrably bounds them.
- Regression tests with a fake relay: trickling continuation lines time out; an over-long line is refused; the existing plain/multiline/dot-stuffing tests still pass.
- The fix states the chosen bounds where
RELAY_TIMEOUTis defined, so the "10s" a reader sees is the bound that actually applies.
Acceptance criteria
- SmtpSink's reply reader has a total deadline and a maximum line length; a trickling or never-terminating relay reply fails the directory request with a bounded error instead of hanging it.
- A regression test drives a slow/never-terminating reply and asserts the request completes (with an error) inside the bound; existing directory_mail tests stay green in CI.
Activity
developing-today commentedon Oct 28, 2026
CODE-826
dezren39 commentedon Oct 28, 2026
🤖 Agent note · filed
dezren39 commentedon Oct 28, 2026
Intake review
🤖 Agent note · filed
Notes: Filed in the large mcpx filing batch of 2026-10-28 21:59–22:24Z and already carries a queued triage task — it is not orphaned. Verified live at intake: no merged PR references this issue and it was filed under an hour ago, so nothing can have resolved it yet. Acceptance criteria appended to the body from the issue's own stated scope; original body preserved verbatim above it.
Risks: Tracker-level scope overlaps its sibling batch issues — the triage pass must dedupe against them before any implementation packet is written, and split rather than implement if the scope is multi-PR.
Suggested approach: Triage first (task 2794): confirm the boundary against the sibling issues, then enqueue one scoped packet per deliverable slice.
dezren39 commentedon Oct 28, 2026
Intake review
🤖 Agent note · filed
Notes: Filed in the large mcpx filing batch of 2026-10-28 21:59–22:24Z and already carries a queued triage task — it is not orphaned. Verified live at intake: no merged PR references this issue and it was filed under an hour ago, so nothing can have resolved it yet. Acceptance criteria appended to the body from the issue's own stated scope; original body preserved verbatim above it.
Risks: Tracker-level scope overlaps its sibling batch issues — the triage pass must dedupe against them before any implementation packet is written, and split rather than implement if the scope is multi-PR.
Suggested approach: Triage first (task 2794): confirm the boundary against the sibling issues, then enqueue one scoped packet per deliverable slice.
178 remaining items
dezren39 commentedon Nov 2, 2026
agent-note:v1 — holder: repo-worker-code (OpenChamber scheduled lane), at 2026-11-02T14:57:09Z. event: picked-up; task: 5054; queue: repo-health-code-pilot. Fresh metadata-only revalidation: #1899 OPEN; #4267 remains OPEN/non-draft and #3708 OPEN/draft, both authored and assigned to dezren39. #3708 remains the recorded target-file ownership conflict. No PR contents/checks/reviews, branch, worktree, or source inspected.
dezren39 commentedon Nov 2, 2026
agent-note:v1 — holder: repo-worker-code (OpenChamber scheduled lane), at 2026-11-02T14:57:13Z. event: parked; task: 5054; queue: repo-health-code-pilot. Parked on the Drew-owned implementation PR and overlapping file ownership. Preserve existing progress; resume only after disposition/handoff, then revalidate.
Merge pull request #4267 from developing-today/queue/5054-1899-smtp-d…
dezren39 commentedon Nov 2, 2026
🤖 Agent note · done