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.
Two Go/TS parser parity gaps in the outbound share-link import path: parseVmess
only applied a ws link's path when the inner JSON also carried a host key, so a
generator that omits host dropped the path back to the default; and parseHysteria2
hardcoded verifyPeerCertByName to empty, ignoring the vcn param the panel emits,
so a hysteria2 outbound with a decoy SNI and a distinct cert name failed TLS
verification after import. The TS parser handles both; make the Go parser match.
The panel's share-link emitters (Go and TS) carry advanced xhttp knobs as a
snake_case x_padding_bytes plus an extra=<json> payload, but the Go parser's
xhttp branch read only top-level camelCase params, so importing an xhttp link
via the outbound-subscription feature dropped xPaddingBytes, scMaxEachPostBytes
and the rest, silently reverting them to the stream defaults and producing a
non-working outbound. Mirror the TS parser: read the snake_case alias, merge the
extra JSON blob, then let explicit camelCase params win.
Xray counts client traffic globally per email, so a client attached to
several of a node's inbounds has its single shared counter copied onto
every inbound by the node's enriched inbound list. When those copies
diverge (legacy per-inbound rows surviving a v3.2.x->v3.3.x upgrade, or
any drift) the per-inbound delta loop read the lower sibling as a
node-counter reset and re-added its full value, inflating the client far
past real usage (#5274).
Fold each email to its per-field node-wide max before the delta loop so
every occurrence is equal: the per-email baseline dedup then holds and
the reset clamp never misfires.