fix(worker): harden HTTP server (loopback bind, token auth, constant-time compare)

Bind 127.0.0.1 instead of all interfaces; wire the previously-unused TokenAuth middleware behind opt-in CLAUDE_MNEMONIC_AUTH_TOKEN (unset = unauthenticated, default-preserving); compare tokens with subtle.ConstantTimeCompare. Also drops the dead contextCache field/type (zero readers).
This commit is contained in:
2026-06-19 14:26:34 +01:00
parent 1b5697b316
commit 86ee0e28ed
2 changed files with 34 additions and 10 deletions
+15 -9
View File
@@ -156,7 +156,6 @@ type Service struct {
updater *update.Updater
rateLimiter *PerClientRateLimiter
expensiveOpLimiter *ExpensiveOperationLimiter
contextCache sync.Map
version string
recentQueriesBuf [maxRecentQueries]RecentSearchQuery
wg sync.WaitGroup
@@ -197,13 +196,6 @@ type staleVerifyRequest struct {
observationID int64
}
// contextCacheEntry caches clustering results for context injection.
type contextCacheEntry struct {
timestamp time.Time
observations []*models.Observation
obsCount int
}
// RecentSearchQuery tracks a search query for analytics.
type RecentSearchQuery struct {
Timestamp time.Time `json:"timestamp"`
@@ -1240,6 +1232,19 @@ func (s *Service) setupMiddleware() {
s.router.Use(PerClientRateLimitMiddleware(s.rateLimiter))
}
// Token authentication: opt-in and default-preserving.
// When CLAUDE_MNEMONIC_AUTH_TOKEN is set, every request (except exempt
// health/ready paths) must present that token via X-Auth-Token or
// "Authorization: Bearer". When the env var is unset (the default), no
// token is configured, auth stays disabled, and the server starts as
// before. The worker binds loopback only, so this is defense-in-depth for
// shared/multi-user hosts rather than a hard requirement.
if authToken := os.Getenv("CLAUDE_MNEMONIC_AUTH_TOKEN"); authToken != "" {
ta := NewTokenAuthWithToken(authToken)
s.router.Use(ta.Middleware)
log.Info().Msg("Token authentication enabled (CLAUDE_MNEMONIC_AUTH_TOKEN set)")
}
// Note: Timeout middleware is applied per-route, not globally,
// to avoid killing SSE connections which need to stay open indefinitely
}
@@ -1588,7 +1593,8 @@ func (s *Service) Start() error {
port := config.GetWorkerPort()
s.server = &http.Server{
Addr: fmt.Sprintf(":%d", port),
// Bind loopback only so the worker is never exposed on external interfaces.
Addr: fmt.Sprintf("127.0.0.1:%d", port),
Handler: s.router,
ReadHeaderTimeout: 10 * time.Second,
ReadTimeout: 30 * time.Second,