Addresses the 2026-08-10 FAIL review (findings 1-7, 9, 10).
KILL never terminated the victim's connection on either transport: both
paths called BroadcastQuit, which deletes the session row and tells the
victim's peers it quit, but leaves the victim holding a socket that looks
alive and silently delivers nothing while its nick is freed for reuse.
Service now owns a session-ID keyed registry of live wire connections that
ircserver populates at registration, and both KILL paths go through the new
Service.KillSession, which broadcasts the QUIT and then sends the victim a
KILL and ERROR :Closing Link before closing its socket. The victim's relay
goroutine is cancelled and its cleanup no longer re-broadcasts a QUIT for
an already-deleted session. TestIntegrationKill now asserts the victim
reads to EOF and is gone from NAMES and WHO, not just that an observer saw
the QUIT relay.
HTTP MODE <othernick> with no body answered with the requester's own modes,
because the target check sat inside the mode-change branch. The check is
hoisted above the query/change split, and both transports now compare nicks
with EqualFold since IRC nicks are case-insensitive.
Service.QueryUserMode returned "+" for a database failure, making an
unreadable mode indistinguishable from an unset one; it now returns an
error, and both callers surface it. db.GetUserhostInfo likewise treated
every scan error as "nick not found"; only sql.ErrNoRows is skipped now.
The four new unsynchronized c.nick reads this branch introduced are read
through currentNick() under c.mu, and c.closed is now guarded everywhere
because KILL writes it from another client's goroutine. Conn.send takes a
write mutex, as a connection is now written to by three goroutines.
server.Server.Run was left with no in-tree callers when its body was
inlined into the fx OnStart hook; it is deleted rather than left to drift.
INFO and VERSION had two implementations that had already diverged: the
version string is now Service.ServerVersion and the INFO body is
Service.InfoLines, used verbatim by both transports. The ctx parameters on
handleVersion/handleAdmin/handleInfo/handleTime existed only to be
discarded and are gone.
- server.go: drop unused (*Server).serve int return (unparam) and
remove the dead exitCode field so cleanShutdown no longer writes
to a field nothing reads.
- service.go: rename range var ch -> modeChar in parseUserModeString
and the isKnownUserModeChar parameter (varnamelen).
- service_test.go: rename tc -> testCase (varnamelen); lift the
inline struct and caseState to package-level named types
(applyUserModeCase, applyUserModeCaseState) with every field
set explicitly (exhaustruct); split the 167-line case table into
four categorised helpers (funlen); extract the per-case runner
and outcome/state verifiers into helpers so TestApplyUserMode
drops below gocognit 30 and flattens the wantErr nestif block.
No changes to .golangci.yml, Makefile, Dockerfile, or CI config.
No //nolint was used to silence any of these findings.
docker build --no-cache . passes clean: 0 lint issues, all tests
pass with -race, binary compiles.
Mode parser (internal/service/service.go):
- Reject strings without leading + or - (e.g. "xw", "w", "") with
ERR_UMODEUNKNOWNFLAG instead of silently treating them as "-".
- Support multi-sign transitions: +w-o, -w+o, +o-w+w, -x+y, +y-x. The
active sign flips each time + or - is seen; subsequent letters apply
with the active sign.
- Atomic from caller's perspective: parse the whole string to a list of
ops first, reject the whole request on any unknown mode char, and only
then apply ops to the DB. Partial application of +w before rejecting
+o is gone.
- HTTP and IRC still share the same ApplyUserMode entry point.
Router race (internal/server/server.go):
- The fx OnStart hook previously spawned serve() in a goroutine that
called SetupRoutes asynchronously, while ServeHTTP delegated to
srv.router. Test harnesses (httptest wrapping srv as Handler) raced
against SetupRoutes writing srv.router vs ServeHTTP reading it,
producing the race detector failures in CI on main.
- SetupRoutes is now called synchronously inside OnStart before the
serve goroutine starts, so srv.router is fully initialized before any
request can reach ServeHTTP.
Tests (internal/service/service_test.go):
- Replaced the per-mode tests with a single table-driven TestApplyUserMode
that asserts both the returned mode string and the persisted DB state
(oper/wallops) for each case, including the malformed and multi-sign
cases above. The +wz case seeds wallops=true to prove the whole string
is rejected and +w is not partially applied.
Changes the Go module path from `git.eeqj.de/sneak/neoirc` to `sneak.berlin/go/neoirc`.
All occurrences updated:
- `go.mod` module directive
- All Go import paths across 35 `.go` files (107 import statements)
- All JSON schema `$id` URIs across 30 `.json` files in `schema/`
No functional changes — this is a pure rename of the module path.
`docker build .` passes clean (formatting, linting, all tests, binary build).
closes#98
Co-authored-by: clawbot <clawbot@users.noreply.git.eeqj.de>
Reviewed-on: #99
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
Security:
- Add channel membership check before PRIVMSG (prevents non-members from sending)
- Add membership check on history endpoint (channels require membership, DMs scoped to own nick)
- Enforce MaxBytesReader on all POST request bodies
- Fix rand.Read error being silently ignored in token generation
Data integrity:
- Fix TOCTOU race in GetOrCreateChannel using INSERT OR IGNORE + SELECT
Build:
- Add CGO_ENABLED=0 to golangci-lint install in Dockerfile (fixes alpine build)
Linting:
- Strict .golangci.yml: only wsl disabled (deprecated in v2)
- Re-enable exhaustruct, depguard, godot, wrapcheck, varnamelen
- Fix linters-settings -> linters.settings for v2 config format
- Fix ALL lint findings in actual code (no linter config weakening)
- Wrap all external package errors (wrapcheck)
- Fill struct fields or add targeted nolint:exhaustruct where appropriate
- Rename short variables (ts->timestamp, n->bufIndex, etc.)
- Add depguard deny policy for io/ioutil and math/rand
- Exclude G704 (SSRF) in gosec config (CLI client takes user-configured URLs)
Tests:
- Add security tests (TestNonMemberCannotSend, TestHistoryNonMember)
- Split TestInsertAndPollMessages for reduced complexity
- Fix parallel test safety (viper global state prevents parallelism)
- Use t.Context() instead of context.Background() in tests
Docker build verified passing locally.
- Fix stuttering type names (e.g. config.ConfigParams → config.Params)
- Add doc comments to all exported types/functions/methods
- Add package doc comments to all packages
- Fix JSON tags to camelCase
- Extract magic numbers to constants
- Add blank lines per nlreturn/wsl_v5 rules
- Use errors.Is() for error comparison
- Unexport NewLoggingResponseWriter (not used externally)
- Replace for-range on ctx.Done() with channel receive
- Rename unused parameters to _
- AGENTS.md: all changes via feature branches, no direct main commits