fix: address all PR #10 review findings
All checks were successful
check / check (push) Successful in 2m19s

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.
This commit is contained in:
clawbot
2026-02-26 21:21:49 -08:00
parent 4b4a337a88
commit a57a73e94e
22 changed files with 2650 additions and 1903 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -40,40 +40,59 @@ type Handlers struct {
// New creates a new Handlers instance.
func New(
lc fx.Lifecycle,
lifecycle fx.Lifecycle,
params Params,
) (*Handlers, error) {
s := new(Handlers)
s.params = &params
s.log = params.Logger.Get()
s.hc = params.Healthcheck
s.broker = broker.New()
hdlr := &Handlers{
params: &params,
log: params.Logger.Get(),
hc: params.Healthcheck,
broker: broker.New(),
}
lc.Append(fx.Hook{
lifecycle.Append(fx.Hook{
OnStart: func(_ context.Context) error {
return nil
},
OnStop: func(_ context.Context) error {
return nil
},
})
return s, nil
return hdlr, nil
}
func (s *Handlers) respondJSON(
w http.ResponseWriter,
func (hdlr *Handlers) respondJSON(
writer http.ResponseWriter,
_ *http.Request,
data any,
status int,
) {
w.Header().Set(
writer.Header().Set(
"Content-Type",
"application/json; charset=utf-8",
)
w.WriteHeader(status)
writer.WriteHeader(status)
if data != nil {
err := json.NewEncoder(w).Encode(data)
err := json.NewEncoder(writer).Encode(data)
if err != nil {
s.log.Error("json encode error", "error", err)
hdlr.log.Error(
"json encode error", "error", err,
)
}
}
}
func (hdlr *Handlers) respondError(
writer http.ResponseWriter,
request *http.Request,
msg string,
status int,
) {
hdlr.respondJSON(
writer, request,
map[string]string{"error": msg},
status,
)
}

View File

@@ -7,9 +7,12 @@ import (
const httpStatusOK = 200
// HandleHealthCheck returns an HTTP handler for the health check endpoint.
func (s *Handlers) HandleHealthCheck() http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
resp := s.hc.Healthcheck()
s.respondJSON(w, req, resp, httpStatusOK)
func (hdlr *Handlers) HandleHealthCheck() http.HandlerFunc {
return func(
writer http.ResponseWriter,
request *http.Request,
) {
resp := hdlr.hc.Healthcheck()
hdlr.respondJSON(writer, request, resp, httpStatusOK)
}
}