security.go 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. package middleware
  2. import (
  3. "net/http"
  4. "strings"
  5. "time"
  6. "github.com/gin-gonic/gin"
  7. )
  8. // SecurityHeaders adds security-related headers to all responses
  9. func SecurityHeaders() gin.HandlerFunc {
  10. return func(c *gin.Context) {
  11. // Security headers
  12. c.Header("X-Content-Type-Options", "nosniff")
  13. c.Header("X-Frame-Options", "DENY")
  14. c.Header("X-XSS-Protection", "1; mode=block")
  15. c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
  16. c.Header("Content-Security-Policy", "default-src 'self'; frame-ancestors 'none'")
  17. c.Header("Referrer-Policy", "strict-origin-when-cross-origin")
  18. c.Header("Permissions-Policy", "geolocation=(), microphone=(), camera=()")
  19. c.Next()
  20. }
  21. }
  22. // RequestSanitizer sanitizes incoming requests
  23. func RequestSanitizer() gin.HandlerFunc {
  24. return func(c *gin.Context) {
  25. // Sanitize headers
  26. sanitizeHeaders(c)
  27. // Block potentially dangerous file extensions
  28. if containsDangerousExtension(c.Request.URL.Path) {
  29. c.AbortWithStatus(http.StatusForbidden)
  30. return
  31. }
  32. c.Next()
  33. }
  34. }
  35. // TimeoutMiddleware adds a timeout to the request context
  36. func TimeoutMiddleware(timeout time.Duration) gin.HandlerFunc {
  37. return func(c *gin.Context) {
  38. // Wrap the request in a timeout
  39. ch := make(chan struct{})
  40. go func() {
  41. c.Next()
  42. ch <- struct{}{}
  43. }()
  44. select {
  45. case <-ch:
  46. return
  47. case <-time.After(timeout):
  48. c.AbortWithStatusJSON(http.StatusGatewayTimeout, gin.H{
  49. "code": "REQUEST_TIMEOUT",
  50. "error": "Request timeout exceeded",
  51. })
  52. return
  53. }
  54. }
  55. }
  56. // Helper functions
  57. func sanitizeHeaders(c *gin.Context) {
  58. // Remove potentially dangerous headers
  59. c.Request.Header.Del("X-Forwarded-For")
  60. c.Request.Header.Del("X-Real-IP")
  61. c.Request.Header.Del("X-Forwarded-Proto")
  62. // Sanitize User-Agent
  63. if ua := c.Request.Header.Get("User-Agent"); ua != "" {
  64. c.Request.Header.Set("User-Agent", sanitizeString(ua))
  65. }
  66. }
  67. func containsDangerousExtension(path string) bool {
  68. dangerous := []string{".php", ".asp", ".aspx", ".jsp", ".cgi", ".exe", ".bat", ".cmd", ".sh", ".pl"}
  69. path = strings.ToLower(path)
  70. for _, ext := range dangerous {
  71. if strings.HasSuffix(path, ext) {
  72. return true
  73. }
  74. }
  75. return false
  76. }
  77. func sanitizeString(s string) string {
  78. // Remove any control characters
  79. return strings.Map(func(r rune) rune {
  80. if r < 32 || r == 127 {
  81. return -1
  82. }
  83. return r
  84. }, s)
  85. }