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.
ToggleClientEnableByEmail (Telegram bot) and SetClientEnableByEmail (LDAP sync)
resolved a single inbound via the legacy client_traffics pointer and flipped
enable only there. A client attached to several inbounds kept connecting through
the siblings' running Xray after being disabled, and the next edit could
re-enable it everywhere from a stale sibling. Route both through the
applyClientFieldByEmail fan-out (the #5039 fix path) so the whole multi-inbound
identity is toggled at once, dropping the circular Set/Toggle dependency.
Add .golangci.yml (v2): the standard linters plus bodyclose, errorlint, noctx, misspell, rowserrcheck, sqlclosecheck, unconvert, usestdlibvars, with gofumpt + goimports formatters. Enable the std-error-handling exclusion preset for idiomatic Close/Remove/Setenv ignores; scope-exclude SA1019 (parser.ParseDir in tools/openapigen) and ST1005 (intentional capitalized user-facing error copy that tests assert verbatim). No inline nolint directives were introduced.
Resolve all 217 findings behavior-preserving: gofumpt/goimports formatting, explicit blank assignment on intentionally ignored errors, errors.Is/errors.As and %w wrapping, context-aware stdlib calls (CommandContext/QueryContext/NewRequestWithContext/Dialer), staticcheck simplifications, removed redundant conversions, http.StatusOK and http.MethodGet, inlined the go:fix intPtr helper, and deferred sql rows Close. Add a golangci CI job mirroring the existing Go jobs.
applyClientFieldByEmail patched only the first inbound that the
client_traffics row pointed at. For a multi-inbound client the sibling
inbounds kept the old expiryTime/totalGB/limitIp in their settings JSON,
and the next SyncInbound over a stale sibling reverted the edit in the
normalized records — the Telegram bot's expiry change appeared to apply
and then sprang back. Patch the field on every inbound linked to the
email, falling back to the legacy single-inbound lookup for clients that
were never normalized.