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
271 lines
6.4 KiB
Markdown
271 lines
6.4 KiB
Markdown
# 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=<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
|
|
```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: <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
|
|
|
|
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.
|