watchdog: add log sanitization and request tracking

Stuff to prevent possible log injection attacks via weird characters,
now sanitized with `strconv` stuff.

- X-Request-ID is now traced in ingestion handler
- ValidateWithMap renamed to Validate (xd)
- Some new tests :D

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I286ec399a5c4a407f0cc117472c079446a6a6964
This commit is contained in:
raf 2026-03-07 12:49:45 +03:00
commit d1181d38f0
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
3 changed files with 163 additions and 9 deletions

View file

@ -1,7 +1,10 @@
package api
import (
"math/rand"
"crypto/rand"
"encoding/hex"
"fmt"
mrand "math/rand"
"net"
"net/http"
"strings"
@ -23,7 +26,7 @@ type IngestionHandler struct {
refRegistry *normalize.ReferrerRegistry
metricsAgg *aggregate.MetricsAggregator
rateLimiter *ratelimit.TokenBucket
rng *rand.Rand
rng *mrand.Rand
trustedNetworks []*net.IPNet // pre-parsed CIDR networks
}
@ -82,12 +85,19 @@ func NewIngestionHandler(
refRegistry: refRegistry,
metricsAgg: metricsAgg,
rateLimiter: limiter,
rng: rand.New(rand.NewSource(time.Now().UnixNano())),
rng: mrand.New(mrand.NewSource(time.Now().UnixNano())),
trustedNetworks: trustedNetworks,
}
}
func (h *IngestionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Generate or extract request ID for tracing
requestID := r.Header.Get("X-Request-ID")
if requestID == "" {
requestID = generateRequestID()
}
w.Header().Set("X-Request-ID", requestID)
// Handle CORS preflight
if r.Method == http.MethodOptions {
h.handleCORS(w, r)
@ -131,8 +141,8 @@ func (h *IngestionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
// Validate event via map lookup (also O(1))
if err := event.ValidateWithMap(h.domainMap); err != nil {
// Validate event via map lookup
if err := event.Validate(h.domainMap); err != nil {
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
@ -322,3 +332,13 @@ func (h *IngestionHandler) classifyDevice(width int, userAgent string) string {
return "unknown"
}
// generateRequestID creates a unique request ID for tracing
func generateRequestID() string {
b := make([]byte, 8)
if _, err := rand.Read(b); err != nil {
// Fallback to timestamp if crypto/rand fails
return fmt.Sprintf("%d", time.Now().UnixNano())
}
return hex.EncodeToString(b)
}