Null Pointer Dereference in Chi
How Null Pointer Dereference Manifests in Chi
Null pointer dereferences in Chi applications typically occur when handling external API responses or database query results. Since Chi is a lightweight Go HTTP router, it doesn't provide built-in null safety, making it vulnerable when developers assume objects will always be populated.
A common pattern is when middleware chains return nil contexts or when route handlers don't validate upstream service responses. For example, when a microservice calls an external dependency that returns an empty response, subsequent code might attempt to access properties on a nil struct, causing a runtime panic.
func getUserHandler(c *chi.Context) error {
userID := c.URLParam("id")
// Vulnerable: assuming user service always returns a valid user
user, err := userService.Get(userID)
if err != nil {
return c.JSON(http.StatusNotFound, map[string]string{"error": "user not found"})
}
// Potential null pointer dereference if user is nil
return c.JSON(http.StatusOK, map[string]string{
"name": user.Name, // PANIC if user is nil
"email": user.Email,
})
}
This becomes particularly dangerous in Chi applications that aggregate data from multiple services. If any service returns nil unexpectedly, the entire request chain can fail with a 500 error, potentially exposing stack traces or sensitive debugging information.
Another Chi-specific manifestation occurs with middleware that assumes request context values will always be present. When authentication middleware fails silently or returns nil, subsequent handlers might dereference nil pointers when accessing user information:
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Might return nil if auth fails without error
user := authenticateUser(r)
ctx := context.WithValue(r.Context(), "user", user)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func protectedHandler(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value("user").(*User) // PANIC if user is nil
fmt.Fprintf(w, "Hello, %s", user.Name)
}
The issue compounds in Chi applications using JSON unmarshaling where the target struct might be nil or when dealing with optional fields in request bodies that aren't properly validated before use.
Chi-Specific Detection
Detecting null pointer dereferences in Chi applications requires both static analysis and runtime monitoring. Static analysis tools like golangci-lint can catch some patterns, but they often miss context-specific issues that only appear during execution.
middleBrick's black-box scanning approach is particularly effective for Chi applications because it tests the actual running API without requiring source code access. The scanner sends requests to your Chi endpoints and analyzes the responses for patterns that indicate null pointer issues, such as:
- 500 Internal Server Error responses with stack traces containing "panic: runtime error: invalid memory address or nil pointer dereference"
- API responses that are unexpectedly empty or malformed
- Service timeouts that might indicate unhandled nil cases causing cascading failures
- Authentication failures that return generic errors instead of proper status codes
When scanning a Chi application with middleBrick, the tool examines the HTTP response patterns and compares them against known null pointer dereference signatures. For example, if a Chi endpoint that should return user data instead returns a 500 error with a Go panic stack trace, middleBrick flags this as a critical vulnerability.
# Scan your Chi API with middleBrick
middlebrick scan https://api.yourservice.com/users/123
The scanner also tests edge cases by sending malformed or incomplete data to your Chi endpoints, attempting to trigger nil pointer scenarios that might not appear during normal operation. This is especially valuable for Chi applications that handle complex request bodies or interact with multiple microservices.
For development environments, integrating middleBrick's GitHub Action into your CI/CD pipeline ensures that null pointer dereferences are caught before deployment:
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run middleBrick Security Scan
uses: middlebrick/middlebrick-action@v1
with:
api-url: http://localhost:8080
fail-on-severity: high
token: ${{ secrets.MIDDLEBRICK_TOKEN }}
This setup fails builds when null pointer dereferences or other critical issues are detected, preventing vulnerable Chi code from reaching production.
Chi-Specific Remediation
Remediating null pointer dereferences in Chi applications requires defensive programming patterns and proper error handling. The key is to never assume that external services, database queries, or context values will be non-nil.
First, implement comprehensive nil checks for all external service calls. In Chi applications, this often means wrapping service calls with validation logic:
func getUserHandler(w http.ResponseWriter, r *http.Request) {
userID := chi.URLParam(r, "id")
user, err := userService.Get(userID)
if err != nil {
http.Error(w, "user not found", http.StatusNotFound)
return
}
if user == nil {
http.Error(w, "user not found", http.StatusNotFound)
return
}
// Safe to use user now
json.NewEncoder(w).Encode(map[string]string{
"name": user.Name,
"email": user.Email,
})
}
Second, use Chi's context handling features to safely retrieve values with nil checks:
func requireAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user := authenticateUser(r)
if user == nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
ctx := chi.NewRouteContext()
ctx.URLParams.Add("user_id", user.ID)
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), "user", user)))
})
}
func protectedHandler(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value("user")
if user == nil {
http.Error(w, "internal server error", http.StatusInternalServerError)
return
}
typedUser := user.(*User)
fmt.Fprintf(w, "Hello, %s", typedUser.Name)
}
Third, leverage Go's error wrapping and custom error types to distinguish between different failure modes. This helps middleware handle errors appropriately without causing nil pointer dereferences:
type NotFoundError struct {
Message string
}
func (e *NotFoundError) Error() string {
return e.Message
}
func handleRequest(w http.ResponseWriter, r *http.Request) {
result, err := someOperation()
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
http.Error(w, "not found", http.StatusNotFound)
} else if errors.As(err, &NotFoundError{}) {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
http.Error(w, "internal server error", http.StatusInternalServerError)
}
return
}
if result == nil {
http.Error(w, "internal server error", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(result)
}
Finally, implement comprehensive testing that specifically targets nil cases. Write unit tests that pass nil values to your Chi handlers and verify they respond with appropriate HTTP status codes rather than panicking:
func TestGetUserHandler_NilUser(t *testing.T) {
req := httptest.NewRequest("GET", "/users/123", nil)
w := httptest.NewRecorder()
// Mock userService.Get to return nil
userService.Get = func(id string) (*User, error) {
return nil, nil
}
getUserHandler(w, req)
if w.Code != http.StatusNotFound {
t.Errorf("expected 404, got %d", w.Code)
}
}
These patterns ensure that Chi applications handle nil cases gracefully, providing proper HTTP responses instead of crashing with null pointer dereferences.