fix: add panic recovery, rate limiting, timing-safe CI token

- Add Recovery middleware (catches panics, returns 500, logs stack trace)
- Add RateLimiter to middleware chain (30 req/min, burst 60 per IP)
- Fix CI token comparison with subtle.ConstantTimeCompare (timing attack)
- Middleware chain: Recovery → Logging → RateLimit → CORS → mux

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 21:08:01 +03:00
parent 5fba2e78d5
commit d418ae2b54
3 changed files with 21 additions and 2 deletions

View File

@@ -7,6 +7,7 @@ import (
"context"
"crypto/sha1"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"encoding/json"
"fmt"
@@ -85,7 +86,7 @@ func (h *Handler) auth(next http.HandlerFunc) http.HandlerFunc {
func (h *Handler) ciToken(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("X-CI-Token")
if token == "" || token != h.cfg.CIsecret {
if token == "" || subtle.ConstantTimeCompare([]byte(token), []byte(h.cfg.CIsecret)) != 1 {
writeError(w, http.StatusForbidden, "Invalid CI token")
return
}

View File

@@ -4,6 +4,7 @@ package middleware
import (
"log"
"net/http"
"runtime/debug"
"strings"
"sync"
"time"
@@ -39,6 +40,20 @@ func Logging(next http.Handler) http.Handler {
})
}
// Recovery catches panics in downstream handlers and returns 500.
// Logs the stack trace for debugging.
func Recovery(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rec := recover(); rec != nil {
log.Printf("PANIC: %v\n%s", rec, debug.Stack())
http.Error(w, `{"error":"Internal Server Error"}`, http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
// statusWriter wraps http.ResponseWriter to capture the status code.
type statusWriter struct {
http.ResponseWriter