# Input Validation Quick Reference ## For Developers: How to Validate User Input ### Basic Usage ```go 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 ```go // Protect PDF endpoint protectedHandler := middleware.MaxRequestSize(1024 * 1024)( http.HandlerFunc(handler.ExportPDF), ) mux.Handle("/export/pdf", protectedHandler) ``` ### Global Application (Already Applied) ```go // 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 ```go lang, err := validator.ValidateLanguage(r.URL.Query().Get("lang")) if err != nil { HandleError(w, r, BadRequestError("Invalid language. Supported: en, es")) return } ``` ### File Upload ```go // 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 ```go // 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 ```bash # Test script #!/bin/bash # Valid request curl -v "http://localhost:1999/endpoint?param=valid" # Invalid characters curl -v "http://localhost:1999/endpoint?param=" # 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 ```go // 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 ```go // 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 ```go log.Printf("SECURITY: - 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 1. **Not validating all inputs** ```go // ❌ BAD: Direct use without validation lang := r.URL.Query().Get("lang") // ✅ GOOD: Always validate lang, err := validator.ValidateLanguage(r.URL.Query().Get("lang")) ``` 2. **Client-side validation only** - Always validate on server-side - Client validation is for UX, not security 3. **Blacklist instead of whitelist** ```go // ❌ BAD: Trying to block bad values if lang == "admin" || lang == "root" { reject() } // ✅ GOOD: Only allow known-good values if lang != "en" && lang != "es" { reject() } ``` 4. **Not sanitizing output** - Even validated input should be sanitized for output - Use HTML escaping for HTML output - Use proper encoding for JSON 5. **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? 1. Check `internal/validator/validator_test.go` for examples 2. Review `SECURITY_VALIDATION_REPORT.md` for detailed info 3. Test locally before deploying 4. Monitor logs for security events **Remember**: When in doubt, reject the input! Better safe than sorry.