fix(cluster-3): http hardening (M15/M16/M14/C2)

- pagination: clamp per_page to [1,100] and page to >=1 in the parser,
  guard the TotalPages division against per_page=0 (panic), and clamp the
  current page to [1,totalPages]; preserves cursor (next-token) mode
- middleware: add panic-recovery as the outermost middleware so handler
  panics return a 500 instead of crashing the process; re-panics
  http.ErrAbortHandler to keep file serving intact
- index: bound the index page query (Limit:200) so it no longer drains
  the entire courses table in 1000-row batches
This commit is contained in:
2026-06-28 04:31:21 +00:00
parent 40e5621eb9
commit 4f89f59232
3 changed files with 49 additions and 4 deletions

View File

@ -1,6 +1,7 @@
package main
import (
"fmt"
"log/slog"
"net/http"
"strings"
@ -54,6 +55,7 @@ func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server
router := mux.NewRouter()
router.Use(
middlewareRecovery(log),
middlewareCustomWriterInjector(),
mux.CORSMethodMiddleware(router),
middlewareLogger(log),
@ -101,6 +103,29 @@ func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server
}
}
func middlewareRecovery(log *slog.Logger) mux.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
rec := recover()
if rec == nil {
return
}
if rec == http.ErrAbortHandler {
panic(rec)
}
xcontext.LogWithError(
r.Context(), log, fmt.Errorf("%v", rec), "recovered from panic",
slog.String("method", r.Method),
slog.String("path", r.URL.Path),
)
http.Error(w, "internal server error", http.StatusInternalServerError)
}()
next.ServeHTTP(w, r)
})
}
}
func middlewareCustomWriterInjector() mux.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {