Harden local admin and secret storage

This commit is contained in:
Mikhail Chusavitin
2026-03-07 22:14:31 +03:00
parent 08de9006ef
commit 96572be712
6 changed files with 187 additions and 22 deletions

View File

@@ -1,18 +1,31 @@
package middleware
import (
"net"
"net/url"
"strings"
"github.com/gin-gonic/gin"
)
func CORS() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
if origin := strings.TrimSpace(c.GetHeader("Origin")); origin != "" {
if isLoopbackOrigin(origin) {
c.Header("Access-Control-Allow-Origin", origin)
c.Header("Vary", "Origin")
}
}
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization")
c.Header("Access-Control-Expose-Headers", "Content-Length, Content-Disposition")
c.Header("Access-Control-Max-Age", "86400")
if c.Request.Method == "OPTIONS" {
if strings.TrimSpace(c.GetHeader("Origin")) != "" && !isLoopbackOrigin(c.GetHeader("Origin")) {
c.AbortWithStatus(403)
return
}
c.AbortWithStatus(204)
return
}
@@ -20,3 +33,19 @@ func CORS() gin.HandlerFunc {
c.Next()
}
}
func isLoopbackOrigin(origin string) bool {
u, err := url.Parse(origin)
if err != nil {
return false
}
host := strings.TrimSpace(u.Hostname())
if host == "" {
return false
}
if strings.EqualFold(host, "localhost") {
return true
}
ip := net.ParseIP(host)
return ip != nil && ip.IsLoopback()
}

View File

@@ -0,0 +1,45 @@
package middleware
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
// OriginProtection blocks browser-driven cross-site requests to localhost.
// Same-origin UI requests continue to work; CLI clients without Origin/Referer remain allowed.
func OriginProtection() gin.HandlerFunc {
return func(c *gin.Context) {
if isSafeMethod(c.Request.Method) {
c.Next()
return
}
if secFetchSite := strings.TrimSpace(c.GetHeader("Sec-Fetch-Site")); secFetchSite == "cross-site" {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "cross-site requests are not allowed"})
return
}
if origin := strings.TrimSpace(c.GetHeader("Origin")); origin != "" && !isLoopbackOrigin(origin) {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "origin must be localhost"})
return
}
if referer := strings.TrimSpace(c.GetHeader("Referer")); referer != "" && !isLoopbackOrigin(referer) {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "referer must be localhost"})
return
}
c.Next()
}
}
func isSafeMethod(method string) bool {
switch method {
case http.MethodGet, http.MethodHead, http.MethodOptions:
return true
default:
return false
}
}