From a27346b4b141f2c0cf684092f08a98db0e33f134 Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Mon, 26 Feb 2024 15:48:54 -0400 Subject: [PATCH 01/32] fix: allow origins check Refactor CORS origin validation and normalization to trim leading or trailing whitespace in the cfg.AllowOrigins string [list]. URLs with whitespace inside the URL are invalid, so the normalizeOrigin will return false because url.Parse will fail, and the middleware will panic. fixes #2882 --- middleware/cors/cors.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/middleware/cors/cors.go b/middleware/cors/cors.go index 2ca3767d1f..a1cbf6d116 100644 --- a/middleware/cors/cors.go +++ b/middleware/cors/cors.go @@ -113,24 +113,28 @@ func New(config ...Config) fiber.Handler { panic("[CORS] Insecure setup, 'AllowCredentials' is set to true, and 'AllowOrigins' is set to a wildcard.") } - // Validate and normalize static AllowOrigins if not using AllowOriginsFunc - if cfg.AllowOriginsFunc == nil && cfg.AllowOrigins != "" && cfg.AllowOrigins != "*" { - validatedOrigins := []string{} + // allowOrigins is a slice of strings that contains the allowed origins + // that are defined in the 'AllowOrigins' configuration. + var allowOrigins []string + + // Validate and normalize static AllowOrigins + if cfg.AllowOrigins != "" && cfg.AllowOrigins != "*" { for _, origin := range strings.Split(cfg.AllowOrigins, ",") { + origin = strings.TrimSpace(origin) isValid, normalizedOrigin := normalizeOrigin(origin) if isValid { - validatedOrigins = append(validatedOrigins, normalizedOrigin) + allowOrigins = append(allowOrigins, normalizedOrigin) } else { log.Warnf("[CORS] Invalid origin format in configuration: %s", origin) panic("[CORS] Invalid origin provided in configuration") } } - cfg.AllowOrigins = strings.Join(validatedOrigins, ",") + } else { + // If AllowOrigins is set to a wildcard or not set, + // set the allowOrigins to a slice with a single element + allowOrigins = []string{cfg.AllowOrigins} } - // Convert string to slice - allowOrigins := strings.Split(strings.ReplaceAll(cfg.AllowOrigins, " ", ""), ",") - // Strip white spaces allowMethods := strings.ReplaceAll(cfg.AllowMethods, " ", "") allowHeaders := strings.ReplaceAll(cfg.AllowHeaders, " ", "") @@ -165,10 +169,8 @@ func New(config ...Config) fiber.Handler { // Run AllowOriginsFunc if the logic for // handling the value in 'AllowOrigins' does // not result in allowOrigin being set. - if allowOrigin == "" && cfg.AllowOriginsFunc != nil { - if cfg.AllowOriginsFunc(originHeader) { - allowOrigin = originHeader - } + if allowOrigin == "" && cfg.AllowOriginsFunc != nil && cfg.AllowOriginsFunc(originHeader) { + allowOrigin = originHeader } // Simple request From 058cf0a617bdd960e3a4e25a483f74679dc23296 Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Mon, 26 Feb 2024 15:54:25 -0400 Subject: [PATCH 02/32] test: AllowOrigins with whitespace --- middleware/cors/cors_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/middleware/cors/cors_test.go b/middleware/cors/cors_test.go index 9fc2852556..c09e1b2e3f 100644 --- a/middleware/cors/cors_test.go +++ b/middleware/cors/cors_test.go @@ -452,6 +452,33 @@ func Test_CORS_AllowOriginsAndAllowOriginsFunc_AllUseCases(t *testing.T) { RequestOrigin: "http://aaa.com", ResponseOrigin: "http://aaa.com", }, + { + Name: "AllowOriginsDefined/AllowOriginsFuncUndefined/MultipleOrigins/NoWhitespace/OriginAllowed", + Config: Config{ + AllowOrigins: "http://aaa.com,http://bbb.com", + AllowOriginsFunc: nil, + }, + RequestOrigin: "http://bbb.com", + ResponseOrigin: "http://bbb.com", + }, + { + Name: "AllowOriginsDefined/AllowOriginsFuncUndefined/MultipleOrigins/NoWhitespace/OriginNotAllowed", + Config: Config{ + AllowOrigins: "http://aaa.com,http://bbb.com", + AllowOriginsFunc: nil, + }, + RequestOrigin: "http://ccc.com", + ResponseOrigin: "", + }, + { + Name: "AllowOriginsDefined/AllowOriginsFuncUndefined/MultipleOrigins/Whitespace/OriginAllowed", + Config: Config{ + AllowOrigins: "http://aaa.com, http://bbb.com", + AllowOriginsFunc: nil, + }, + RequestOrigin: "http://aaa.com", + ResponseOrigin: "http://aaa.com", + }, { Name: "AllowOriginsDefined/AllowOriginsFuncUndefined/OriginNotAllowed", Config: Config{ From 9224a2e30d8768e5a316403169903e8eeac53d63 Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Mon, 26 Feb 2024 16:06:31 -0400 Subject: [PATCH 03/32] test(middleware/cors): add benchmarks --- middleware/cors/cors_test.go | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/middleware/cors/cors_test.go b/middleware/cors/cors_test.go index c09e1b2e3f..3eae1fe8ca 100644 --- a/middleware/cors/cors_test.go +++ b/middleware/cors/cors_test.go @@ -674,3 +674,43 @@ func Test_CORS_AllowCredentials(t *testing.T) { }) } } + +func Benchmark_CORS_NewHandler(b *testing.B) { + app := fiber.New() + app.Use(New(Config{ + AllowOrigins: "http://localhost,http://example.com", + AllowMethods: "GET,POST,PUT,DELETE", + AllowHeaders: "Origin,Content-Type,Accept", + AllowCredentials: true, + MaxAge: 600, + })) + + req := httptest.NewRequest("GET", "/", nil) + req.Header.Set(fiber.HeaderOrigin, "http://localhost") + + b.ResetTimer() + for i := 0; i < b.N; i++ { + app.Test(req) + } +} + +func Benchmark_CORS_NewHandlerPreflight(b *testing.B) { + app := fiber.New() + app.Use(New(Config{ + AllowOrigins: "http://localhost,http://example.com", + AllowMethods: "GET,POST,PUT,DELETE", + AllowHeaders: "Origin,Content-Type,Accept", + AllowCredentials: true, + MaxAge: 600, + })) + + req := httptest.NewRequest("OPTIONS", "/", nil) + req.Header.Set(fiber.HeaderOrigin, "http://localhost") + req.Header.Set(fiber.HeaderAccessControlRequestMethod, "POST") + req.Header.Set(fiber.HeaderAccessControlRequestHeaders, "Origin,Content-Type,Accept") + + b.ResetTimer() + for i := 0; i < b.N; i++ { + app.Test(req) + } +} From 221310e3a7e0efb317394d7248a1bf5097a50437 Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Mon, 26 Feb 2024 16:14:55 -0400 Subject: [PATCH 04/32] chore: fix linter errors --- middleware/cors/cors_test.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/middleware/cors/cors_test.go b/middleware/cors/cors_test.go index 3eae1fe8ca..36aeef1f67 100644 --- a/middleware/cors/cors_test.go +++ b/middleware/cors/cors_test.go @@ -685,12 +685,16 @@ func Benchmark_CORS_NewHandler(b *testing.B) { MaxAge: 600, })) - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(fiber.MethodGet, "/", nil) req.Header.Set(fiber.HeaderOrigin, "http://localhost") b.ResetTimer() for i := 0; i < b.N; i++ { - app.Test(req) + resp, err := app.Test(req) + if err != nil { + b.Fatalf("Failed to perform request: %v", err) + } + _ = resp } } @@ -704,13 +708,17 @@ func Benchmark_CORS_NewHandlerPreflight(b *testing.B) { MaxAge: 600, })) - req := httptest.NewRequest("OPTIONS", "/", nil) + req := httptest.NewRequest(fiber.MethodOptions, "/", nil) req.Header.Set(fiber.HeaderOrigin, "http://localhost") - req.Header.Set(fiber.HeaderAccessControlRequestMethod, "POST") + req.Header.Set(fiber.HeaderAccessControlRequestMethod, fiber.MethodPost) req.Header.Set(fiber.HeaderAccessControlRequestHeaders, "Origin,Content-Type,Accept") b.ResetTimer() for i := 0; i < b.N; i++ { - app.Test(req) + resp, err := app.Test(req) + if err != nil { + b.Fatalf("Failed to perform request: %v", err) + } + _ = resp } } From 9bc71611428f80b45fee92231900e8929055790a Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Tue, 27 Feb 2024 15:24:51 -0400 Subject: [PATCH 05/32] test(middleware/cors): use h() instead of app.Test() --- middleware/cors/cors_test.go | 56 +++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/middleware/cors/cors_test.go b/middleware/cors/cors_test.go index 36aeef1f67..ed77894b80 100644 --- a/middleware/cors/cors_test.go +++ b/middleware/cors/cors_test.go @@ -675,50 +675,74 @@ func Test_CORS_AllowCredentials(t *testing.T) { } } +// go test -v -run=^$ -bench=Benchmark_CORS_NewHandler -benchmem -count=4 func Benchmark_CORS_NewHandler(b *testing.B) { app := fiber.New() - app.Use(New(Config{ + c := New(Config{ AllowOrigins: "http://localhost,http://example.com", AllowMethods: "GET,POST,PUT,DELETE", AllowHeaders: "Origin,Content-Type,Accept", AllowCredentials: true, MaxAge: 600, - })) + }) + + app.Use(c) + app.Use(func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + h := app.Handler() + ctx := &fasthttp.RequestCtx{} - req := httptest.NewRequest(fiber.MethodGet, "/", nil) + req := &fasthttp.Request{} + req.Header.SetMethod(fiber.MethodGet) + req.SetRequestURI("/") req.Header.Set(fiber.HeaderOrigin, "http://localhost") + req.Header.Set(fiber.HeaderAccessControlRequestMethod, fiber.MethodGet) + req.Header.Set(fiber.HeaderAccessControlRequestHeaders, "Origin,Content-Type,Accept") + + ctx.Init(req, nil, nil) + b.ReportAllocs() b.ResetTimer() + for i := 0; i < b.N; i++ { - resp, err := app.Test(req) - if err != nil { - b.Fatalf("Failed to perform request: %v", err) - } - _ = resp + h(ctx) } } +// go test -v -run=^$ -bench=Benchmark_CORS_NewHandlerPreflight -benchmem -count=4 func Benchmark_CORS_NewHandlerPreflight(b *testing.B) { app := fiber.New() - app.Use(New(Config{ + c := New(Config{ AllowOrigins: "http://localhost,http://example.com", AllowMethods: "GET,POST,PUT,DELETE", AllowHeaders: "Origin,Content-Type,Accept", AllowCredentials: true, MaxAge: 600, - })) + }) + + app.Use(c) + app.Use(func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) - req := httptest.NewRequest(fiber.MethodOptions, "/", nil) + h := app.Handler() + ctx := &fasthttp.RequestCtx{} + + req := &fasthttp.Request{} + req.Header.SetMethod(fiber.MethodOptions) + req.SetRequestURI("/") req.Header.Set(fiber.HeaderOrigin, "http://localhost") req.Header.Set(fiber.HeaderAccessControlRequestMethod, fiber.MethodPost) req.Header.Set(fiber.HeaderAccessControlRequestHeaders, "Origin,Content-Type,Accept") + ctx.Init(req, nil, nil) + + b.ReportAllocs() b.ResetTimer() + for i := 0; i < b.N; i++ { - resp, err := app.Test(req) - if err != nil { - b.Fatalf("Failed to perform request: %v", err) - } - _ = resp + h(ctx) } } From 000c548b5a678d09cbd92d14129f67fa31f27daa Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Tue, 27 Feb 2024 15:28:09 -0400 Subject: [PATCH 06/32] test(middleware/cors): add miltiple origins in Test_CORS_AllowOriginScheme --- middleware/cors/cors_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/middleware/cors/cors_test.go b/middleware/cors/cors_test.go index ed77894b80..06d64e4661 100644 --- a/middleware/cors/cors_test.go +++ b/middleware/cors/cors_test.go @@ -307,6 +307,21 @@ func Test_CORS_AllowOriginScheme(t *testing.T) { reqOrigin: "http://ccc.bbb.example.com", shouldAllowOrigin: false, }, + { + pattern: "http://domain-1.com, http://example.com", + reqOrigin: "http://example.com", + shouldAllowOrigin: true, + }, + { + pattern: "http://domain-1.com, http://example.com", + reqOrigin: "http://domain-2.com", + shouldAllowOrigin: false, + }, + { + pattern: "http://domain-1.com,http://example.com", + reqOrigin: "http://domain-1.com", + shouldAllowOrigin: true, + }, } for _, tt := range tests { From b3c7a2c75345dd5d18f2e880be93e21f9b40553b Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Tue, 27 Feb 2024 15:43:59 -0400 Subject: [PATCH 07/32] chore: refactor validate and normalize --- middleware/cors/cors.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/middleware/cors/cors.go b/middleware/cors/cors.go index a1cbf6d116..3accc2f1dd 100644 --- a/middleware/cors/cors.go +++ b/middleware/cors/cors.go @@ -114,24 +114,28 @@ func New(config ...Config) fiber.Handler { } // allowOrigins is a slice of strings that contains the allowed origins - // that are defined in the 'AllowOrigins' configuration. + // defined in the 'AllowOrigins' configuration. var allowOrigins []string // Validate and normalize static AllowOrigins if cfg.AllowOrigins != "" && cfg.AllowOrigins != "*" { - for _, origin := range strings.Split(cfg.AllowOrigins, ",") { - origin = strings.TrimSpace(origin) - isValid, normalizedOrigin := normalizeOrigin(origin) + origins := strings.Split(cfg.AllowOrigins, ",") + allowOrigins = make([]string, len(origins)) + + for i, origin := range origins { + trimmedOrigin := strings.TrimSpace(origin) + isValid, normalizedOrigin := normalizeOrigin(trimmedOrigin) + if isValid { - allowOrigins = append(allowOrigins, normalizedOrigin) + allowOrigins[i] = normalizedOrigin } else { - log.Warnf("[CORS] Invalid origin format in configuration: %s", origin) + log.Warnf("[CORS] Invalid origin format in configuration: %s", trimmedOrigin) panic("[CORS] Invalid origin provided in configuration") } } } else { // If AllowOrigins is set to a wildcard or not set, - // set the allowOrigins to a slice with a single element + // set allowOrigins to a slice with a single element allowOrigins = []string{cfg.AllowOrigins} } From a8cf4f0b801833e083cc71f649dec7039ec58b19 Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Thu, 29 Feb 2024 21:44:25 -0400 Subject: [PATCH 08/32] test(cors/middleware): add more benchmarks --- middleware/cors/cors_test.go | 380 ++++++++++++++++++++++++++++++++++- 1 file changed, 379 insertions(+), 1 deletion(-) diff --git a/middleware/cors/cors_test.go b/middleware/cors/cors_test.go index 06d64e4661..23692c3f84 100644 --- a/middleware/cors/cors_test.go +++ b/middleware/cors/cors_test.go @@ -726,6 +726,195 @@ func Benchmark_CORS_NewHandler(b *testing.B) { } } +// go test -v -run=^$ -bench=Benchmark_CORS_NewHandlerParallel -benchmem -count=4 +func Benchmark_CORS_NewHandlerParallel(b *testing.B) { + app := fiber.New() + c := New(Config{ + AllowOrigins: "http://localhost,http://example.com", + AllowMethods: "GET,POST,PUT,DELETE", + AllowHeaders: "Origin,Content-Type,Accept", + AllowCredentials: true, + MaxAge: 600, + }) + + app.Use(c) + app.Use(func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + h := app.Handler() + + b.ReportAllocs() + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + ctx := &fasthttp.RequestCtx{} + + req := &fasthttp.Request{} + req.Header.SetMethod(fiber.MethodGet) + req.SetRequestURI("/") + req.Header.Set(fiber.HeaderOrigin, "http://localhost") + req.Header.Set(fiber.HeaderAccessControlRequestMethod, fiber.MethodGet) + req.Header.Set(fiber.HeaderAccessControlRequestHeaders, "Origin,Content-Type,Accept") + + ctx.Init(req, nil, nil) + + for pb.Next() { + h(ctx) + } + }) +} + +// go test -v -run=^$ -bench=Benchmark_CORS_NewHandlerSingleOrigin -benchmem -count=4 +func Benchmark_CORS_NewHandlerSingleOrigin(b *testing.B) { + app := fiber.New() + c := New(Config{ + AllowOrigins: "http://example.com", + AllowMethods: "GET,POST,PUT,DELETE", + AllowHeaders: "Origin,Content-Type,Accept", + AllowCredentials: true, + MaxAge: 600, + }) + + app.Use(c) + app.Use(func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + h := app.Handler() + ctx := &fasthttp.RequestCtx{} + + req := &fasthttp.Request{} + req.Header.SetMethod(fiber.MethodGet) + req.SetRequestURI("/") + req.Header.Set(fiber.HeaderOrigin, "http://example.com") + req.Header.Set(fiber.HeaderAccessControlRequestMethod, fiber.MethodGet) + req.Header.Set(fiber.HeaderAccessControlRequestHeaders, "Origin,Content-Type,Accept") + + ctx.Init(req, nil, nil) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + h(ctx) + } +} + +// go test -v -run=^$ -bench=Benchmark_CORS_NewHandlerSingleOriginParallel -benchmem -count=4 +func Benchmark_CORS_NewHandlerSingleOriginParallel(b *testing.B) { + app := fiber.New() + c := New(Config{ + AllowOrigins: "http://example.com", + AllowMethods: "GET,POST,PUT,DELETE", + AllowHeaders: "Origin,Content-Type,Accept", + AllowCredentials: true, + MaxAge: 600, + }) + + app.Use(c) + app.Use(func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + h := app.Handler() + + b.ReportAllocs() + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + ctx := &fasthttp.RequestCtx{} + + req := &fasthttp.Request{} + req.Header.SetMethod(fiber.MethodGet) + req.SetRequestURI("/") + req.Header.Set(fiber.HeaderOrigin, "http://example.com") + req.Header.Set(fiber.HeaderAccessControlRequestMethod, fiber.MethodGet) + req.Header.Set(fiber.HeaderAccessControlRequestHeaders, "Origin,Content-Type,Accept") + + ctx.Init(req, nil, nil) + + for pb.Next() { + h(ctx) + } + }) +} + +// go test -v -run=^$ -bench=Benchmark_CORS_NewHandlerWildcard -benchmem -count=4 +func Benchmark_CORS_NewHandlerWildcard(b *testing.B) { + app := fiber.New() + c := New(Config{ + AllowOrigins: "*", + AllowMethods: "GET,POST,PUT,DELETE", + AllowHeaders: "Origin,Content-Type,Accept", + AllowCredentials: false, + MaxAge: 600, + }) + + app.Use(c) + app.Use(func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + h := app.Handler() + ctx := &fasthttp.RequestCtx{} + + req := &fasthttp.Request{} + req.Header.SetMethod(fiber.MethodGet) + req.SetRequestURI("/") + req.Header.Set(fiber.HeaderOrigin, "http://example.com") + req.Header.Set(fiber.HeaderAccessControlRequestMethod, fiber.MethodGet) + req.Header.Set(fiber.HeaderAccessControlRequestHeaders, "Origin,Content-Type,Accept") + + ctx.Init(req, nil, nil) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + h(ctx) + } +} + +// go test -v -run=^$ -bench=Benchmark_CORS_NewHandlerWildcardParallel -benchmem -count=4 +func Benchmark_CORS_NewHandlerWildcardParallel(b *testing.B) { + app := fiber.New() + c := New(Config{ + AllowOrigins: "*", + AllowMethods: "GET,POST,PUT,DELETE", + AllowHeaders: "Origin,Content-Type,Accept", + AllowCredentials: false, + MaxAge: 600, + }) + + app.Use(c) + app.Use(func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + h := app.Handler() + + b.ReportAllocs() + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + ctx := &fasthttp.RequestCtx{} + + req := &fasthttp.Request{} + req.Header.SetMethod(fiber.MethodGet) + req.SetRequestURI("/") + req.Header.Set(fiber.HeaderOrigin, "http://example.com") + req.Header.Set(fiber.HeaderAccessControlRequestMethod, fiber.MethodGet) + req.Header.Set(fiber.HeaderAccessControlRequestHeaders, "Origin,Content-Type,Accept") + + ctx.Init(req, nil, nil) + + for pb.Next() { + h(ctx) + } + }) +} + // go test -v -run=^$ -bench=Benchmark_CORS_NewHandlerPreflight -benchmem -count=4 func Benchmark_CORS_NewHandlerPreflight(b *testing.B) { app := fiber.New() @@ -748,7 +937,7 @@ func Benchmark_CORS_NewHandlerPreflight(b *testing.B) { req := &fasthttp.Request{} req.Header.SetMethod(fiber.MethodOptions) req.SetRequestURI("/") - req.Header.Set(fiber.HeaderOrigin, "http://localhost") + req.Header.Set(fiber.HeaderOrigin, "http://example.com") req.Header.Set(fiber.HeaderAccessControlRequestMethod, fiber.MethodPost) req.Header.Set(fiber.HeaderAccessControlRequestHeaders, "Origin,Content-Type,Accept") @@ -761,3 +950,192 @@ func Benchmark_CORS_NewHandlerPreflight(b *testing.B) { h(ctx) } } + +// go test -v -run=^$ -bench=Benchmark_CORS_NewHandlerPreflightParallel -benchmem -count=4 +func Benchmark_CORS_NewHandlerPreflightParallel(b *testing.B) { + app := fiber.New() + c := New(Config{ + AllowOrigins: "http://localhost,http://example.com", + AllowMethods: "GET,POST,PUT,DELETE", + AllowHeaders: "Origin,Content-Type,Accept", + AllowCredentials: true, + MaxAge: 600, + }) + + app.Use(c) + app.Use(func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + h := app.Handler() + + b.ReportAllocs() + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + ctx := &fasthttp.RequestCtx{} + + req := &fasthttp.Request{} + req.Header.SetMethod(fiber.MethodOptions) + req.SetRequestURI("/") + req.Header.Set(fiber.HeaderOrigin, "http://example.com") + req.Header.Set(fiber.HeaderAccessControlRequestMethod, fiber.MethodPost) + req.Header.Set(fiber.HeaderAccessControlRequestHeaders, "Origin,Content-Type,Accept") + + ctx.Init(req, nil, nil) + + for pb.Next() { + h(ctx) + } + }) +} + +// go test -v -run=^$ -bench=Benchmark_CORS_NewHandlerPreflightSingleOrigin -benchmem -count=4 +func Benchmark_CORS_NewHandlerPreflightSingleOrigin(b *testing.B) { + app := fiber.New() + c := New(Config{ + AllowOrigins: "http://example.com", + AllowMethods: "GET,POST,PUT,DELETE", + AllowHeaders: "Origin,Content-Type,Accept", + AllowCredentials: true, + MaxAge: 600, + }) + + app.Use(c) + app.Use(func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + h := app.Handler() + ctx := &fasthttp.RequestCtx{} + + req := &fasthttp.Request{} + req.Header.SetMethod(fiber.MethodOptions) + req.SetRequestURI("/") + req.Header.Set(fiber.HeaderOrigin, "http://example.com") + req.Header.Set(fiber.HeaderAccessControlRequestMethod, fiber.MethodPost) + req.Header.Set(fiber.HeaderAccessControlRequestHeaders, "Origin,Content-Type,Accept") + + ctx.Init(req, nil, nil) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + h(ctx) + } +} + +// go test -v -run=^$ -bench=Benchmark_CORS_NewHandlerPreflightSingleOriginParallel -benchmem -count=4 +func Benchmark_CORS_NewHandlerPreflightSingleOriginParallel(b *testing.B) { + app := fiber.New() + c := New(Config{ + AllowOrigins: "http://example.com", + AllowMethods: "GET,POST,PUT,DELETE", + AllowHeaders: "Origin,Content-Type,Accept", + AllowCredentials: true, + MaxAge: 600, + }) + + app.Use(c) + app.Use(func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + h := app.Handler() + + b.ReportAllocs() + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + ctx := &fasthttp.RequestCtx{} + + req := &fasthttp.Request{} + req.Header.SetMethod(fiber.MethodOptions) + req.SetRequestURI("/") + req.Header.Set(fiber.HeaderOrigin, "http://example.com") + req.Header.Set(fiber.HeaderAccessControlRequestMethod, fiber.MethodPost) + req.Header.Set(fiber.HeaderAccessControlRequestHeaders, "Origin,Content-Type,Accept") + + ctx.Init(req, nil, nil) + + for pb.Next() { + h(ctx) + } + }) +} + +// go test -v -run=^$ -bench=Benchmark_CORS_NewHandlerPreflightWildcard -benchmem -count=4 +func Benchmark_CORS_NewHandlerPreflightWildcard(b *testing.B) { + app := fiber.New() + c := New(Config{ + AllowOrigins: "*", + AllowMethods: "GET,POST,PUT,DELETE", + AllowHeaders: "Origin,Content-Type,Accept", + AllowCredentials: false, + MaxAge: 600, + }) + + app.Use(c) + app.Use(func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + h := app.Handler() + ctx := &fasthttp.RequestCtx{} + + req := &fasthttp.Request{} + req.Header.SetMethod(fiber.MethodOptions) + req.SetRequestURI("/") + req.Header.Set(fiber.HeaderOrigin, "http://example.com") + req.Header.Set(fiber.HeaderAccessControlRequestMethod, fiber.MethodPost) + req.Header.Set(fiber.HeaderAccessControlRequestHeaders, "Origin,Content-Type,Accept") + + ctx.Init(req, nil, nil) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + h(ctx) + } +} + +// go test -v -run=^$ -bench=Benchmark_CORS_NewHandlerPreflightWildcardParallel -benchmem -count=4 +func Benchmark_CORS_NewHandlerPreflightWildcardParallel(b *testing.B) { + app := fiber.New() + c := New(Config{ + AllowOrigins: "*", + AllowMethods: "GET,POST,PUT,DELETE", + AllowHeaders: "Origin,Content-Type,Accept", + AllowCredentials: false, + MaxAge: 600, + }) + + app.Use(c) + app.Use(func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + h := app.Handler() + + b.ReportAllocs() + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + ctx := &fasthttp.RequestCtx{} + + req := &fasthttp.Request{} + req.Header.SetMethod(fiber.MethodOptions) + req.SetRequestURI("/") + req.Header.Set(fiber.HeaderOrigin, "http://example.com") + req.Header.Set(fiber.HeaderAccessControlRequestMethod, fiber.MethodPost) + req.Header.Set(fiber.HeaderAccessControlRequestHeaders, "Origin,Content-Type,Accept") + + ctx.Init(req, nil, nil) + + for pb.Next() { + h(ctx) + } + }) +} From bba56fbbccbc5ed876c26f06e3ef5fc81a4c8a0d Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Mon, 11 Mar 2024 13:09:36 -0300 Subject: [PATCH 09/32] fix(middleware/cors): handling and wildcard subdomain matching docs(middleware/cors): add How it works and Security Considerations --- docs/api/middleware/cors.md | 50 +++++++++++++++++++++- middleware/cors/cors.go | 4 +- middleware/cors/cors_test.go | 78 ++++++++++++++++++++++++++++------- middleware/cors/utils.go | 25 +++++------ middleware/cors/utils_test.go | 13 +++--- 5 files changed, 134 insertions(+), 36 deletions(-) diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index ca250833d6..3e1f67e417 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -73,7 +73,7 @@ app.Use(cors.New(cors.Config{ |:-----------------|:---------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------| | Next | `func(*fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` | | AllowOriginsFunc | `func(origin string) bool` | AllowOriginsFunc defines a function that will set the 'access-control-allow-origin' response header to the 'origin' request header when returned true. This allows for dynamic evaluation of allowed origins. Note if AllowCredentials is true, wildcard origins will be not have the 'access-control-allow-credentials' header set to 'true'. | `nil` | -| AllowOrigins | `string` | AllowOrigin defines a comma separated list of origins that may access the resource. | `"*"` | +| AllowOrigins | `string` | AllowOrigin defines a comma separated list of origins that may access the resource. This supports subdomain matching, so you can use a value like "https://.example.com" to allow any subdomain of example.com to submit requests. | `"*"` | | AllowMethods | `string` | AllowMethods defines a list of methods allowed when accessing the resource. This is used in response to a preflight request. | `"GET,POST,HEAD,PUT,DELETE,PATCH"` | | AllowHeaders | `string` | AllowHeaders defines a list of request headers that can be used when making the actual request. This is in response to a preflight request. | `""` | | AllowCredentials | `bool` | AllowCredentials indicates whether or not the response to the request can be exposed when the credentials flag is true. When used as part of a response to a preflight request, this indicates whether or not the actual request can be made using credentials. Note: If true, AllowOrigins cannot be set to a wildcard ("*") to prevent security vulnerabilities. | `false` | @@ -101,3 +101,51 @@ var ConfigDefault = Config{ MaxAge: 0, } ``` + +# How It Works + +The CORS middleware works by adding the necessary CORS headers to responses from your Fiber application. These headers tell browsers what origins, methods, and headers are allowed for cross-origin requests. + +When a request comes in, the middleware first checks if it's a preflight request, which is a CORS mechanism to determine whether the actual request is safe to send. Preflight requests are HTTP OPTIONS requests with specific CORS headers. If it's a preflight request, the middleware responds with the appropriate CORS headers and ends the request. + +If it's not a preflight request, the middleware adds the CORS headers to the response and passes the request to the next handler. The actual CORS headers added depend on the configuration of the middleware. + +The `AllowOrigins` option controls which origins can make cross-origin requests. The middleware handles different `AllowOrigins` configurations as follows: + +- **Single origin:** If `AllowOrigins` is set to a single origin like `"http://www.example.com"`, and that origin matches the origin of the incoming request, the middleware adds the header `Access-Control-Allow-Origin: http://www.example.com` to the response. + +- **Multiple origins:** If `AllowOrigins` is set to multiple origins like `"https://example.com, https://www.example.com"`, the middleware picks the origin that matches the origin of the incoming request. + +- **Subdomain wildcard:** If `AllowOrigins` contains `"https://.example.com"`, a subdomain like `www` would be matched and `"https://www.example.com"` would be the header. + +- **Wildcard origin:** If `AllowOrigins` is set to `"*"`, the middleware uses that and adds the header `Access-Control-Allow-Origin: *` to the response. + +In all cases above, except the **Wildcard origin**, the middleware will either add the `Access-Control-Allow-Origin` header to the response matching the origin of the incoming request, or it will not add the header at all if the origin is not allowed. + +The `AllowMethods` option controls which HTTP methods are allowed. For example, if `AllowMethods` is set to `"GET, POST"`, the middleware adds the header `Access-Control-Allow-Methods: GET, POST` to the response. + +The middleware also handles the `AllowOriginsFunc` option, which allows you to programmatically determine if an origin is allowed. If `AllowOriginsFunc` returns `true` for an origin, the middleware sets the `Access-Control-Allow-Origin` header to that origin. + +This way, the CORS middleware allows you to control how your Fiber application responds to cross-origin requests. + +## Security Considerations + +When configuring CORS, misconfiguration can potentially expose your application to various security risks. + +- **Allowing all origins:** Setting `Access-Control-Allow-Origin` to `*` (a wildcard) allows any domain to make cross-origin requests. This can expose your application to cross-site request forgery (CSRF) attacks. It's generally safer to specify the exact domains that are allowed to make requests. + +- **Allowing credentials:** The `Access-Control-Allow-Credentials` header indicates whether the browser should include credentials with cross-origin requests. If this is set to `true`, it can expose your application to attacks if combined with a wildcard `Access-Control-Allow-Origin`. We specifically prohibit this action in our CORS middleware, in line with the Fetch specification. + +- **Exposing headers:** The `Access-Control-Expose-Headers` header lets the server whitelist headers that browsers are allowed to access. Be careful not to expose sensitive headers. + +:::note +In our CORS middleware, we specifically prevent `Access-Control-Allow-Credentials` from being `true` when `Access-Control-Allow-Origin` is set to the wildcard (`*`). This is to prevent potential security risks associated with allowing credentials to be shared with all origins. + +When using `AllowOrigins`, a configuration check will cause a panic if `Access-Control-Allow-Credentials` is `true` and `Access-Control-Allow-Origin` is set to the wildcard. +::: + +:::caution +Be extra careful when using `AllowOriginsFunc`. Make sure to properly validate the origin to prevent potential security risks. + +When using `AllowOriginsFunc`, the `Access-Control-Allow-Origin` header will always be set to the origin header if the func returns `true`, which can bypass such protections if you simply return `true` in all situations. +::: \ No newline at end of file diff --git a/middleware/cors/cors.go b/middleware/cors/cors.go index 3accc2f1dd..074caa077a 100644 --- a/middleware/cors/cors.go +++ b/middleware/cors/cors.go @@ -195,7 +195,6 @@ func New(config ...Config) fiber.Handler { c.Vary(fiber.HeaderOrigin) c.Vary(fiber.HeaderAccessControlRequestMethod) c.Vary(fiber.HeaderAccessControlRequestHeaders) - c.Set(fiber.HeaderAccessControlAllowOrigin, allowOrigin) c.Set(fiber.HeaderAccessControlAllowMethods, allowMethods) if cfg.AllowCredentials { @@ -204,9 +203,10 @@ func New(config ...Config) fiber.Handler { c.Set(fiber.HeaderAccessControlAllowOrigin, allowOrigin) c.Set(fiber.HeaderAccessControlAllowCredentials, "true") } else if allowOrigin == "*" { + c.Set(fiber.HeaderAccessControlAllowOrigin, allowOrigin) log.Warn("[CORS] 'AllowCredentials' is true, but 'AllowOrigins' cannot be set to '*'.") } - } else { + } else if len(allowOrigin) > 0 { // For non-credential requests, it's safe to set to '*' or specific origins c.Set(fiber.HeaderAccessControlAllowOrigin, allowOrigin) } diff --git a/middleware/cors/cors_test.go b/middleware/cors/cors_test.go index 23692c3f84..6b9e6d63f6 100644 --- a/middleware/cors/cors_test.go +++ b/middleware/cors/cors_test.go @@ -195,13 +195,37 @@ func Test_CORS_Invalid_Origin_Panic(t *testing.T) { } } +func Test_CORS_Invalid_Origin_URL_Panic(t *testing.T) { + t.Parallel() + // New fiber instance + app := fiber.New() + + didPanic := false + func() { + defer func() { + if r := recover(); r != nil { + didPanic = true + } + }() + + app.Use(New(Config{ + AllowOrigins: "http://foo.[a-z]*.example.com", + AllowCredentials: true, + })) + }() + + if !didPanic { + t.Errorf("Expected a panic when Origin is missing scheme") + } +} + // go test -run -v Test_CORS_Subdomain func Test_CORS_Subdomain(t *testing.T) { t.Parallel() // New fiber instance app := fiber.New() // OPTIONS (preflight) response headers when AllowOrigins is set to a subdomain - app.Use("/", New(Config{AllowOrigins: "http://*.example.com"})) + app.Use("/", New(Config{AllowOrigins: "http://.example.com"})) // Get handler pointer handler := app.Handler() @@ -215,7 +239,7 @@ func Test_CORS_Subdomain(t *testing.T) { // Perform request handler(ctx) - // Allow-Origin header should be "" because http://google.com does not satisfy http://*.example.com + // Allow-Origin header should be "" because http://google.com does not satisfy http://.example.com utils.AssertEqual(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin))) ctx.Request.Reset() @@ -253,22 +277,22 @@ func Test_CORS_AllowOriginScheme(t *testing.T) { shouldAllowOrigin: false, }, { - pattern: "http://*.example.com", + pattern: "http://.example.com", reqOrigin: "http://aaa.example.com", shouldAllowOrigin: true, }, { - pattern: "http://*.example.com", + pattern: "http://.example.com", reqOrigin: "http://bbb.aaa.example.com", shouldAllowOrigin: true, }, { - pattern: "http://*.aaa.example.com", + pattern: "http://.aaa.example.com", reqOrigin: "http://bbb.aaa.example.com", shouldAllowOrigin: true, }, { - pattern: "http://*.example.com:8080", + pattern: "http://.example.com:8080", reqOrigin: "http://aaa.example.com:8080", shouldAllowOrigin: true, }, @@ -278,12 +302,12 @@ func Test_CORS_AllowOriginScheme(t *testing.T) { shouldAllowOrigin: false, }, { - pattern: "http://*.aaa.example.com", + pattern: "http://.aaa.example.com", reqOrigin: "http://ccc.bbb.example.com", shouldAllowOrigin: false, }, { - pattern: "http://*.example.com", + pattern: "http://.example.com", reqOrigin: "http://1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.example.com", shouldAllowOrigin: true, }, @@ -293,20 +317,15 @@ func Test_CORS_AllowOriginScheme(t *testing.T) { shouldAllowOrigin: false, }, { - pattern: "https://*--aaa.bbb.com", + pattern: "https://--aaa.bbb.com", reqOrigin: "https://prod-preview--aaa.bbb.com", shouldAllowOrigin: false, }, { - pattern: "http://*.example.com", + pattern: "http://.example.com", reqOrigin: "http://ccc.bbb.example.com", shouldAllowOrigin: true, }, - { - pattern: "http://foo.[a-z]*.example.com", - reqOrigin: "http://ccc.bbb.example.com", - shouldAllowOrigin: false, - }, { pattern: "http://domain-1.com, http://example.com", reqOrigin: "http://example.com", @@ -345,6 +364,35 @@ func Test_CORS_AllowOriginScheme(t *testing.T) { } } +func Test_CORS_AllowOriginHeader_NoMatch(t *testing.T) { + t.Parallel() + // New fiber instance + app := fiber.New() + app.Use("/", New(Config{ + AllowOrigins: "http://example-1.com, https://example-1.com", + })) + + // Get handler pointer + handler := app.Handler() + + // Make request with disallowed origin + ctx := &fasthttp.RequestCtx{} + ctx.Request.SetRequestURI("/") + ctx.Request.Header.SetMethod(fiber.MethodOptions) + ctx.Request.Header.Set(fiber.HeaderOrigin, "http://google.com") + + // Perform request + handler(ctx) + + var headerExists bool + ctx.Response.Header.VisitAll(func(key, _ []byte) { + if string(key) == fiber.HeaderAccessControlAllowOrigin { + headerExists = true + } + }) + utils.AssertEqual(t, false, headerExists, "Access-Control-Allow-Origin header should not be set") +} + // go test -run Test_CORS_Next func Test_CORS_Next(t *testing.T) { t.Parallel() diff --git a/middleware/cors/utils.go b/middleware/cors/utils.go index d1280899c9..fddbf53405 100644 --- a/middleware/cors/utils.go +++ b/middleware/cors/utils.go @@ -24,19 +24,13 @@ func validateDomain(domain, pattern string) bool { normalizedPattern := normalizeDomain(pattern) // Handling the case where pattern is a wildcard subdomain pattern. - if strings.HasPrefix(normalizedPattern, "*.") { - // Trim leading "*." from pattern for comparison. - trimmedPattern := normalizedPattern[2:] + if strings.HasPrefix(normalizedPattern, ".") { + // Trim leading "." from pattern for comparison. + trimmedPattern := normalizedPattern[1:] - // Check if the domain ends with the trimmed pattern. - if strings.HasSuffix(normalizedDomain, trimmedPattern) { - // Ensure that the domain is not exactly the base domain. - if normalizedDomain != trimmedPattern { - // Special handling to prevent "example.com" matching "*.example.com". - if strings.TrimSuffix(normalizedDomain, trimmedPattern) != "" { - return true - } - } + // Check if the domain ends with a dot followed by the trimmed pattern. + if strings.HasSuffix(normalizedDomain, "."+trimmedPattern) { + return true } } @@ -73,6 +67,13 @@ func normalizeOrigin(origin string) (bool, string) { return false, "" } + // Don't allow a wildcard with a protocol + // wildcards cannot be used within any other value. For example, the following header is not valid: + // Access-Control-Allow-Origin: https://*.normal-website.com + if strings.Contains(parsedOrigin.Host, "*") { + return false, "" + } + // Validate there is a host present. The presence of a path, query, or fragment components // is checked, but a trailing "/" (indicative of the root) is allowed for the path and will be normalized if parsedOrigin.Host == "" || (parsedOrigin.Path != "" && parsedOrigin.Path != "/") || parsedOrigin.RawQuery != "" || parsedOrigin.Fragment != "" { diff --git a/middleware/cors/utils_test.go b/middleware/cors/utils_test.go index 3acd692521..972c1be4d4 100644 --- a/middleware/cors/utils_test.go +++ b/middleware/cors/utils_test.go @@ -91,13 +91,14 @@ func Test_validateOrigin(t *testing.T) { {"http://example.com:8080", "http://example.com:8081", false}, // Different ports should not match. {"example.com", "example.com", true}, // Simplified form, assuming scheme and port are not considered here, but in practice, they are part of the origin. {"sub.example.com", "example.com", false}, // Subdomain should not match the base domain directly. - {"sub.example.com", "*.example.com", true}, // Correct assumption for wildcard subdomain matching. - {"example.com", "*.example.com", false}, // Base domain should not match its wildcard subdomain pattern. - {"sub.example.com", "*.com", true}, // Technically correct for pattern matching, but broad wildcard use like this is not recommended for CORS. - {"sub.sub.example.com", "*.example.com", true}, // Nested subdomain should match the wildcard pattern. - {"example.com", "*.org", false}, // Different TLDs should not match. + {"sub.example.com", ".example.com", true}, // Correct assumption for wildcard subdomain matching. + {"evilexample.com", ".example.com", false}, // Base domain should not match its wildcard subdomain pattern. + {"example.com", ".example.com", false}, // Base domain should not match its wildcard subdomain pattern. + {"sub.example.com", ".com", true}, // Technically correct for pattern matching, but broad wildcard use like this is not recommended for CORS. + {"sub.sub.example.com", ".example.com", true}, // Nested subdomain should match the wildcard pattern. + {"example.com", ".org", false}, // Different TLDs should not match. {"example.com", "example.org", false}, // Different domains should not match. - {"example.com:8080", "*.example.com", false}, // Different ports mean different origins. + {"example.com:8080", ".example.com", false}, // Different ports mean different origins. {"example.com", "sub.example.net", false}, // Different domains should not match. {"http://localhost", "http://localhost", true}, // Localhost should match. {"http://127.0.0.1", "http://127.0.0.1", true}, // IPv4 address should match. From af930bd0b244efd4aa107e86c85a8a5a5c3e4480 Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Mon, 11 Mar 2024 13:31:24 -0300 Subject: [PATCH 10/32] chore: grammar --- docs/api/middleware/cors.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index 3e1f67e417..f55df6a909 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -132,14 +132,14 @@ This way, the CORS middleware allows you to control how your Fiber application r When configuring CORS, misconfiguration can potentially expose your application to various security risks. -- **Allowing all origins:** Setting `Access-Control-Allow-Origin` to `*` (a wildcard) allows any domain to make cross-origin requests. This can expose your application to cross-site request forgery (CSRF) attacks. It's generally safer to specify the exact domains that are allowed to make requests. +- **Allowing all origins:** Setting `Access-Control-Allow-Origin` to `*` (a wildcard) allows any domain to make cross-origin requests. This can expose your application to cross-site request forgery (CSRF) attacks. It's generally safer to specify the exact domains allowed to make requests. - **Allowing credentials:** The `Access-Control-Allow-Credentials` header indicates whether the browser should include credentials with cross-origin requests. If this is set to `true`, it can expose your application to attacks if combined with a wildcard `Access-Control-Allow-Origin`. We specifically prohibit this action in our CORS middleware, in line with the Fetch specification. - **Exposing headers:** The `Access-Control-Expose-Headers` header lets the server whitelist headers that browsers are allowed to access. Be careful not to expose sensitive headers. :::note -In our CORS middleware, we specifically prevent `Access-Control-Allow-Credentials` from being `true` when `Access-Control-Allow-Origin` is set to the wildcard (`*`). This is to prevent potential security risks associated with allowing credentials to be shared with all origins. +In our CORS middleware, we specifically prevent `Access-Control-Allow-Credentials` from being `true` when `Access-Control-Allow-Origin` is set to the wildcard (`*`). This prevents potential security risks associated with allowing credentials to be shared with all origins. When using `AllowOrigins`, a configuration check will cause a panic if `Access-Control-Allow-Credentials` is `true` and `Access-Control-Allow-Origin` is set to the wildcard. ::: @@ -147,5 +147,5 @@ When using `AllowOrigins`, a configuration check will cause a panic if `Access-C :::caution Be extra careful when using `AllowOriginsFunc`. Make sure to properly validate the origin to prevent potential security risks. -When using `AllowOriginsFunc`, the `Access-Control-Allow-Origin` header will always be set to the origin header if the func returns `true`, which can bypass such protections if you simply return `true` in all situations. +When using `AllowOriginsFunc`, the `Access-Control-Allow-Origin` header will always be set to the origin header if the func returns `true`, which can bypass such protections if you return `true` in all situations. ::: \ No newline at end of file From 03e09c9884f3cd3bf324602f33d5e62c688271cb Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Mon, 11 Mar 2024 13:40:38 -0300 Subject: [PATCH 11/32] Apply suggestions from code review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- middleware/cors/cors_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middleware/cors/cors_test.go b/middleware/cors/cors_test.go index 6b9e6d63f6..d8a225c096 100644 --- a/middleware/cors/cors_test.go +++ b/middleware/cors/cors_test.go @@ -215,7 +215,7 @@ func Test_CORS_Invalid_Origin_URL_Panic(t *testing.T) { }() if !didPanic { - t.Errorf("Expected a panic when Origin is missing scheme") + t.Errorf("Expected a panic due to invalid origin URL pattern") } } From 6070ea33309de60b01cc9fba9476cb958936890a Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Mon, 11 Mar 2024 13:41:50 -0300 Subject: [PATCH 12/32] chore: fix misspelling --- docs/api/middleware/cors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index f55df6a909..b8bebf42d0 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -8,7 +8,7 @@ CORS middleware for [Fiber](https://github.com/gofiber/fiber) that can be used t The middleware conforms to the `access-control-allow-origin` specification by parsing `AllowOrigins`. First, the middleware checks if there is a matching allowed origin for the requesting 'origin' header. If there is a match, it returns exactly one matching domain from the list of allowed origins. -For more control, `AllowOriginsFunc` can be used to programatically determine if an origin is allowed. If no match was found in `AllowOrigins` and if `AllowOriginsFunc` returns true then the 'access-control-allow-origin' response header is set to the 'origin' request header. +For more control, `AllowOriginsFunc` can be used to programmatically determine if an origin is allowed. If no match was found in `AllowOrigins` and if `AllowOriginsFunc` returns true then the 'access-control-allow-origin' response header is set to the 'origin' request header. When defining your Origins make sure they are properly formatted. The middleware validates and normalizes the provided origins, ensuring they're in the correct format by checking for valid schemes (http or https), and removing any trailing slashes. From 6a482d276bd5797367294113f0a5492620b770ee Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Mon, 11 Mar 2024 13:48:47 -0300 Subject: [PATCH 13/32] test(middleware/cors): combine Invalid_Origins tests --- middleware/cors/cors_test.go | 64 +++++++++++++++--------------------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/middleware/cors/cors_test.go b/middleware/cors/cors_test.go index d8a225c096..3176e880cb 100644 --- a/middleware/cors/cors_test.go +++ b/middleware/cors/cors_test.go @@ -171,51 +171,39 @@ func Test_CORS_Wildcard_AllowCredentials_Panic(t *testing.T) { } // go test -run -v Test_CORS_Invalid_Origin_Panic -func Test_CORS_Invalid_Origin_Panic(t *testing.T) { +func Test_CORS_Invalid_Origins_Panic(t *testing.T) { t.Parallel() - // New fiber instance - app := fiber.New() - - didPanic := false - func() { - defer func() { - if r := recover(); r != nil { - didPanic = true - } - }() - app.Use(New(Config{ - AllowOrigins: "localhost", - AllowCredentials: true, - })) - }() - - if !didPanic { - t.Errorf("Expected a panic when Origin is missing scheme") + invalidOrigins := []string{ + "localhost", + "http://foo.[a-z]*.example.com", + "http://*", + "https://*", + "invalid url", + // add more invalid origins as needed } -} -func Test_CORS_Invalid_Origin_URL_Panic(t *testing.T) { - t.Parallel() - // New fiber instance - app := fiber.New() + for _, origin := range invalidOrigins { + // New fiber instance + app := fiber.New() - didPanic := false - func() { - defer func() { - if r := recover(); r != nil { - didPanic = true - } - }() + didPanic := false + func() { + defer func() { + if r := recover(); r != nil { + didPanic = true + } + }() - app.Use(New(Config{ - AllowOrigins: "http://foo.[a-z]*.example.com", - AllowCredentials: true, - })) - }() + app.Use(New(Config{ + AllowOrigins: origin, + AllowCredentials: true, + })) + }() - if !didPanic { - t.Errorf("Expected a panic due to invalid origin URL pattern") + if !didPanic { + t.Errorf("Expected a panic for invalid origin: %s", origin) + } } } From b7929513fd4dc55a12d966780580b81d91380343 Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Mon, 11 Mar 2024 14:26:22 -0300 Subject: [PATCH 14/32] refactor(middleware/cors): headers handling --- middleware/cors/cors.go | 83 +++++++++++++++++++----------------- middleware/cors/cors_test.go | 1 + 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/middleware/cors/cors.go b/middleware/cors/cors.go index 074caa077a..c00cab6025 100644 --- a/middleware/cors/cors.go +++ b/middleware/cors/cors.go @@ -179,56 +179,63 @@ func New(config ...Config) fiber.Handler { // Simple request if c.Method() != fiber.MethodOptions { - c.Vary(fiber.HeaderOrigin) - c.Set(fiber.HeaderAccessControlAllowOrigin, allowOrigin) - - if cfg.AllowCredentials { - c.Set(fiber.HeaderAccessControlAllowCredentials, "true") - } - if exposeHeaders != "" { - c.Set(fiber.HeaderAccessControlExposeHeaders, exposeHeaders) - } + setCORSHeaders(c, allowOrigin, allowMethods, allowHeaders, exposeHeaders, maxAge, cfg) return c.Next() } // Preflight request - c.Vary(fiber.HeaderOrigin) c.Vary(fiber.HeaderAccessControlRequestMethod) c.Vary(fiber.HeaderAccessControlRequestHeaders) - c.Set(fiber.HeaderAccessControlAllowMethods, allowMethods) - if cfg.AllowCredentials { - // When AllowCredentials is true, set the Access-Control-Allow-Origin to the specific origin instead of '*' - if allowOrigin != "*" && allowOrigin != "" { - c.Set(fiber.HeaderAccessControlAllowOrigin, allowOrigin) - c.Set(fiber.HeaderAccessControlAllowCredentials, "true") - } else if allowOrigin == "*" { - c.Set(fiber.HeaderAccessControlAllowOrigin, allowOrigin) - log.Warn("[CORS] 'AllowCredentials' is true, but 'AllowOrigins' cannot be set to '*'.") - } - } else if len(allowOrigin) > 0 { - // For non-credential requests, it's safe to set to '*' or specific origins + setCORSHeaders(c, allowOrigin, allowMethods, allowHeaders, exposeHeaders, maxAge, cfg) + + // Send 204 No Content + return c.SendStatus(fiber.StatusNoContent) + } +} + +// Function to set CORS headers +func setCORSHeaders(c *fiber.Ctx, allowOrigin, allowMethods, allowHeaders, exposeHeaders, maxAge string, cfg Config) { + c.Vary(fiber.HeaderOrigin) + + if cfg.AllowCredentials { + // When AllowCredentials is true, set the Access-Control-Allow-Origin to the specific origin instead of '*' + if allowOrigin != "*" && allowOrigin != "" { c.Set(fiber.HeaderAccessControlAllowOrigin, allowOrigin) + c.Set(fiber.HeaderAccessControlAllowCredentials, "true") + } else if allowOrigin == "*" { + c.Set(fiber.HeaderAccessControlAllowOrigin, allowOrigin) + log.Warn("[CORS] 'AllowCredentials' is true, but 'AllowOrigins' cannot be set to '*'.") } + } else if len(allowOrigin) > 0 { + // For non-credential requests, it's safe to set to '*' or specific origins + c.Set(fiber.HeaderAccessControlAllowOrigin, allowOrigin) + } - // Set Allow-Headers if not empty - if allowHeaders != "" { - c.Set(fiber.HeaderAccessControlAllowHeaders, allowHeaders) - } else { - h := c.Get(fiber.HeaderAccessControlRequestHeaders) - if h != "" { - c.Set(fiber.HeaderAccessControlAllowHeaders, h) - } - } + // Set Allow-Methods if not empty + if allowMethods != "" { + c.Set(fiber.HeaderAccessControlAllowMethods, allowMethods) + } - // Set MaxAge is set - if cfg.MaxAge > 0 { - c.Set(fiber.HeaderAccessControlMaxAge, maxAge) - } else if cfg.MaxAge < 0 { - c.Set(fiber.HeaderAccessControlMaxAge, "0") + // Set Allow-Headers if not empty + if allowHeaders != "" { + c.Set(fiber.HeaderAccessControlAllowHeaders, allowHeaders) + } else { + h := c.Get(fiber.HeaderAccessControlRequestHeaders) + if h != "" { + c.Set(fiber.HeaderAccessControlAllowHeaders, h) } + } - // Send 204 No Content - return c.SendStatus(fiber.StatusNoContent) + // Set MaxAge if set + if cfg.MaxAge > 0 { + c.Set(fiber.HeaderAccessControlMaxAge, maxAge) + } else if cfg.MaxAge < 0 { + c.Set(fiber.HeaderAccessControlMaxAge, "0") + } + + // Set Expose-Headers if not empty + if exposeHeaders != "" { + c.Set(fiber.HeaderAccessControlExposeHeaders, exposeHeaders) } } diff --git a/middleware/cors/cors_test.go b/middleware/cors/cors_test.go index 3176e880cb..0d1b428c81 100644 --- a/middleware/cors/cors_test.go +++ b/middleware/cors/cors_test.go @@ -137,6 +137,7 @@ func Test_CORS_Origin_AllowCredentials(t *testing.T) { // Test non OPTIONS (preflight) response headers ctx = &fasthttp.RequestCtx{} + ctx.Request.Header.Set(fiber.HeaderOrigin, "http://localhost") ctx.Request.Header.SetMethod(fiber.MethodGet) handler(ctx) From 5f53a50a4aa02c977f1a4446585e97cb829463d8 Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Tue, 12 Mar 2024 11:50:50 -0300 Subject: [PATCH 15/32] docs(middleware/cors): Update AllowOrigins description --- docs/api/middleware/cors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index b8bebf42d0..ea0c02cbbf 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -73,7 +73,7 @@ app.Use(cors.New(cors.Config{ |:-----------------|:---------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------| | Next | `func(*fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` | | AllowOriginsFunc | `func(origin string) bool` | AllowOriginsFunc defines a function that will set the 'access-control-allow-origin' response header to the 'origin' request header when returned true. This allows for dynamic evaluation of allowed origins. Note if AllowCredentials is true, wildcard origins will be not have the 'access-control-allow-credentials' header set to 'true'. | `nil` | -| AllowOrigins | `string` | AllowOrigin defines a comma separated list of origins that may access the resource. This supports subdomain matching, so you can use a value like "https://.example.com" to allow any subdomain of example.com to submit requests. | `"*"` | +| AllowOrigins | `string` | AllowOrigins defines a comma separated list of origins that may access the resource. This supports subdomain matching, so you can use a value like "https://.example.com" to allow any subdomain of example.com to submit requests. | `"*"` | | AllowMethods | `string` | AllowMethods defines a list of methods allowed when accessing the resource. This is used in response to a preflight request. | `"GET,POST,HEAD,PUT,DELETE,PATCH"` | | AllowHeaders | `string` | AllowHeaders defines a list of request headers that can be used when making the actual request. This is in response to a preflight request. | `""` | | AllowCredentials | `bool` | AllowCredentials indicates whether or not the response to the request can be exposed when the credentials flag is true. When used as part of a response to a preflight request, this indicates whether or not the actual request can be made using credentials. Note: If true, AllowOrigins cannot be set to a wildcard ("*") to prevent security vulnerabilities. | `false` | From 3bc500de68f3acf067f8b7d3ae376e08fee89004 Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Thu, 14 Mar 2024 13:40:43 -0300 Subject: [PATCH 16/32] chore: merge --- middleware/cors/cors_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/middleware/cors/cors_test.go b/middleware/cors/cors_test.go index 35483a14cf..f14468d675 100644 --- a/middleware/cors/cors_test.go +++ b/middleware/cors/cors_test.go @@ -326,7 +326,6 @@ func Test_CORS_AllowOriginScheme(t *testing.T) { shouldAllowOrigin: false, }, { -<<<<<<< HEAD pattern: "http://domain-1.com, http://example.com", reqOrigin: "http://example.com", shouldAllowOrigin: true, @@ -337,8 +336,6 @@ func Test_CORS_AllowOriginScheme(t *testing.T) { shouldAllowOrigin: false, }, { -======= ->>>>>>> upstream/fix-cors-allow-origins pattern: "http://domain-1.com,http://example.com", reqOrigin: "http://domain-1.com", shouldAllowOrigin: true, From 46b8d02e77d9b98d1aedc80db05db9f00d82abe9 Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Thu, 14 Mar 2024 14:04:29 -0300 Subject: [PATCH 17/32] perf(middleware/cors): optimize handler --- middleware/cors/cors.go | 62 +++++++++++++++++++++++++---------- middleware/cors/cors_test.go | 3 ++ middleware/cors/utils.go | 35 ++++++-------------- middleware/cors/utils_test.go | 39 ---------------------- 4 files changed, 57 insertions(+), 82 deletions(-) diff --git a/middleware/cors/cors.go b/middleware/cors/cors.go index c00cab6025..929c8e1c04 100644 --- a/middleware/cors/cors.go +++ b/middleware/cors/cors.go @@ -115,28 +115,31 @@ func New(config ...Config) fiber.Handler { // allowOrigins is a slice of strings that contains the allowed origins // defined in the 'AllowOrigins' configuration. - var allowOrigins []string + allowOrigins := []string{} + allowSOrigins := []subdomain{} + allowAllOrigins := false // Validate and normalize static AllowOrigins if cfg.AllowOrigins != "" && cfg.AllowOrigins != "*" { origins := strings.Split(cfg.AllowOrigins, ",") - allowOrigins = make([]string, len(origins)) - for i, origin := range origins { + for _, origin := range origins { trimmedOrigin := strings.TrimSpace(origin) isValid, normalizedOrigin := normalizeOrigin(trimmedOrigin) - if isValid { - allowOrigins[i] = normalizedOrigin - } else { + if !isValid { log.Warnf("[CORS] Invalid origin format in configuration: %s", trimmedOrigin) panic("[CORS] Invalid origin provided in configuration") } + + if i := strings.Index(normalizedOrigin, "://."); i != -1 { + allowSOrigins = append(allowSOrigins, subdomain{prefix: normalizedOrigin[:i+3], suffix: normalizedOrigin[i+3:]}) + } else { + allowOrigins = append(allowOrigins, normalizedOrigin) + } } - } else { - // If AllowOrigins is set to a wildcard or not set, - // set allowOrigins to a slice with a single element - allowOrigins = []string{cfg.AllowOrigins} + } else if cfg.AllowOrigins == "*" { + allowAllOrigins = true } // Strip white spaces @@ -155,18 +158,41 @@ func New(config ...Config) fiber.Handler { } // Get originHeader header - originHeader := c.Get(fiber.HeaderOrigin) + originHeader := strings.ToLower(c.Get(fiber.HeaderOrigin)) + + // If the request does not have an Origin header, the request is outside the scope of CORS + if originHeader == "" { + return c.Next() + } + + // Set default allowOrigin to empty string allowOrigin := "" // Check allowed origins - for _, origin := range allowOrigins { - if origin == "*" { - allowOrigin = "*" - break + if allowAllOrigins { + allowOrigin = "*" + } else { + // Check if the origin is in the list of allowed origins + for _, origin := range allowOrigins { + if origin == "*" { + allowOrigin = "*" + break + } + + if origin == originHeader { + allowOrigin = originHeader + break + } } - if validateDomain(originHeader, origin) { - allowOrigin = originHeader - break + + // Check if the origin is in the list of allowed subdomains + if allowOrigin == "" { + for _, sOrigin := range allowSOrigins { + if sOrigin.match(originHeader) { + allowOrigin = originHeader + break + } + } } } diff --git a/middleware/cors/cors_test.go b/middleware/cors/cors_test.go index f14468d675..c9b7bd9136 100644 --- a/middleware/cors/cors_test.go +++ b/middleware/cors/cors_test.go @@ -49,6 +49,7 @@ func testDefaultOrEmptyConfig(t *testing.T, app *fiber.App) { // Test default GET response headers ctx := &fasthttp.RequestCtx{} ctx.Request.Header.SetMethod(fiber.MethodGet) + ctx.Request.Header.Set(fiber.HeaderOrigin, "http://localhost") h(ctx) utils.AssertEqual(t, "*", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin))) @@ -58,6 +59,7 @@ func testDefaultOrEmptyConfig(t *testing.T, app *fiber.App) { // Test default OPTIONS (preflight) response headers ctx = &fasthttp.RequestCtx{} ctx.Request.Header.SetMethod(fiber.MethodOptions) + ctx.Request.Header.Set(fiber.HeaderOrigin, "http://localhost") h(ctx) utils.AssertEqual(t, "GET,POST,HEAD,PUT,DELETE,PATCH", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowMethods))) @@ -98,6 +100,7 @@ func Test_CORS_Wildcard(t *testing.T) { // Test non OPTIONS (preflight) response headers ctx = &fasthttp.RequestCtx{} ctx.Request.Header.SetMethod(fiber.MethodGet) + ctx.Request.Header.Set(fiber.HeaderOrigin, "http://localhost") handler(ctx) utils.AssertEqual(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowCredentials))) diff --git a/middleware/cors/utils.go b/middleware/cors/utils.go index fddbf53405..9fca1df197 100644 --- a/middleware/cors/utils.go +++ b/middleware/cors/utils.go @@ -12,31 +12,6 @@ func matchScheme(domain, pattern string) bool { return didx != -1 && pidx != -1 && domain[:didx] == pattern[:pidx] } -// validateDomain checks if the domain matches the pattern -func validateDomain(domain, pattern string) bool { - // Directly compare the domain and pattern for an exact match. - if domain == pattern { - return true - } - - // Normalize domain and pattern to exclude schemes and ports for matching purposes - normalizedDomain := normalizeDomain(domain) - normalizedPattern := normalizeDomain(pattern) - - // Handling the case where pattern is a wildcard subdomain pattern. - if strings.HasPrefix(normalizedPattern, ".") { - // Trim leading "." from pattern for comparison. - trimmedPattern := normalizedPattern[1:] - - // Check if the domain ends with a dot followed by the trimmed pattern. - if strings.HasSuffix(normalizedDomain, "."+trimmedPattern) { - return true - } - } - - return false -} - // normalizeDomain removes the scheme and port from the input domain func normalizeDomain(input string) string { // Remove scheme @@ -84,3 +59,13 @@ func normalizeOrigin(origin string) (bool, string) { // The path or trailing slash is not included in the normalized origin. return true, strings.ToLower(parsedOrigin.Scheme) + "://" + strings.ToLower(parsedOrigin.Host) } + +type subdomain struct { + // The wildcard pattern + prefix string + suffix string +} + +func (s subdomain) match(o string) bool { + return len(o) >= len(s.prefix)+len(s.suffix) && strings.HasPrefix(o, s.prefix) && strings.HasSuffix(o, s.suffix) +} diff --git a/middleware/cors/utils_test.go b/middleware/cors/utils_test.go index 972c1be4d4..adc729d05f 100644 --- a/middleware/cors/utils_test.go +++ b/middleware/cors/utils_test.go @@ -75,45 +75,6 @@ func Test_matchScheme(t *testing.T) { } } -// go test -run -v Test_validateOrigin -func Test_validateOrigin(t *testing.T) { - testCases := []struct { - domain string - pattern string - expected bool - }{ - {"http://example.com", "http://example.com", true}, // Exact match should work. - {"https://example.com", "http://example.com", false}, // Scheme mismatch should matter in CORS context. - {"http://example.com", "https://example.com", false}, // Scheme mismatch should matter in CORS context. - {"http://example.com", "http://example.org", false}, // Different domains should not match. - {"http://example.com", "http://example.com:8080", false}, // Port mismatch should matter. - {"http://example.com:8080", "http://example.com", false}, // Port mismatch should matter. - {"http://example.com:8080", "http://example.com:8081", false}, // Different ports should not match. - {"example.com", "example.com", true}, // Simplified form, assuming scheme and port are not considered here, but in practice, they are part of the origin. - {"sub.example.com", "example.com", false}, // Subdomain should not match the base domain directly. - {"sub.example.com", ".example.com", true}, // Correct assumption for wildcard subdomain matching. - {"evilexample.com", ".example.com", false}, // Base domain should not match its wildcard subdomain pattern. - {"example.com", ".example.com", false}, // Base domain should not match its wildcard subdomain pattern. - {"sub.example.com", ".com", true}, // Technically correct for pattern matching, but broad wildcard use like this is not recommended for CORS. - {"sub.sub.example.com", ".example.com", true}, // Nested subdomain should match the wildcard pattern. - {"example.com", ".org", false}, // Different TLDs should not match. - {"example.com", "example.org", false}, // Different domains should not match. - {"example.com:8080", ".example.com", false}, // Different ports mean different origins. - {"example.com", "sub.example.net", false}, // Different domains should not match. - {"http://localhost", "http://localhost", true}, // Localhost should match. - {"http://127.0.0.1", "http://127.0.0.1", true}, // IPv4 address should match. - {"http://[::1]", "http://[::1]", true}, // IPv6 address should match. - } - - for _, tc := range testCases { - result := validateDomain(tc.domain, tc.pattern) - - if result != tc.expected { - t.Errorf("Expected validateOrigin('%s', '%s') to be %v, but got %v", tc.domain, tc.pattern, tc.expected, result) - } - } -} - // go test -run -v Test_normalizeDomain func Test_normalizeDomain(t *testing.T) { testCases := []struct { From 742b9154bd8fcdeed1460b1315c88b7ecdd056cd Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Thu, 14 Mar 2024 14:25:45 -0300 Subject: [PATCH 18/32] perf(middleware/cors): optimize handler --- middleware/cors/cors.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/middleware/cors/cors.go b/middleware/cors/cors.go index 929c8e1c04..e935c0903f 100644 --- a/middleware/cors/cors.go +++ b/middleware/cors/cors.go @@ -174,11 +174,6 @@ func New(config ...Config) fiber.Handler { } else { // Check if the origin is in the list of allowed origins for _, origin := range allowOrigins { - if origin == "*" { - allowOrigin = "*" - break - } - if origin == originHeader { allowOrigin = originHeader break From f1678ae8c8c2bc599b4f5107d474aa6d2f83bcac Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Fri, 15 Mar 2024 16:19:39 -0300 Subject: [PATCH 19/32] chore(middleware/cors): ipdate origin handling logic --- docs/api/middleware/cors.md | 50 ++++++++++++++++++++++-------------- middleware/cors/cors.go | 38 +++++++++++++++++---------- middleware/cors/cors_test.go | 30 +++++++++++++++------- middleware/cors/utils.go | 2 +- 4 files changed, 78 insertions(+), 42 deletions(-) diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index ea0c02cbbf..e59c64906e 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -6,11 +6,9 @@ id: cors CORS middleware for [Fiber](https://github.com/gofiber/fiber) that can be used to enable [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) with various options. -The middleware conforms to the `access-control-allow-origin` specification by parsing `AllowOrigins`. First, the middleware checks if there is a matching allowed origin for the requesting 'origin' header. If there is a match, it returns exactly one matching domain from the list of allowed origins. +The middleware conforms to the `Access-Control-Allow-Origin` specification by parsing `AllowOrigins`. First, the middleware checks if there is a matching allowed origin for the requesting 'origin' header. If there is a match, it returns exactly one matching domain from the list of allowed origins. If there is no match, the middleware does not add the `Access-Control-Allow-Origin` header to the response. -For more control, `AllowOriginsFunc` can be used to programmatically determine if an origin is allowed. If no match was found in `AllowOrigins` and if `AllowOriginsFunc` returns true then the 'access-control-allow-origin' response header is set to the 'origin' request header. - -When defining your Origins make sure they are properly formatted. The middleware validates and normalizes the provided origins, ensuring they're in the correct format by checking for valid schemes (http or https), and removing any trailing slashes. +To ensure that the provided origins are correctly formatted, this middleware validates and normalizes them. It checks for valid schemes, i.e., HTTP or HTTPS, and it will automatically remove trailing slashes. If the provided origin is invalid, the middleware will panic. ## Signatures @@ -73,7 +71,7 @@ app.Use(cors.New(cors.Config{ |:-----------------|:---------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------| | Next | `func(*fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` | | AllowOriginsFunc | `func(origin string) bool` | AllowOriginsFunc defines a function that will set the 'access-control-allow-origin' response header to the 'origin' request header when returned true. This allows for dynamic evaluation of allowed origins. Note if AllowCredentials is true, wildcard origins will be not have the 'access-control-allow-credentials' header set to 'true'. | `nil` | -| AllowOrigins | `string` | AllowOrigins defines a comma separated list of origins that may access the resource. This supports subdomain matching, so you can use a value like "https://.example.com" to allow any subdomain of example.com to submit requests. | `"*"` | +| AllowOrigins | `string` | AllowOrigins defines a comma separated list of origins that may access the resource. This supports subdomain matching, so you can use a value like "https://*.example.com" to allow any subdomain of example.com to submit requests. | `"*"` | | AllowMethods | `string` | AllowMethods defines a list of methods allowed when accessing the resource. This is used in response to a preflight request. | `"GET,POST,HEAD,PUT,DELETE,PATCH"` | | AllowHeaders | `string` | AllowHeaders defines a list of request headers that can be used when making the actual request. This is in response to a preflight request. | `""` | | AllowCredentials | `bool` | AllowCredentials indicates whether or not the response to the request can be exposed when the credentials flag is true. When used as part of a response to a preflight request, this indicates whether or not the actual request can be made using credentials. Note: If true, AllowOrigins cannot be set to a wildcard ("*") to prevent security vulnerabilities. | `false` | @@ -102,6 +100,20 @@ var ConfigDefault = Config{ } ``` +## Subdomain Matching + +The `AllowOrigins` configuration supports matching subdomains at any level. This means you can use a value like `"https://*.example.com"` to allow any subdomain of `example.com` to submit requests, including multiple subdomain levels such as `"https://sub.sub.example.com"`. + +### Example + +If you want to allow CORS requests from any subdomain of `example.com`, including nested subdomains, you can configure the `AllowOrigins` like so: + +```go +app.Use(cors.New(cors.Config{ + AllowOrigins: "https://*.example.com", +})) +``` + # How It Works The CORS middleware works by adding the necessary CORS headers to responses from your Fiber application. These headers tell browsers what origins, methods, and headers are allowed for cross-origin requests. @@ -116,7 +128,7 @@ The `AllowOrigins` option controls which origins can make cross-origin requests. - **Multiple origins:** If `AllowOrigins` is set to multiple origins like `"https://example.com, https://www.example.com"`, the middleware picks the origin that matches the origin of the incoming request. -- **Subdomain wildcard:** If `AllowOrigins` contains `"https://.example.com"`, a subdomain like `www` would be matched and `"https://www.example.com"` would be the header. +- **Subdomain matching:** If `AllowOrigins` includes `"https://*.example.com"`, a subdomain like `https://sub.example.com` will be matched and `"https://sub.example.com"` will be the header. This will also match `https://sub.sub.example.com` and so on, but not `https://example.com`. - **Wildcard origin:** If `AllowOrigins` is set to `"*"`, the middleware uses that and adds the header `Access-Control-Allow-Origin: *` to the response. @@ -128,24 +140,24 @@ The middleware also handles the `AllowOriginsFunc` option, which allows you to p This way, the CORS middleware allows you to control how your Fiber application responds to cross-origin requests. -## Security Considerations +## Security Considerations + +When configuring CORS, misconfiguration can potentially expose your application to various security risks. Here are some secure configurations and common pitfalls to avoid: + +### Secure Configurations -When configuring CORS, misconfiguration can potentially expose your application to various security risks. +- **Specify Allowed Origins**: Instead of using a wildcard (`*`), specify the exact domains allowed to make requests. For example, `AllowOrigins: "https://www.example.com, https://api.example.com"` ensures only these domains can make cross-origin requests to your application. -- **Allowing all origins:** Setting `Access-Control-Allow-Origin` to `*` (a wildcard) allows any domain to make cross-origin requests. This can expose your application to cross-site request forgery (CSRF) attacks. It's generally safer to specify the exact domains allowed to make requests. +- **Use Credentials Carefully**: If your application needs to support credentials in cross-origin requests, ensure `AllowCredentials` is set to `true` and specify exact origins in `AllowOrigins`. Do not use a wildcard origin in this case. -- **Allowing credentials:** The `Access-Control-Allow-Credentials` header indicates whether the browser should include credentials with cross-origin requests. If this is set to `true`, it can expose your application to attacks if combined with a wildcard `Access-Control-Allow-Origin`. We specifically prohibit this action in our CORS middleware, in line with the Fetch specification. +- **Limit Exposed Headers**: Only whitelist headers that are necessary for the client-side application by setting `ExposeHeaders` appropriately. This minimizes the risk of exposing sensitive information. -- **Exposing headers:** The `Access-Control-Expose-Headers` header lets the server whitelist headers that browsers are allowed to access. Be careful not to expose sensitive headers. +### Common Pitfalls -:::note -In our CORS middleware, we specifically prevent `Access-Control-Allow-Credentials` from being `true` when `Access-Control-Allow-Origin` is set to the wildcard (`*`). This prevents potential security risks associated with allowing credentials to be shared with all origins. +- **Wildcard Origin with Credentials**: Setting `AllowOrigins` to `*` (a wildcard) and `AllowCredentials` to `true` is a common misconfiguration. This combination is prohibited because it can expose your application to security risks. -When using `AllowOrigins`, a configuration check will cause a panic if `Access-Control-Allow-Credentials` is `true` and `Access-Control-Allow-Origin` is set to the wildcard. -::: +- **Overly Permissive Origins**: Specifying too many origins or using overly broad patterns (e.g., `https://*.example.com`) can inadvertently allow malicious sites to interact with your application. Be as specific as possible with allowed origins. -:::caution -Be extra careful when using `AllowOriginsFunc`. Make sure to properly validate the origin to prevent potential security risks. +- **Neglecting `AllowOriginsFunc` Validation**: When using `AllowOriginsFunc` for dynamic origin validation, ensure the function includes robust checks to prevent unauthorized origins from being accepted. Simply returning `true` for all origins can bypass security protections. -When using `AllowOriginsFunc`, the `Access-Control-Allow-Origin` header will always be set to the origin header if the func returns `true`, which can bypass such protections if you return `true` in all situations. -::: \ No newline at end of file +Remember, the key to secure CORS configuration is specificity and caution. By carefully selecting which origins, methods, and headers are allowed, you can help protect your application from cross-origin attacks. \ No newline at end of file diff --git a/middleware/cors/cors.go b/middleware/cors/cors.go index e935c0903f..e27e74cba8 100644 --- a/middleware/cors/cors.go +++ b/middleware/cors/cors.go @@ -15,10 +15,10 @@ type Config struct { // Optional. Default: nil Next func(c *fiber.Ctx) bool - // AllowOriginsFunc defines a function that will set the 'access-control-allow-origin' + // AllowOriginsFunc defines a function that will set the 'Access-Control-Allow-Origin' // response header to the 'origin' request header when returned true. This allows for // dynamic evaluation of allowed origins. Note if AllowCredentials is true, wildcard origins - // will be not have the 'access-control-allow-credentials' header set to 'true'. + // will be not have the 'Access-Control-Allow-Credentials' header set to 'true'. // // Optional. Default: nil AllowOriginsFunc func(origin string) bool @@ -119,22 +119,34 @@ func New(config ...Config) fiber.Handler { allowSOrigins := []subdomain{} allowAllOrigins := false + // processOrigin processes an origin string, normalizes it and checks its validity + // it will panic if the origin is invalid + processOrigin := func(origin string) (string, bool) { + trimmedOrigin := strings.TrimSpace(origin) + isValid, normalizedOrigin := normalizeOrigin(trimmedOrigin) + if !isValid { + log.Warnf("[CORS] Invalid origin format in configuration: %s", trimmedOrigin) + panic("[CORS] Invalid origin provided in configuration") + } + return normalizedOrigin, true + } + // Validate and normalize static AllowOrigins if cfg.AllowOrigins != "" && cfg.AllowOrigins != "*" { origins := strings.Split(cfg.AllowOrigins, ",") - for _, origin := range origins { - trimmedOrigin := strings.TrimSpace(origin) - isValid, normalizedOrigin := normalizeOrigin(trimmedOrigin) - - if !isValid { - log.Warnf("[CORS] Invalid origin format in configuration: %s", trimmedOrigin) - panic("[CORS] Invalid origin provided in configuration") - } - - if i := strings.Index(normalizedOrigin, "://."); i != -1 { - allowSOrigins = append(allowSOrigins, subdomain{prefix: normalizedOrigin[:i+3], suffix: normalizedOrigin[i+3:]}) + if i := strings.Index(origin, "://*."); i != -1 { + normalizedOrigin, isValid := processOrigin(origin[:i+3] + origin[i+4:]) + if !isValid { + continue + } + sd := subdomain{prefix: normalizedOrigin[:i+3], suffix: normalizedOrigin[i+3:]} + allowSOrigins = append(allowSOrigins, sd) } else { + normalizedOrigin, isValid := processOrigin(origin) + if !isValid { + continue + } allowOrigins = append(allowOrigins, normalizedOrigin) } } diff --git a/middleware/cors/cors_test.go b/middleware/cors/cors_test.go index c9b7bd9136..ff5cdd7c25 100644 --- a/middleware/cors/cors_test.go +++ b/middleware/cors/cors_test.go @@ -217,7 +217,7 @@ func Test_CORS_Subdomain(t *testing.T) { // New fiber instance app := fiber.New() // OPTIONS (preflight) response headers when AllowOrigins is set to a subdomain - app.Use("/", New(Config{AllowOrigins: "http://.example.com"})) + app.Use("/", New(Config{AllowOrigins: "http://*.example.com"})) // Get handler pointer handler := app.Handler() @@ -231,7 +231,19 @@ func Test_CORS_Subdomain(t *testing.T) { // Perform request handler(ctx) - // Allow-Origin header should be "" because http://google.com does not satisfy http://.example.com + // Allow-Origin header should be "" because http://google.com does not satisfy http://*.example.com + utils.AssertEqual(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin))) + + ctx.Request.Reset() + ctx.Response.Reset() + + // Make request with domain only (disallowed) + ctx.Request.SetRequestURI("/") + ctx.Request.Header.SetMethod(fiber.MethodOptions) + ctx.Request.Header.Set(fiber.HeaderOrigin, "http://example.com") + + handler(ctx) + utils.AssertEqual(t, "", string(ctx.Response.Header.Peek(fiber.HeaderAccessControlAllowOrigin))) ctx.Request.Reset() @@ -269,22 +281,22 @@ func Test_CORS_AllowOriginScheme(t *testing.T) { shouldAllowOrigin: false, }, { - pattern: "http://.example.com", + pattern: "http://*.example.com", reqOrigin: "http://aaa.example.com", shouldAllowOrigin: true, }, { - pattern: "http://.example.com", + pattern: "http://*.example.com", reqOrigin: "http://bbb.aaa.example.com", shouldAllowOrigin: true, }, { - pattern: "http://.aaa.example.com", + pattern: "http://*.aaa.example.com", reqOrigin: "http://bbb.aaa.example.com", shouldAllowOrigin: true, }, { - pattern: "http://.example.com:8080", + pattern: "http://*.example.com:8080", reqOrigin: "http://aaa.example.com:8080", shouldAllowOrigin: true, }, @@ -294,12 +306,12 @@ func Test_CORS_AllowOriginScheme(t *testing.T) { shouldAllowOrigin: false, }, { - pattern: "http://.aaa.example.com", + pattern: "http://*.aaa.example.com", reqOrigin: "http://ccc.bbb.example.com", shouldAllowOrigin: false, }, { - pattern: "http://.example.com", + pattern: "http://*.example.com", reqOrigin: "http://1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.example.com", shouldAllowOrigin: true, }, @@ -314,7 +326,7 @@ func Test_CORS_AllowOriginScheme(t *testing.T) { shouldAllowOrigin: false, }, { - pattern: "http://.example.com", + pattern: "http://*.example.com", reqOrigin: "http://ccc.bbb.example.com", shouldAllowOrigin: true, }, diff --git a/middleware/cors/utils.go b/middleware/cors/utils.go index 9fca1df197..443e648903 100644 --- a/middleware/cors/utils.go +++ b/middleware/cors/utils.go @@ -44,7 +44,7 @@ func normalizeOrigin(origin string) (bool, string) { // Don't allow a wildcard with a protocol // wildcards cannot be used within any other value. For example, the following header is not valid: - // Access-Control-Allow-Origin: https://*.normal-website.com + // Access-Control-Allow-Origin: https://* if strings.Contains(parsedOrigin.Host, "*") { return false, "" } From f12d083c2128ade2435e656fe49e7e68b8eacfee Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Fri, 15 Mar 2024 16:22:16 -0300 Subject: [PATCH 20/32] chore(middleware/cors): fix header capitalization --- docs/api/middleware/cors.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index e59c64906e..f3c88f41fe 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -42,7 +42,7 @@ app.Use(cors.New(cors.Config{ Using the `AllowOriginsFunc` function. In this example any origin will be allowed via CORS. -For example, if a browser running on `http://localhost:3000` sends a request, this will be accepted and the `access-control-allow-origin` response header will be set to `http://localhost:3000`. +For example, if a browser running on `http://localhost:3000` sends a request, this will be accepted and the `Access-Control-Allow-Origin` response header will be set to `http://localhost:3000`. **Note: Using this feature is discouraged in production and it's best practice to explicitly set CORS origins via `AllowOrigins`.** @@ -70,7 +70,7 @@ app.Use(cors.New(cors.Config{ | Property | Type | Description | Default | |:-----------------|:---------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------| | Next | `func(*fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` | -| AllowOriginsFunc | `func(origin string) bool` | AllowOriginsFunc defines a function that will set the 'access-control-allow-origin' response header to the 'origin' request header when returned true. This allows for dynamic evaluation of allowed origins. Note if AllowCredentials is true, wildcard origins will be not have the 'access-control-allow-credentials' header set to 'true'. | `nil` | +| AllowOriginsFunc | `func(origin string) bool` | AllowOriginsFunc defines a function that will set the 'Access-Control-Allow-Origin' response header to the 'origin' request header when returned true. This allows for dynamic evaluation of allowed origins. Note if AllowCredentials is true, wildcard origins will be not have the 'Access-Control-Allow-Credentials' header set to 'true'. | `nil` | | AllowOrigins | `string` | AllowOrigins defines a comma separated list of origins that may access the resource. This supports subdomain matching, so you can use a value like "https://*.example.com" to allow any subdomain of example.com to submit requests. | `"*"` | | AllowMethods | `string` | AllowMethods defines a list of methods allowed when accessing the resource. This is used in response to a preflight request. | `"GET,POST,HEAD,PUT,DELETE,PATCH"` | | AllowHeaders | `string` | AllowHeaders defines a list of request headers that can be used when making the actual request. This is in response to a preflight request. | `""` | From 79d46e08b57040fb6cbc454cc05fd557e4f2822e Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Sat, 16 Mar 2024 10:10:45 -0300 Subject: [PATCH 21/32] docs(middleware/cors): improve sercuity notes --- docs/api/middleware/cors.md | 52 ++++++++++++++----------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index f3c88f41fe..4f9fef4244 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -4,9 +4,9 @@ id: cors # CORS -CORS middleware for [Fiber](https://github.com/gofiber/fiber) that can be used to enable [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) with various options. +CORS middleware for [Fiber](https://github.com/gofiber/fiber) that can be used to enable [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) with various options. CORS is not a security feature, it's a feature that relaxes the security model of web browsers, and it's only intended to be used by servers that need to allow cross-origin requests. -The middleware conforms to the `Access-Control-Allow-Origin` specification by parsing `AllowOrigins`. First, the middleware checks if there is a matching allowed origin for the requesting 'origin' header. If there is a match, it returns exactly one matching domain from the list of allowed origins. If there is no match, the middleware does not add the `Access-Control-Allow-Origin` header to the response. +The middleware conforms to the `Access-Control-Allow-Origin` specification by parsing `AllowOrigins`. First, the middleware checks if there is a matching allowed origin for the requesting 'origin' header. If there is a match, the `Access-Control-Allow-Origin` response header will be set to the 'origin' request header. If there is no match, the `Access-Control-Allow-Origin` response header will not be set. To ensure that the provided origins are correctly formatted, this middleware validates and normalizes them. It checks for valid schemes, i.e., HTTP or HTTPS, and it will automatically remove trailing slashes. If the provided origin is invalid, the middleware will panic. @@ -36,23 +36,7 @@ app.Use(cors.New()) // Or extend your config for customization app.Use(cors.New(cors.Config{ AllowOrigins: "https://gofiber.io, https://gofiber.net", - AllowHeaders: "Origin, Content-Type, Accept", -})) -``` - -Using the `AllowOriginsFunc` function. In this example any origin will be allowed via CORS. - -For example, if a browser running on `http://localhost:3000` sends a request, this will be accepted and the `Access-Control-Allow-Origin` response header will be set to `http://localhost:3000`. - -**Note: Using this feature is discouraged in production and it's best practice to explicitly set CORS origins via `AllowOrigins`.** - -```go -app.Use(cors.New()) - -app.Use(cors.New(cors.Config{ - AllowOriginsFunc: func(origin string) bool { - return os.Getenv("ENVIRONMENT") == "development" - }, + AllowHeaders: "Origin, Content-Type, Accept", })) ``` @@ -70,7 +54,7 @@ app.Use(cors.New(cors.Config{ | Property | Type | Description | Default | |:-----------------|:---------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------| | Next | `func(*fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` | -| AllowOriginsFunc | `func(origin string) bool` | AllowOriginsFunc defines a function that will set the 'Access-Control-Allow-Origin' response header to the 'origin' request header when returned true. This allows for dynamic evaluation of allowed origins. Note if AllowCredentials is true, wildcard origins will be not have the 'Access-Control-Allow-Credentials' header set to 'true'. | `nil` | +| AllowOriginsFunc | `func(origin string) bool` | `AllowOriginsFunc` is a function that dynamically determines whether to allow a request based on its origin. If this function returns `true`, the 'Access-Control-Allow-Origin' response header will be set to the request's 'origin' header. This function is only used if the request's origin doesn't match any origin in `AllowOrigins`. | `nil` | | AllowOrigins | `string` | AllowOrigins defines a comma separated list of origins that may access the resource. This supports subdomain matching, so you can use a value like "https://*.example.com" to allow any subdomain of example.com to submit requests. | `"*"` | | AllowMethods | `string` | AllowMethods defines a list of methods allowed when accessing the resource. This is used in response to a preflight request. | `"GET,POST,HEAD,PUT,DELETE,PATCH"` | | AllowHeaders | `string` | AllowHeaders defines a list of request headers that can be used when making the actual request. This is in response to a preflight request. | `""` | @@ -102,7 +86,7 @@ var ConfigDefault = Config{ ## Subdomain Matching -The `AllowOrigins` configuration supports matching subdomains at any level. This means you can use a value like `"https://*.example.com"` to allow any subdomain of `example.com` to submit requests, including multiple subdomain levels such as `"https://sub.sub.example.com"`. +The `AllowOrigins` configuration supports matching subdomains at any level. This means you can use a value like `"https://*.example.com"` to allow any subdomain of `example.com` to submit requests, including multiple subdomain levels such as `"https://sub.sub.example.com"`. ### Example @@ -136,28 +120,32 @@ In all cases above, except the **Wildcard origin**, the middleware will either a The `AllowMethods` option controls which HTTP methods are allowed. For example, if `AllowMethods` is set to `"GET, POST"`, the middleware adds the header `Access-Control-Allow-Methods: GET, POST` to the response. -The middleware also handles the `AllowOriginsFunc` option, which allows you to programmatically determine if an origin is allowed. If `AllowOriginsFunc` returns `true` for an origin, the middleware sets the `Access-Control-Allow-Origin` header to that origin. +- **Programmatic origin validation:**: The middleware also handles the `AllowOriginsFunc` option, which allows you to programmatically determine if an origin is allowed. If `AllowOriginsFunc` returns `true` for an origin, the middleware sets the `Access-Control-Allow-Origin` header to that origin. This way, the CORS middleware allows you to control how your Fiber application responds to cross-origin requests. -## Security Considerations +## Security Considerations -When configuring CORS, misconfiguration can potentially expose your application to various security risks. Here are some secure configurations and common pitfalls to avoid: +When configuring CORS, misconfiguration can potentially expose your application to various security risks. Here are some secure configurations and common pitfalls to avoid: -### Secure Configurations +### Secure Configurations -- **Specify Allowed Origins**: Instead of using a wildcard (`*`), specify the exact domains allowed to make requests. For example, `AllowOrigins: "https://www.example.com, https://api.example.com"` ensures only these domains can make cross-origin requests to your application. +- **Specify Allowed Origins**: Instead of using a wildcard (`*`), specify the exact domains allowed to make requests. For example, `AllowOrigins: "https://www.example.com, https://api.example.com"` ensures only these domains can make cross-origin requests to your application. -- **Use Credentials Carefully**: If your application needs to support credentials in cross-origin requests, ensure `AllowCredentials` is set to `true` and specify exact origins in `AllowOrigins`. Do not use a wildcard origin in this case. +- **Use Credentials Carefully**: If your application needs to support credentials in cross-origin requests, ensure `AllowCredentials` is set to `true` and specify exact origins in `AllowOrigins`. Do not use a wildcard origin in this case. - **Limit Exposed Headers**: Only whitelist headers that are necessary for the client-side application by setting `ExposeHeaders` appropriately. This minimizes the risk of exposing sensitive information. -### Common Pitfalls +### Common Pitfalls + +- **Wildcard Origin with Credentials**: Setting `AllowOrigins` to `*` (a wildcard) and `AllowCredentials` to `true` is a common misconfiguration. This combination is prohibited because it can expose your application to security risks. -- **Wildcard Origin with Credentials**: Setting `AllowOrigins` to `*` (a wildcard) and `AllowCredentials` to `true` is a common misconfiguration. This combination is prohibited because it can expose your application to security risks. +- **Overly Permissive Origins**: Specifying too many origins or using overly broad patterns (e.g., `https://*.example.com`) can inadvertently allow malicious sites to interact with your application. Be as specific as possible with allowed origins. -- **Overly Permissive Origins**: Specifying too many origins or using overly broad patterns (e.g., `https://*.example.com`) can inadvertently allow malicious sites to interact with your application. Be as specific as possible with allowed origins. +- **Neglecting `AllowOriginsFunc` Validation**: When using `AllowOriginsFunc` for dynamic origin validation, ensure the function includes robust checks to prevent unauthorized origins from being accepted. Simply returning `true` for all origins can bypass security protections. -- **Neglecting `AllowOriginsFunc` Validation**: When using `AllowOriginsFunc` for dynamic origin validation, ensure the function includes robust checks to prevent unauthorized origins from being accepted. Simply returning `true` for all origins can bypass security protections. +:::caution +Do not use `AllowOriginsFunc` to return `true` for all origins. This is particularly crucial when `AllowCredentials` is set to `true`. Doing so can bypass the restriction of using a wildcard origin with credentials, exposing your application to serious security threats. +::: -Remember, the key to secure CORS configuration is specificity and caution. By carefully selecting which origins, methods, and headers are allowed, you can help protect your application from cross-origin attacks. \ No newline at end of file +Remember, the key to secure CORS configuration is specificity and caution. By carefully selecting which origins, methods, and headers are allowed, you can help protect your application from cross-origin attacks. \ No newline at end of file From 68601305f7392e251149dccdf7e06bbb7d2ee140 Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Sat, 16 Mar 2024 10:14:02 -0300 Subject: [PATCH 22/32] docs(middleware/cors): Improve security notes --- docs/api/middleware/cors.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index 4f9fef4244..1d6e39d792 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -142,10 +142,12 @@ When configuring CORS, misconfiguration can potentially expose your application - **Overly Permissive Origins**: Specifying too many origins or using overly broad patterns (e.g., `https://*.example.com`) can inadvertently allow malicious sites to interact with your application. Be as specific as possible with allowed origins. -- **Neglecting `AllowOriginsFunc` Validation**: When using `AllowOriginsFunc` for dynamic origin validation, ensure the function includes robust checks to prevent unauthorized origins from being accepted. Simply returning `true` for all origins can bypass security protections. +- **Inadequate `AllowOriginsFunc` Validation**: When using `AllowOriginsFunc` for dynamic origin validation, ensure the function includes robust checks to prevent unauthorized origins from being accepted. Overly permissive validation can lead to security vulnerabilities. :::caution -Do not use `AllowOriginsFunc` to return `true` for all origins. This is particularly crucial when `AllowCredentials` is set to `true`. Doing so can bypass the restriction of using a wildcard origin with credentials, exposing your application to serious security threats. +Never allow `AllowOriginsFunc` to return `true` for all origins. This is particularly crucial when `AllowCredentials` is set to `true`. Doing so can bypass the restriction of using a wildcard origin with credentials, exposing your application to serious security threats. + +If you need to allow wildcard origins, use `AllowOrigins` with a wildcard '*' instead of `AllowOriginsFunc`. ::: Remember, the key to secure CORS configuration is specificity and caution. By carefully selecting which origins, methods, and headers are allowed, you can help protect your application from cross-origin attacks. \ No newline at end of file From 81ddb4d3107619aa2e8565db28afb279c5397396 Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Sat, 16 Mar 2024 10:40:29 -0300 Subject: [PATCH 23/32] docs(middleware/cors): improve CORS overview --- docs/api/middleware/cors.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index 1d6e39d792..5075d02b35 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -4,11 +4,13 @@ id: cors # CORS -CORS middleware for [Fiber](https://github.com/gofiber/fiber) that can be used to enable [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) with various options. CORS is not a security feature, it's a feature that relaxes the security model of web browsers, and it's only intended to be used by servers that need to allow cross-origin requests. +CORS (Cross-Origin Resource Sharing) is a middleware for [Fiber](https://github.com/gofiber/fiber) that allows servers to specify who can access its resources and how. It's not a security feature, but a way to relax the security model of web browsers for cross-origin requests. You can learn more about CORS on [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). -The middleware conforms to the `Access-Control-Allow-Origin` specification by parsing `AllowOrigins`. First, the middleware checks if there is a matching allowed origin for the requesting 'origin' header. If there is a match, the `Access-Control-Allow-Origin` response header will be set to the 'origin' request header. If there is no match, the `Access-Control-Allow-Origin` response header will not be set. +This middleware works by adding CORS headers to responses from your Fiber application. These headers specify which origins, methods, and headers are allowed for cross-origin requests. It also handles preflight requests, which are a CORS mechanism to check if the actual request is safe to send. -To ensure that the provided origins are correctly formatted, this middleware validates and normalizes them. It checks for valid schemes, i.e., HTTP or HTTPS, and it will automatically remove trailing slashes. If the provided origin is invalid, the middleware will panic. +The middleware uses the `AllowOrigins` option to control which origins can make cross-origin requests. It supports single origin, multiple origins, subdomain matching, and wildcard origin. It also allows programmatic origin validation with the `AllowOriginsFunc` option. + +When configuring CORS, it's important to avoid [common pitfalls](#common-pitfalls) like using a wildcard origin with credentials, being overly permissive with origins, and inadequate validation with `AllowOriginsFunc`. Misconfiguration can expose your application to various security risks. ## Signatures From 2e57b0bcdcaf0fb9c5c7aa0c7a7d78507e1a5822 Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Sat, 16 Mar 2024 10:46:18 -0300 Subject: [PATCH 24/32] docs(middleware/cors): fix ordering of how it works --- docs/api/middleware/cors.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index 5075d02b35..d0a257adf4 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -120,10 +120,10 @@ The `AllowOrigins` option controls which origins can make cross-origin requests. In all cases above, except the **Wildcard origin**, the middleware will either add the `Access-Control-Allow-Origin` header to the response matching the origin of the incoming request, or it will not add the header at all if the origin is not allowed. -The `AllowMethods` option controls which HTTP methods are allowed. For example, if `AllowMethods` is set to `"GET, POST"`, the middleware adds the header `Access-Control-Allow-Methods: GET, POST` to the response. - - **Programmatic origin validation:**: The middleware also handles the `AllowOriginsFunc` option, which allows you to programmatically determine if an origin is allowed. If `AllowOriginsFunc` returns `true` for an origin, the middleware sets the `Access-Control-Allow-Origin` header to that origin. +The `AllowMethods` option controls which HTTP methods are allowed. For example, if `AllowMethods` is set to `"GET, POST"`, the middleware adds the header `Access-Control-Allow-Methods: GET, POST` to the response. + This way, the CORS middleware allows you to control how your Fiber application responds to cross-origin requests. ## Security Considerations From 025d358dc8243d7ee2d7fd22f0d120c93aea9b2c Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Sat, 16 Mar 2024 11:01:45 -0300 Subject: [PATCH 25/32] docs(middleware/cors): add additional info to How to works --- docs/api/middleware/cors.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index d0a257adf4..d02d4fe6b5 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -124,7 +124,15 @@ In all cases above, except the **Wildcard origin**, the middleware will either a The `AllowMethods` option controls which HTTP methods are allowed. For example, if `AllowMethods` is set to `"GET, POST"`, the middleware adds the header `Access-Control-Allow-Methods: GET, POST` to the response. -This way, the CORS middleware allows you to control how your Fiber application responds to cross-origin requests. +The `AllowHeaders` option specifies which headers are allowed in the actual request. The middleware sets the Access-Control-Allow-Headers response header to the value of `AllowHeaders`. This informs the client which headers it can use in the actual request. + +The `AllowCredentials` option indicates whether the response to the request can be exposed when the credentials flag is true. If `AllowCredentials` is set to `true`, the middleware adds the header `Access-Control-Allow-Credentials: true` to the response. To prevent security vulnerabilities, `AllowCredentials` cannot be set to `true` if `AllowOrigins` is set to a wildcard (`*`). + +The `ExposeHeaders` option defines a whitelist of headers that clients are allowed to access. If `ExposeHeaders` is set to `"X-Custom-Header"`, the middleware adds the header `Access-Control-Expose-Headers: X-Custom-Header` to the response. + +The `MaxAge` option indicates how long the results of a preflight request can be cached. If `MaxAge` is set to `3600`, the middleware adds the header `Access-Control-Max-Age: 3600` to the response. + +The `Vary` header is used in this middleware to inform the client that the server's response to a request. For or both preflight and actual requests, the Vary header is set to `Access-Control-Request-Method` and `Access-Control-Request-Headers`. For preflight requests, the Vary header is also set to `Origin`. The `Vary` header is important for caching. It helps caches (like a web browser's cache or a CDN) determine when a cached response can be used in response to a future request, and when the server needs to be queried for a new response. ## Security Considerations From 2431a099a82223fca4e4bafcaea3528ebbf473ef Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Sat, 16 Mar 2024 11:06:15 -0300 Subject: [PATCH 26/32] docs(middleware/cors): rm space --- docs/api/middleware/cors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index d02d4fe6b5..ad3ca034e7 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -92,7 +92,7 @@ The `AllowOrigins` configuration supports matching subdomains at any level. This ### Example -If you want to allow CORS requests from any subdomain of `example.com`, including nested subdomains, you can configure the `AllowOrigins` like so: +If you want to allow CORS requests from any subdomain of `example.com`, including nested subdomains, you can configure the `AllowOrigins` like so: ```go app.Use(cors.New(cors.Config{ From 029c2f95803f3202fb7943397b2666caf6cb357c Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Sat, 16 Mar 2024 11:09:22 -0300 Subject: [PATCH 27/32] docs(middleware/cors): add validation for AllowOrigins origins to overview --- docs/api/middleware/cors.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index ad3ca034e7..404618ea89 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -10,6 +10,8 @@ This middleware works by adding CORS headers to responses from your Fiber applic The middleware uses the `AllowOrigins` option to control which origins can make cross-origin requests. It supports single origin, multiple origins, subdomain matching, and wildcard origin. It also allows programmatic origin validation with the `AllowOriginsFunc` option. +To ensure that the provided `AllowOrigins` origins are correctly formatted, this middleware validates and normalizes them. It checks for valid schemes, i.e., HTTP or HTTPS, and it will automatically remove trailing slashes. If the provided origin is invalid, the middleware will panic. + When configuring CORS, it's important to avoid [common pitfalls](#common-pitfalls) like using a wildcard origin with credentials, being overly permissive with origins, and inadequate validation with `AllowOriginsFunc`. Misconfiguration can expose your application to various security risks. ## Signatures From d220abc19154ab06d756975e402cf2fab44d5c3b Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Sat, 16 Mar 2024 11:23:35 -0300 Subject: [PATCH 28/32] docs(middleware/cors): update ExposeHeaders and MaxAge descriptions --- docs/api/middleware/cors.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index 404618ea89..bc3a4e9776 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -63,8 +63,8 @@ app.Use(cors.New(cors.Config{ | AllowMethods | `string` | AllowMethods defines a list of methods allowed when accessing the resource. This is used in response to a preflight request. | `"GET,POST,HEAD,PUT,DELETE,PATCH"` | | AllowHeaders | `string` | AllowHeaders defines a list of request headers that can be used when making the actual request. This is in response to a preflight request. | `""` | | AllowCredentials | `bool` | AllowCredentials indicates whether or not the response to the request can be exposed when the credentials flag is true. When used as part of a response to a preflight request, this indicates whether or not the actual request can be made using credentials. Note: If true, AllowOrigins cannot be set to a wildcard ("*") to prevent security vulnerabilities. | `false` | -| ExposeHeaders | `string` | ExposeHeaders defines a whitelist headers that clients are allowed to access. | `""` | -| MaxAge | `int` | MaxAge indicates how long (in seconds) the results of a preflight request can be cached. If you pass MaxAge 0, Access-Control-Max-Age header will not be added and browser will use 5 seconds by default. To disable caching completely, pass MaxAge value negative. It will set the Access-Control-Max-Age header 0. | `0` | +| ExposeHeaders | `string` | ExposeHeaders defines whitelist headers that clients are allowed to access. | `""` | +| MaxAge | `int` | MaxAge indicates how long (in seconds) the results of a preflight request can be cached. If you pass MaxAge 0, the Access-Control-Max-Age header will not be added and the browser will use 5 seconds by default. To disable caching completely, pass MaxAge value negative. It will set the Access-Control-Max-Age header to 0. | `0` | ## Default Config From cc8c559c85415f45326b6d17170caff1e1941ac9 Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Sat, 16 Mar 2024 11:45:28 -0300 Subject: [PATCH 29/32] docs(middleware/cors): Add dynamic origin validation example --- docs/api/middleware/cors.md | 53 ++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index bc3a4e9776..49aac0d75f 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -33,6 +33,8 @@ import ( After you initiate your Fiber app, you can use the following possibilities: +### Basic usage + ```go // Initialize default config app.Use(cors.New()) @@ -44,6 +46,45 @@ app.Use(cors.New(cors.Config{ })) ``` +### Dynamic origin validation + +You can use `AllowOriginsFunc` to programmatically determine whether to allow a request based on its origin. This is useful when you need to validate origins against a database or other dynamic sources. The function should return `true` if the origin is allowed, and `false` otherwise. + +Be sure to review the [security considerations](#security-considerations) when using `AllowOriginsFunc`. + +:::caution +Never allow `AllowOriginsFunc` to return `true` for all origins. This is particularly crucial when `AllowCredentials` is set to `true`. Doing so can bypass the restriction of using a wildcard origin with credentials, exposing your application to serious security threats. + +If you need to allow wildcard origins, use `AllowOrigins` with a wildcard '*' instead of `AllowOriginsFunc`. +::: + +```go +// dbCheckOrigin checks if the origin is in the list of allowed origins in the database. +func dbCheckOrigin(db *sql.DB, origin string) bool { + // Placeholder query - adjust according to your database schema and query needs + query := "SELECT COUNT(*) FROM allowed_origins WHERE origin = $1" + + var count int + err := db.QueryRow(query, origin).Scan(&count) + if err != nil { + // Handle error (e.g., log it); for simplicity, we return false here + return false + } + + return count > 0 +} + +// ... + +app.Use(cors.New(cors.Config{ + AllowOriginsFunc: func(origin string) bool { + return dbCheckOrigin(db, origin) + }, +})) +``` + +### Porhibited usage + **Note: The following configuration is considered insecure and will result in a panic.** ```go @@ -88,11 +129,11 @@ var ConfigDefault = Config{ } ``` -## Subdomain Matching +## Subdomain Matching The `AllowOrigins` configuration supports matching subdomains at any level. This means you can use a value like `"https://*.example.com"` to allow any subdomain of `example.com` to submit requests, including multiple subdomain levels such as `"https://sub.sub.example.com"`. -### Example +### Example If you want to allow CORS requests from any subdomain of `example.com`, including nested subdomains, you can configure the `AllowOrigins` like so: @@ -154,12 +195,6 @@ When configuring CORS, misconfiguration can potentially expose your application - **Overly Permissive Origins**: Specifying too many origins or using overly broad patterns (e.g., `https://*.example.com`) can inadvertently allow malicious sites to interact with your application. Be as specific as possible with allowed origins. -- **Inadequate `AllowOriginsFunc` Validation**: When using `AllowOriginsFunc` for dynamic origin validation, ensure the function includes robust checks to prevent unauthorized origins from being accepted. Overly permissive validation can lead to security vulnerabilities. - -:::caution -Never allow `AllowOriginsFunc` to return `true` for all origins. This is particularly crucial when `AllowCredentials` is set to `true`. Doing so can bypass the restriction of using a wildcard origin with credentials, exposing your application to serious security threats. - -If you need to allow wildcard origins, use `AllowOrigins` with a wildcard '*' instead of `AllowOriginsFunc`. -::: +- **Inadequate `AllowOriginsFunc` Validation**: When using `AllowOriginsFunc` for dynamic origin validation, ensure the function includes robust checks to prevent unauthorized origins from being accepted. Overly permissive validation can lead to security vulnerabilities. Never allow `AllowOriginsFunc` to return `true` for all origins. This is particularly crucial when `AllowCredentials` is set to `true`. Doing so can bypass the restriction of using a wildcard origin with credentials, exposing your application to serious security threats. If you need to allow wildcard origins, use `AllowOrigins` with a wildcard '*' instead of `AllowOriginsFunc`. Remember, the key to secure CORS configuration is specificity and caution. By carefully selecting which origins, methods, and headers are allowed, you can help protect your application from cross-origin attacks. \ No newline at end of file From fb48678d2af29a14fc3dd86759f8971184e954b7 Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Sat, 16 Mar 2024 11:47:25 -0300 Subject: [PATCH 30/32] docs(middleware/cors): Improve security notes and fix header capitalization --- docs/api/middleware/cors.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index 49aac0d75f..6420f4c643 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -85,7 +85,7 @@ app.Use(cors.New(cors.Config{ ### Porhibited usage -**Note: The following configuration is considered insecure and will result in a panic.** +The following example is prohibited because it can expose your application to security risks. It sets `AllowOrigins` to `*` (a wildcard) and `AllowCredentials` to `true`. ```go app.Use(cors.New(cors.Config{ @@ -94,6 +94,12 @@ app.Use(cors.New(cors.Config{ })) ``` +This will result in the following panic: + +``` +panic: [CORS] 'AllowCredentials' is true, but 'AllowOrigins' cannot be set to '*'. +``` + ## Config | Property | Type | Description | Default | From c17860e34d6e3a1791cb8ea673d5ef9cdf699156 Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Sat, 16 Mar 2024 11:51:24 -0300 Subject: [PATCH 31/32] docs(middleware/cors): configuration examples --- docs/api/middleware/cors.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index 6420f4c643..9a13ae6156 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -35,6 +35,14 @@ After you initiate your Fiber app, you can use the following possibilities: ### Basic usage +To use the default configuration, simply use `cors.New()`. This will allow wildcard origins, all methods, no credentials, and no headers or exposed headers. + +```go +app.Use(cors.New()) +``` + +### Custom configuration (specific origins, headers, etc.) + ```go // Initialize default config app.Use(cors.New()) From 87e7cadbcf6f236dbb572922c4b69d06d9d60de2 Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Sat, 16 Mar 2024 11:58:39 -0300 Subject: [PATCH 32/32] docs(middleware/cors): `"*"` --- docs/api/middleware/cors.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/api/middleware/cors.md b/docs/api/middleware/cors.md index 9a13ae6156..882a74808b 100644 --- a/docs/api/middleware/cors.md +++ b/docs/api/middleware/cors.md @@ -35,7 +35,7 @@ After you initiate your Fiber app, you can use the following possibilities: ### Basic usage -To use the default configuration, simply use `cors.New()`. This will allow wildcard origins, all methods, no credentials, and no headers or exposed headers. +To use the default configuration, simply use `cors.New()`. This will allow wildcard origins '*', all methods, no credentials, and no headers or exposed headers. ```go app.Use(cors.New()) @@ -63,7 +63,7 @@ Be sure to review the [security considerations](#security-considerations) when u :::caution Never allow `AllowOriginsFunc` to return `true` for all origins. This is particularly crucial when `AllowCredentials` is set to `true`. Doing so can bypass the restriction of using a wildcard origin with credentials, exposing your application to serious security threats. -If you need to allow wildcard origins, use `AllowOrigins` with a wildcard '*' instead of `AllowOriginsFunc`. +If you need to allow wildcard origins, use `AllowOrigins` with a wildcard `"*"` instead of `AllowOriginsFunc`. ::: ```go @@ -91,9 +91,9 @@ app.Use(cors.New(cors.Config{ })) ``` -### Porhibited usage +### Prohibited usage -The following example is prohibited because it can expose your application to security risks. It sets `AllowOrigins` to `*` (a wildcard) and `AllowCredentials` to `true`. +The following example is prohibited because it can expose your application to security risks. It sets `AllowOrigins` to `"*"` (a wildcard) and `AllowCredentials` to `true`. ```go app.Use(cors.New(cors.Config{ @@ -105,7 +105,7 @@ app.Use(cors.New(cors.Config{ This will result in the following panic: ``` -panic: [CORS] 'AllowCredentials' is true, but 'AllowOrigins' cannot be set to '*'. +panic: [CORS] 'AllowCredentials' is true, but 'AllowOrigins' cannot be set to `"*"`. ``` ## Config @@ -117,7 +117,7 @@ panic: [CORS] 'AllowCredentials' is true, but 'AllowOrigins' cannot be set to '* | AllowOrigins | `string` | AllowOrigins defines a comma separated list of origins that may access the resource. This supports subdomain matching, so you can use a value like "https://*.example.com" to allow any subdomain of example.com to submit requests. | `"*"` | | AllowMethods | `string` | AllowMethods defines a list of methods allowed when accessing the resource. This is used in response to a preflight request. | `"GET,POST,HEAD,PUT,DELETE,PATCH"` | | AllowHeaders | `string` | AllowHeaders defines a list of request headers that can be used when making the actual request. This is in response to a preflight request. | `""` | -| AllowCredentials | `bool` | AllowCredentials indicates whether or not the response to the request can be exposed when the credentials flag is true. When used as part of a response to a preflight request, this indicates whether or not the actual request can be made using credentials. Note: If true, AllowOrigins cannot be set to a wildcard ("*") to prevent security vulnerabilities. | `false` | +| AllowCredentials | `bool` | AllowCredentials indicates whether or not the response to the request can be exposed when the credentials flag is true. When used as part of a response to a preflight request, this indicates whether or not the actual request can be made using credentials. Note: If true, AllowOrigins cannot be set to a wildcard (`"*"`) to prevent security vulnerabilities. | `false` | | ExposeHeaders | `string` | ExposeHeaders defines whitelist headers that clients are allowed to access. | `""` | | MaxAge | `int` | MaxAge indicates how long (in seconds) the results of a preflight request can be cached. If you pass MaxAge 0, the Access-Control-Max-Age header will not be added and the browser will use 5 seconds by default. To disable caching completely, pass MaxAge value negative. It will set the Access-Control-Max-Age header to 0. | `0` | @@ -197,7 +197,7 @@ When configuring CORS, misconfiguration can potentially expose your application ### Secure Configurations -- **Specify Allowed Origins**: Instead of using a wildcard (`*`), specify the exact domains allowed to make requests. For example, `AllowOrigins: "https://www.example.com, https://api.example.com"` ensures only these domains can make cross-origin requests to your application. +- **Specify Allowed Origins**: Instead of using a wildcard (`"*"`), specify the exact domains allowed to make requests. For example, `AllowOrigins: "https://www.example.com, https://api.example.com"` ensures only these domains can make cross-origin requests to your application. - **Use Credentials Carefully**: If your application needs to support credentials in cross-origin requests, ensure `AllowCredentials` is set to `true` and specify exact origins in `AllowOrigins`. Do not use a wildcard origin in this case. @@ -205,10 +205,10 @@ When configuring CORS, misconfiguration can potentially expose your application ### Common Pitfalls -- **Wildcard Origin with Credentials**: Setting `AllowOrigins` to `*` (a wildcard) and `AllowCredentials` to `true` is a common misconfiguration. This combination is prohibited because it can expose your application to security risks. +- **Wildcard Origin with Credentials**: Setting `AllowOrigins` to `"*"` (a wildcard) and `AllowCredentials` to `true` is a common misconfiguration. This combination is prohibited because it can expose your application to security risks. - **Overly Permissive Origins**: Specifying too many origins or using overly broad patterns (e.g., `https://*.example.com`) can inadvertently allow malicious sites to interact with your application. Be as specific as possible with allowed origins. -- **Inadequate `AllowOriginsFunc` Validation**: When using `AllowOriginsFunc` for dynamic origin validation, ensure the function includes robust checks to prevent unauthorized origins from being accepted. Overly permissive validation can lead to security vulnerabilities. Never allow `AllowOriginsFunc` to return `true` for all origins. This is particularly crucial when `AllowCredentials` is set to `true`. Doing so can bypass the restriction of using a wildcard origin with credentials, exposing your application to serious security threats. If you need to allow wildcard origins, use `AllowOrigins` with a wildcard '*' instead of `AllowOriginsFunc`. +- **Inadequate `AllowOriginsFunc` Validation**: When using `AllowOriginsFunc` for dynamic origin validation, ensure the function includes robust checks to prevent unauthorized origins from being accepted. Overly permissive validation can lead to security vulnerabilities. Never allow `AllowOriginsFunc` to return `true` for all origins. This is particularly crucial when `AllowCredentials` is set to `true`. Doing so can bypass the restriction of using a wildcard origin with credentials, exposing your application to serious security threats. If you need to allow wildcard origins, use `AllowOrigins` with a wildcard `"*"` instead of `AllowOriginsFunc`. Remember, the key to secure CORS configuration is specificity and caution. By carefully selecting which origins, methods, and headers are allowed, you can help protect your application from cross-origin attacks. \ No newline at end of file