Files
gis/internal/transport/http/health.go
Bakhtiyar Issakhmetov 5b44cbda2f fix(security): close High/Critical findings from the Stage 8 scan
- Bump github.com/rabbitmq/amqp091-go 1.12.0 -> 1.13.0 (3 Critical, 7 High,
  reachable via rabbitmq.Connect) and golang.org/x/{text,net,crypto},
  klauspost/compress; toolchain go1.26.8, builder image pinned to
  golang:1.26.8-alpine3.24.
- Build Content-Disposition per RFC 6266/8187 (ASCII fallback +
  filename*=UTF-8''), stripping CR/LF, quotes and backslashes (CWE-113).
- Cap request bodies with MaxBytesReader (HTTP_MAX_UPLOAD_BYTES, default
  2 GiB); multipart uploads over the limit return 413 (CWE-400).
- /readyz reports "unavailable" instead of raw backend errors (CWE-209).
- Drop deprecated middleware.RealIP; add X-Content-Type-Options: nosniff.
- docker-compose credentials come from ${VAR:-default}.
- Check the RegisterValidation error; annotate verified gosec false
  positives with #nosec and a reason.

Re-scan: govulncheck 0 reachable, trivy fs/image 0 High/Critical,
gosec High 0. go test -race passes against PostGIS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:44:10 +05:00

59 lines
1.6 KiB
Go

package http
import (
"context"
"log/slog"
"net/http"
"gis/pkg/httputil"
)
// ReadinessCheck reports whether a dependency is reachable.
type ReadinessCheck func(ctx context.Context) error
// HealthHandler serves liveness and readiness probes.
type HealthHandler struct {
checks map[string]ReadinessCheck
log *slog.Logger
}
// NewHealthHandler builds a HealthHandler with the given named readiness checks.
// Failed checks are logged with their error; the response only names the
// failing component, so connection details never reach the client.
func NewHealthHandler(checks map[string]ReadinessCheck, log *slog.Logger) *HealthHandler {
if log == nil {
log = slog.Default()
}
return &HealthHandler{checks: checks, log: log}
}
// Live reports that the process is up.
func (h *HealthHandler) Live(w http.ResponseWriter, r *http.Request) {
httputil.WriteJSON(w, http.StatusOK, map[string]string{"status": "ok"})
}
// Ready runs all readiness checks and reports per-dependency status. It returns
// 503 if any check fails.
func (h *HealthHandler) Ready(w http.ResponseWriter, r *http.Request) {
results := make(map[string]string, len(h.checks))
ready := true
for name, check := range h.checks {
if err := check(r.Context()); err != nil {
h.log.Warn("readiness check failed", "component", name, "error", err)
results[name] = "unavailable"
ready = false
continue
}
results[name] = "ok"
}
status := http.StatusOK
if !ready {
status = http.StatusServiceUnavailable
}
httputil.WriteJSON(w, status, map[string]any{
"ready": ready,
"components": results,
})
}