Skip to content

Commit e03f225

Browse files
authored
fix(security): remove CORS in production, dev-only allowlist (#443)
1 parent 9b373c3 commit e03f225

File tree

3 files changed

+62
-7
lines changed

3 files changed

+62
-7
lines changed

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ func main() {
240240
}
241241
r.Use(gin.Recovery())
242242
r.Use(middleware.Logger())
243-
r.Use(middleware.CORS())
243+
r.Use(middleware.DevCORS(common.CORSAllowedOrigins))
244244
model.InitDB()
245245
if _, err := model.GetGeneralSetting(); err != nil {
246246
klog.Warningf("Failed to load general setting: %v", err)

pkg/common/common.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ var (
4343
DisableGZIP = true
4444
EnableVersionCheck = true
4545

46+
// CORSAllowedOrigins is empty by default (no CORS in production).
47+
// Developers can set CORS_ALLOWED_ORIGINS=http://localhost:5173 for
48+
// local Vite dev server cross-origin requests.
49+
CORSAllowedOrigins []string
50+
4651
APIKeyProvider = "api_key"
4752

4853
AgentPodNamespace = "kube-system"
@@ -111,4 +116,15 @@ func LoadEnvs() {
111116
Base = strings.TrimRight(v, "/")
112117
klog.Infof("Using base path: %s", Base)
113118
}
119+
120+
if v := os.Getenv("CORS_ALLOWED_ORIGINS"); v != "" {
121+
origins := strings.Split(v, ",")
122+
for _, o := range origins {
123+
o = strings.TrimSpace(o)
124+
if o != "" {
125+
CORSAllowedOrigins = append(CORSAllowedOrigins, o)
126+
}
127+
}
128+
klog.Warningf("CORS enabled for origins: %v — disable in production", CORSAllowedOrigins)
129+
}
114130
}

pkg/middleware/cors.go

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,57 @@
11
package middleware
22

33
import (
4+
"net/http"
5+
"strings"
6+
47
"github.com/gin-gonic/gin"
58
)
69

7-
func CORS() gin.HandlerFunc {
10+
// DevCORS returns a CORS middleware that only allows explicitly configured
11+
// origins. In production the SPA is served from the same origin as the API
12+
// (go:embed), so no CORS headers are needed at all.
13+
//
14+
// Developers running the Vite dev server on a different port can set
15+
// CORS_ALLOWED_ORIGINS=http://localhost:5173 to enable cross-origin
16+
// requests during local development.
17+
//
18+
// When allowedOrigins is empty the middleware is a no-op — no CORS
19+
// headers are emitted and browsers enforce same-origin policy.
20+
func DevCORS(allowedOrigins []string) gin.HandlerFunc {
21+
allowed := make(map[string]bool, len(allowedOrigins))
22+
for _, o := range allowedOrigins {
23+
o = strings.TrimSpace(o)
24+
if o != "" {
25+
allowed[strings.TrimRight(o, "/")] = true
26+
}
27+
}
28+
829
return func(c *gin.Context) {
9-
c.Writer.Header().Set("Access-Control-Allow-Origin", c.Request.Header.Get("Origin"))
30+
// Fast path: no origins configured → no CORS headers (production)
31+
if len(allowed) == 0 {
32+
c.Next()
33+
return
34+
}
1035

36+
origin := c.Request.Header.Get("Origin")
37+
if origin == "" || !allowed[origin] {
38+
c.Next()
39+
return
40+
}
41+
42+
// Only set CORS headers for explicitly allowed origins
43+
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
1144
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
12-
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With, X-Cluster-Name")
13-
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
14-
if c.Request.Method == "OPTIONS" {
15-
c.AbortWithStatus(204)
45+
c.Writer.Header().Set("Access-Control-Allow-Headers",
46+
"Content-Type, Authorization, X-Cluster-Name")
47+
c.Writer.Header().Set("Access-Control-Allow-Methods",
48+
"GET, POST, PUT, DELETE, PATCH, OPTIONS")
49+
c.Writer.Header().Set("Access-Control-Max-Age", "86400")
50+
// Vary so caches/CDNs don't serve a response with the wrong origin
51+
c.Writer.Header().Add("Vary", "Origin")
52+
53+
if c.Request.Method == http.MethodOptions {
54+
c.AbortWithStatus(http.StatusNoContent)
1655
return
1756
}
1857
c.Next()

0 commit comments

Comments
 (0)