92dffe8c60
- Enhanced CI/CD pipeline with coverage reporting, benchmarks, and artifact uploads - Implemented rate limiter IP validation with proxy support and spoofing protection - Added extensive Makefile test targets for coverage, benchmarks, and continuous testing - Expanded middleware chain with request validation, size limits, and suspicious activity logging
6.4 KiB
6.4 KiB
Input Validation Quick Reference
For Developers: How to Validate User Input
Basic Usage
import "github.com/juanatsap/cv-site/internal/validator"
// 1. Validate language parameter
lang, err := validator.ValidateLanguage(r.URL.Query().Get("lang"))
if err != nil {
// Log and reject
log.Printf("SECURITY: Invalid input - IP: %s, Value: %q", getIP(r), lang)
http.Error(w, "Invalid parameter", http.StatusBadRequest)
return
}
// 2. Validate general query parameter
value, err := validator.ValidateQueryParam(
r.URL.Query().Get("param"),
50, // max length
regexp.MustCompile(`^[a-zA-Z0-9]+$`), // pattern (optional)
)
// 3. Check file paths (prevent path traversal)
if !validator.IsValidFilePath(filePath) {
http.Error(w, "Invalid file path", http.StatusBadRequest)
return
}
// 4. Sanitize user input
clean := validator.SanitizeInput(userInput)
// 5. Check for suspicious patterns
if validator.ContainsSuspiciousPatterns(input) {
log.Printf("SECURITY: Attack detected - %q", input)
http.Error(w, "Invalid input", http.StatusBadRequest)
return
}
Middleware Usage
Apply to Specific Routes
// Protect PDF endpoint
protectedHandler := middleware.MaxRequestSize(1024 * 1024)(
http.HandlerFunc(handler.ExportPDF),
)
mux.Handle("/export/pdf", protectedHandler)
Global Application (Already Applied)
// Full security stack (in main.go)
handler := middleware.Recovery(
middleware.Logger(
middleware.LogSuspiciousActivity(
middleware.SanitizeHeaders(
middleware.ValidateQueryStrings(
middleware.ValidateRequestPath(
middleware.MaxRequestSize(10 * 1024 * 1024)(
middleware.SecurityHeaders(mux),
),
),
),
),
),
),
)
Common Validation Patterns
Language Selection
lang, err := validator.ValidateLanguage(r.URL.Query().Get("lang"))
if err != nil {
HandleError(w, r, BadRequestError("Invalid language. Supported: en, es"))
return
}
File Upload
// 1. Check content type
allowedTypes := []string{"image/png", "image/jpeg", "application/pdf"}
if !validator.ValidateContentType(r.Header.Get("Content-Type"), allowedTypes) {
http.Error(w, "Invalid file type", http.StatusBadRequest)
return
}
// 2. Sanitize filename
filename := validator.SanitizeFilename(r.FormValue("filename"))
// 3. Validate path
if !validator.IsValidFilePath(filename) {
http.Error(w, "Invalid filename", http.StatusBadRequest)
return
}
Search Query
// Alphanumeric only, max 100 chars
query, err := validator.ValidateQueryParam(
r.URL.Query().Get("q"),
100,
regexp.MustCompile(`^[a-zA-Z0-9 ]+$`),
)
if err != nil {
http.Error(w, "Invalid search query", http.StatusBadRequest)
return
}
Security Checklist for New Endpoints
- Validate all query parameters
- Validate all form inputs
- Check file paths if accessing files
- Sanitize user-provided content
- Check for suspicious patterns
- Log rejected inputs
- Return generic error messages (don't expose internals)
- Add rate limiting if resource-intensive
- Test with attack vectors
Testing Your Validation
# Test script
#!/bin/bash
# Valid request
curl -v "http://localhost:1999/endpoint?param=valid"
# Invalid characters
curl -v "http://localhost:1999/endpoint?param=<script>alert(1)</script>"
# Path traversal
curl -v "http://localhost:1999/endpoint?param=../../etc/passwd"
# SQL injection
curl -v "http://localhost:1999/endpoint?param=' OR '1'='1"
# Null byte
curl -v "http://localhost:1999/endpoint?param=test%00admin"
# Excessive length
curl -v "http://localhost:1999/endpoint?param=$(python3 -c 'print("a"*5000)')"
Error Handling Best Practices
✅ DO
// Generic error message
if err := validator.ValidateLanguage(lang); err != nil {
log.Printf("SECURITY: Invalid input - IP: %s, Value: %q", ip, lang)
http.Error(w, "Invalid parameter", http.StatusBadRequest)
return
}
❌ DON'T
// Exposing validation details to attacker
if err := validator.ValidateLanguage(lang); err != nil {
http.Error(w, fmt.Sprintf("Invalid language: %v", err), http.StatusBadRequest)
return
}
Security Logging
Log Format
log.Printf("SECURITY: <event> - IP: %s, Path: %s, Value: %q",
getClientIP(r), r.URL.Path, suspiciousValue)
What to Log
- ✅ Rejected inputs
- ✅ Attack patterns detected
- ✅ Suspicious activity
- ✅ IP addresses
- ✅ Timestamps
- ❌ Don't log sensitive data (passwords, tokens, PII)
Common Mistakes to Avoid
-
Not validating all inputs
// ❌ BAD: Direct use without validation lang := r.URL.Query().Get("lang") // ✅ GOOD: Always validate lang, err := validator.ValidateLanguage(r.URL.Query().Get("lang")) -
Client-side validation only
- Always validate on server-side
- Client validation is for UX, not security
-
Blacklist instead of whitelist
// ❌ BAD: Trying to block bad values if lang == "admin" || lang == "root" { reject() } // ✅ GOOD: Only allow known-good values if lang != "en" && lang != "es" { reject() } -
Not sanitizing output
- Even validated input should be sanitized for output
- Use HTML escaping for HTML output
- Use proper encoding for JSON
-
Trusting referer/origin headers
- Headers can be spoofed
- Use secure tokens for sensitive operations
When to Add New Validation
Add validation whenever:
- Accepting user input (query params, forms, headers)
- Constructing file paths from user input
- Building commands or queries from user input
- Accepting file uploads
- Processing URL parameters
- Handling HTTP headers
Performance Considerations
Validation is fast (<1ms per request), but:
- Cache validated results when possible
- Don't re-validate same input multiple times
- Use simple checks first (length) before complex (regex)
- Consider async validation for non-critical paths
Questions?
- Check
internal/validator/validator_test.gofor examples - Review
SECURITY_VALIDATION_REPORT.mdfor detailed info - Test locally before deploying
- Monitor logs for security events
Remember: When in doubt, reject the input! Better safe than sorry.