
Implementing Secure Error Handling in Go
Secure error handling involves several key principles: minimizing information leakage, logging errors appropriately, and ensuring that error messages do not reveal sensitive application logic. This tutorial will cover these principles with practical examples.
1. Avoiding Information Leakage
One of the primary goals of secure error handling is to avoid leaking sensitive information through error messages. This includes stack traces, internal logic, and database queries. Instead, provide generic error messages to users while logging detailed errors for developers.
Example: Generic User Error Messages
package main
import (
"fmt"
"log"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
_, err := someOperation()
if err != nil {
// Log the detailed error for developers
log.Printf("Error occurred: %v", err)
// Return a generic error message to the user
http.Error(w, "An unexpected error occurred. Please try again later.", http.StatusInternalServerError)
return
}
fmt.Fprintln(w, "Operation successful!")
}
func someOperation() (string, error) {
return "", fmt.Errorf("this is a detailed error message")
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}In this example, the detailed error is logged for developers, while the user receives a generic message that does not disclose sensitive information.
2. Centralized Error Handling
Centralizing error handling can help maintain consistency across your application. By using middleware or a custom error handler, you can ensure that all errors are processed in a uniform manner.
Example: Custom Error Handler Middleware
package main
import (
"fmt"
"log"
"net/http"
)
type errorHandler struct {
next http.Handler
}
func (h *errorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("Recovered from error: %v", err)
http.Error(w, "An unexpected error occurred. Please try again later.", http.StatusInternalServerError)
}
}()
h.next.ServeHTTP(w, r)
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
panic("this is a panic error")
})
log.Fatal(http.ListenAndServe(":8080", &errorHandler{next: mux}))
}In this example, the custom error handler middleware captures panics and logs them, while providing a secure response to the user.
3. Logging Best Practices
When logging errors, it’s important to include relevant context without exposing sensitive information. Use structured logging to capture key details.
Example: Structured Logging
package main
import (
"log"
"os"
)
func main() {
logger := log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
err := someOperation()
if err != nil {
logger.Printf("Error: %v | UserID: %d | Operation: %s", err, 12345, "someOperation")
}
}
func someOperation() error {
return fmt.Errorf("this is a detailed error message")
}In this structured logging example, the error message is logged along with relevant context, such as the user ID and operation type, without revealing sensitive application details.
4. User-Friendly Error Handling
While it’s essential to avoid leaking sensitive information, it’s also important to provide users with meaningful feedback. This can be achieved by categorizing errors and providing specific messages based on the type of error.
Example: Categorized Error Messages
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
err := someOperation()
if err != nil {
switch err {
case ErrNotFound:
http.Error(w, "Resource not found", http.StatusNotFound)
case ErrUnauthorized:
http.Error(w, "Unauthorized access", http.StatusUnauthorized)
default:
http.Error(w, "An unexpected error occurred. Please try again later.", http.StatusInternalServerError)
}
return
}
fmt.Fprintln(w, "Operation successful!")
}
var (
ErrNotFound = fmt.Errorf("not found")
ErrUnauthorized = fmt.Errorf("unauthorized")
)
func someOperation() error {
return ErrNotFound // Simulating an error
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}In this example, different error types are handled with specific messages, improving user experience while maintaining security.
Summary
Implementing secure error handling in Go is essential for protecting sensitive information while ensuring a good user experience. By avoiding information leakage, centralizing error handling, following logging best practices, and providing user-friendly messages, you can create a robust error handling strategy.
| Principle | Description |
|---|---|
| Avoid Information Leakage | Use generic messages for users; log detailed errors for developers. |
| Centralized Error Handling | Implement middleware to handle errors consistently across the application. |
| Logging Best Practices | Use structured logging to capture context without exposing sensitive data. |
| User-Friendly Error Handling | Categorize errors and provide meaningful feedback without revealing details. |
Learn more with useful resources:
