Second-pass review of the 54-commit self-correcting audit. Each item below
was confirmed by reading the surrounding source (and, where practical, the
pre-fix code) before being changed; regression tests are included for every
behavioral fix.
Concurrency:
- eventbus: Bus.Subscribe called wg.Add with no synchronization against a
concurrent Bus.Stop's wg.Wait, a real "WaitGroup misuse" panic risk (e.g. a
Telegram-bot settings save racing panel shutdown/restart). Stop now flips a
mu-guarded `stopped` flag before waiting, and Subscribe checks it under the
same lock, so Add and Wait can no longer race.
Security:
- login_limiter: evictForRoom's fallback eviction picked an arbitrary map
key, including ones still under an active cooldown - an attacker flooding
/login with fresh usernames could evict their own (or anyone's) blocked
record and reset the lockout. The fallback now skips actively-blocked
records, only falling back to an unconditional evict if the map is
somehow entirely full of active blocks (preserves the hard memory cap).
Subscription-endpoint panics (reachable by any client hitting /sub):
- internal/sub/service.go: applyPathAndHostParams/Obj (ws/httpupgrade/xhttp
with no path settings object) and the TLS alpn readers in three places
used unchecked type assertions - exactly the bug class abab7cd0 patched
elsewhere in the same switch statements, just not these call sites.
- internal/sub/json_service.go, clash_service.go: the externalProxy loops
in the JSON and Clash generators used unchecked assertions on a
legacy/admin-supplied field (missing "port", non-object entry, etc.).
- internal/sub/json_service.go: realityData's shortId/serverName selection
could assert a non-string array element.
Other correctness:
- client_traffic.go: ResetAllTraffics (touched by 3eb214d0) still skipped
clearing NodeClientTraffic node-sync baselines, unlike its sibling reset
paths in the same file - a node's next sync would re-add pre-reset delta
on top of the freshly-zeroed counter.
- inbound_traffic.go: the traffic-tick tx's Commit/Rollback errors were
silently discarded; now logged so a backend-level commit failure (e.g. an
aborted Postgres tx from a best-effort helper) doesn't masquerade as a
successful tick.
- outbound_subscription.go: the new subscriptionFetchClient doc comment was
wedged between fetchAndStore's existing comment and fetchAndStore itself,
leaving fetchAndStore undocumented and the comment describing the wrong
function.
Convention cleanup:
- Removed narrative // comments added by the audit that violate this repo's
no-inline-comment rule (mostly narrating the specific bug/fix rather than
a lasting contract, and mostly on new Test functions, which this repo's
existing tests never comment) - calibrated against this exact codebase's
own pre-existing comment style so legitimate godoc-style doc comments
were left alone.
The web/sub same-port check compared the two listen addresses as raw strings, so
binding both on all interfaces with different spellings (webListen 0.0.0.0 vs an
empty subListen) slipped past validation and only failed at startup with an
opaque bind error. Treat any wildcard listen ('', 0.0.0.0, ::) as overlapping so
the clash is reported up front, while still allowing two distinct specific
addresses to share a port.
The notification/test email carried only From/To/Subject/MIME headers, and
the From header was the raw SMTP username. Two problems:
- When the SMTP login is not a bare email address (common with relays and
submission services), the From header has no valid address and strict
receivers reject the message — e.g. Gmail returns "550-5.7.1 ... Messages
missing a valid address in From: header".
- There was no Date (mandatory per RFC 5322 section 3.6) and no Message-ID,
which also raises spam score.
Add smtpFrom (sender address) and smtpFromName (display name) settings and
assemble the message with net/mail: a name-addr From ("Name" <addr>), a
Date, a Message-ID, and an RFC 2047 encoded Subject, in a deterministic
header order. From falls back to the username when smtpFrom is empty, so
existing setups keep working. Wire the settings through the model, the SMTP
send and test paths, the Email settings UI, and all 13 locale files;
regenerate the Zod/OpenAPI artifacts.
Validate smtpFrom in AllSetting.CheckValid (reject anything net/mail cannot
parse), which surfaces a bad address at configuration time and prevents CRLF
header injection; strip CR/LF in buildMessage as defense in depth. Add
buildMessage and CheckValid tests.