Insufficient Logging in Chi
How Insufficient Logging Manifests in Chi
Insufficient logging in Chi applications creates blind spots that attackers exploit to maintain persistence and evade detection. When Chi APIs fail to log authentication failures, authorization errors, or suspicious input patterns, security teams lose the ability to reconstruct attack timelines or identify compromised accounts.
A common manifestation occurs in Chi's middleware chains where authentication failures go unlogged. Consider a Chi router handling JWT validation:
router := chi.NewRouter()
router.Use(middleware.JWT("secret"))
router.Get("/api/users", func(w http.ResponseWriter, r *http.Request) {
// Missing: logging failed JWT validation attempts
users := getUsers()
json.NewEncoder(w).Encode(users)
})
The default JWT middleware in Chi doesn't log validation failures by default, creating a perfect attack vector for credential stuffing attacks. Attackers can brute-force tokens without triggering any security alerts.
Another critical area is request body logging in Chi applications. When handling sensitive operations like password changes or financial transactions, insufficient logging of request payloads (while properly masking sensitive data) prevents forensic analysis:
router.Post("/api/users/{id}/password", func(w http.ResponseWriter, r *http.Request) {
vars := chi.URLParam(r, "id")
var payload struct{ Current, New string }
json.NewDecoder(r.Body).Decode("payload")
// Missing: logging the password change attempt with masked values
if err := changePassword(vars, payload.Current, payload.New); err != nil {
http.Error(w, err.Error(), 400)
return
}
w.WriteHeader(200)
})
Chi's minimalist design philosophy, while excellent for performance, places the burden of comprehensive logging on developers. Without proper logging middleware, critical security events like rate limit violations, suspicious IP patterns, or unusual request volumes go unnoticed.
Chi-Specific Detection
Detecting insufficient logging in Chi applications requires examining both the application code and runtime behavior. The first step is auditing Chi's middleware stack to ensure security-focused logging middleware is properly configured.
middleBrick's scanner specifically identifies Chi applications with insufficient logging by examining:
- Missing audit trails for authentication/authorization failures
- Absence of structured logging for security events
- Unlogged rate limit violations and brute force attempts
- Missing correlation IDs for request tracing
- Lack of logging for sensitive operations (password changes, financial transactions)
Here's how middleBrick detects logging gaps in a Chi application:
package main
import (
"github.com/go-chi/chi/v5/middleware"
"github.com/middlebrick/middlebrick-go"
)
func main() {
router := chi.NewRouter()
// middleBrick scan target
mb := middlebrick.New("https://api.example.com")
// Test for logging gaps
results := mb.Scan()
// Check for missing security logs
if results.Logs.AuthenticationFailures == 0 {
fmt.Println("CRITICAL: No authentication failure logs detected")
}
// Check for missing audit trails
if results.Logs.SensitiveOperations == 0 {
fmt.Println("HIGH: No sensitive operation logging detected")
}
}
Beyond automated scanning, developers should manually audit their Chi applications for logging completeness. Use chi.URLParam and chi.RouteContext to ensure all request metadata is captured:
router.Use(middleware.Logger)
router.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := chi.RouteContext(r.Context())
routePath := ctx.RoutePattern()
method := r.Method
// Log security-relevant context
log.WithFields(logrus.Fields{
"route": routePath,
"method": method,
"client_ip": realip.FromRequest(r),
"user_agent": r.UserAgent(),
}).Info("request_received")
next.ServeHTTP(w, r)
})
})
Chi-Specific Remediation
Remediating insufficient logging in Chi applications requires implementing comprehensive logging middleware and ensuring all security-relevant events are captured. The solution leverages Chi's middleware architecture to create a security-focused logging layer.
First, implement structured logging middleware that captures all security-relevant events:
package logging
import (
"github.com/go-chi/chi/v5/middleware"
"github.com/sirupsen/logrus"
"net/http"
"time"
)
type SecurityLogger struct{}
func (sl *SecurityLogger) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Create request context with security metadata
ctx := chi.RouteContext(r.Context())
routePattern := ctx.RoutePattern()
// Wrap response writer to capture status code
rw := &responseLogger{ResponseWriter: w, statusCode: http.StatusOK}
// Log request start
logrus.WithFields(logrus.Fields{
"method": r.Method,
"route": routePattern,
"client_ip": realip.FromRequest(r),
"user_agent": r.UserAgent(),
"request_id": middleware.GetReqID(ctx),
}).Info("request_start")
next.ServeHTTP(rw, r)
// Log request completion with security context
duration := time.Since(start)
logrus.WithFields(logrus.Fields{
"method": r.Method,
"route": routePattern,
"status": rw.statusCode,
"duration": duration,
"client_ip": realip.FromRequest(r),
"user_agent": r.UserAgent(),
"request_id": middleware.GetReqID(ctx),
}).Info("request_complete")
// Log security events based on status code
if rw.statusCode == http.StatusUnauthorized ||
rw.statusCode == http.StatusForbidden {
logrus.WithFields(logrus.Fields{
"method": r.Method,
"route": routePattern,
"client_ip": realip.FromRequest(r),
"user_agent": r.UserAgent(),
"request_id": middleware.GetReqID(ctx),
}).Warn("authentication_failure")
}
})
}
type responseLogger struct {
http.ResponseWriter
statusCode int
}
func (rl *responseLogger) WriteHeader(code int) {
rl.statusCode = code
rl.ResponseWriter.WriteHeader(code)
}
Integrate this middleware into your Chi application:
router := chi.NewRouter()
router.Use(logging.SecurityLogger{}.Middleware)
router.Use(middleware.Recoverer)
router.Use(middleware.RequestID)
router.Use(middleware.RealIP)
// Security-focused route logging
router.Group(func(r chi.Router) {
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Log sensitive operations
if strings.Contains(r.URL.Path, "/password") ||
strings.Contains(r.URL.Path, "/admin") {
logrus.WithFields(logrus.Fields{
"operation": "sensitive_operation",
"route": r.URL.Path,
"method": r.Method,
"client_ip": realip.FromRequest(r),
}).Info("sensitive_operation_attempted")
}
next.ServeHTTP(w, r)
})
})
})
For comprehensive audit trails, implement structured logging with correlation IDs and ensure all middleware components contribute to the security log:
func AuditMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Generate correlation ID
correlationID := uuid.New().String()
ctx := context.WithValue(r.Context(), "correlation_id", correlationID)
// Log request with correlation ID
logrus.WithFields(logrus.Fields{
"correlation_id": correlationID,
"method": r.Method,
"path": r.URL.Path,
"client_ip": realip.FromRequest(r),
"user_agent": r.UserAgent(),
}).Info("audit_request")
// Add correlation ID to request context
r = r.WithContext(ctx)
// Store start time for duration tracking
start := time.Now()
// Create response wrapper to capture status
rw := &statusCapturingResponseWriter{w, http.StatusOK}
next.ServeHTTP(rw, r)
// Log response with security context
duration := time.Since(start)
logrus.WithFields(logrus.Fields{
"correlation_id": correlationID,
"status": rw.statusCode,
"duration": duration,
"route": chi.RouteContext(r.Context()).RoutePattern(),
}).Info("audit_response")
// Log security events
if rw.statusCode >= 400 {
logrus.WithFields(logrus.Fields{
"correlation_id": correlationID,
"status": rw.statusCode,
"client_ip": realip.FromRequest(r),
"user_agent": r.UserAgent(),
"route": chi.RouteContext(r.Context()).RoutePattern(),
}).Warn("security_event")
}
})
}
type statusCapturingResponseWriter struct {
http.ResponseWriter
statusCode int
}
func (scrw *statusCapturingResponseWriter) WriteHeader(code int) {
scrw.statusCode = code
scrw.ResponseWriter.WriteHeader(code)
}