From 4b95dc09e51bd1fc6b124d1f09f05f148821e879 Mon Sep 17 00:00:00 2001 From: orbisai0security Date: Wed, 22 Apr 2026 12:25:19 +0530 Subject: [PATCH] fix: V-002 security vulnerability Automated security fix generated by Orbis Security AI --- auth.go | 12 ++++++++++++ auth_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/auth.go b/auth.go index 5d3222d5..8ca4e4bd 100644 --- a/auth.go +++ b/auth.go @@ -52,6 +52,12 @@ func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc { realm = "Basic realm=" + strconv.Quote(realm) pairs := processAccounts(accounts) return func(c *Context) { + // Enforce HTTPS: Basic Auth credentials are Base64-encoded, not + // encrypted, and must not be transmitted over plain HTTP. + if c.Request.TLS == nil && c.GetHeader("X-Forwarded-Proto") != "https" { + c.AbortWithStatus(http.StatusForbidden) + return + } // Search user in the slice of allowed credentials user, found := pairs.searchCredential(c.requestHeader("Authorization")) if !found { @@ -102,6 +108,12 @@ func BasicAuthForProxy(accounts Accounts, realm string) HandlerFunc { realm = "Basic realm=" + strconv.Quote(realm) pairs := processAccounts(accounts) return func(c *Context) { + // Enforce HTTPS: Basic Auth credentials are Base64-encoded, not + // encrypted, and must not be transmitted over plain HTTP. + if c.Request.TLS == nil && c.GetHeader("X-Forwarded-Proto") != "https" { + c.AbortWithStatus(http.StatusForbidden) + return + } proxyUser, found := pairs.searchCredential(c.requestHeader("Proxy-Authorization")) if !found { // Credentials doesn't match, we return 407 and abort handlers chain. diff --git a/auth_test.go b/auth_test.go index 9166e3b0..a4e8663f 100644 --- a/auth_test.go +++ b/auth_test.go @@ -92,6 +92,7 @@ func TestBasicAuthSucceed(t *testing.T) { w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/login", nil) req.Header.Set("Authorization", authorizationHeader("admin", "password")) + req.Header.Set("X-Forwarded-Proto", "https") router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -111,6 +112,7 @@ func TestBasicAuth401(t *testing.T) { w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/login", nil) req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password"))) + req.Header.Set("X-Forwarded-Proto", "https") router.ServeHTTP(w, req) assert.False(t, called) @@ -118,6 +120,26 @@ func TestBasicAuth401(t *testing.T) { assert.Equal(t, "Basic realm=\"Authorization Required\"", w.Header().Get("WWW-Authenticate")) } +func TestBasicAuth403OverHTTP(t *testing.T) { + called := false + accounts := Accounts{"foo": "bar"} + router := New() + router.Use(BasicAuth(accounts)) + router.GET("/login", func(c *Context) { + called = true + c.String(http.StatusOK, c.MustGet(AuthUserKey).(string)) + }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/login", nil) + req.Header.Set("Authorization", authorizationHeader("foo", "bar")) + // No X-Forwarded-Proto and no TLS — must be rejected before credentials are checked. + router.ServeHTTP(w, req) + + assert.False(t, called) + assert.Equal(t, http.StatusForbidden, w.Code) +} + func TestBasicAuth401WithCustomRealm(t *testing.T) { called := false accounts := Accounts{"foo": "bar"} @@ -131,6 +153,7 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) { w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/login", nil) req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password"))) + req.Header.Set("X-Forwarded-Proto", "https") router.ServeHTTP(w, req) assert.False(t, called) @@ -149,6 +172,7 @@ func TestBasicAuthForProxySucceed(t *testing.T) { w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/test", nil) req.Header.Set("Proxy-Authorization", authorizationHeader("admin", "password")) + req.Header.Set("X-Forwarded-Proto", "https") router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -168,9 +192,30 @@ func TestBasicAuthForProxy407(t *testing.T) { w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/test", nil) req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password"))) + req.Header.Set("X-Forwarded-Proto", "https") router.ServeHTTP(w, req) assert.False(t, called) assert.Equal(t, http.StatusProxyAuthRequired, w.Code) assert.Equal(t, "Basic realm=\"Proxy Authorization Required\"", w.Header().Get("Proxy-Authenticate")) } + +func TestBasicAuthForProxy403OverHTTP(t *testing.T) { + called := false + accounts := Accounts{"foo": "bar"} + router := New() + router.Use(BasicAuthForProxy(accounts, "")) + router.Any("/*proxyPath", func(c *Context) { + called = true + c.String(http.StatusOK, c.MustGet(AuthProxyUserKey).(string)) + }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/test", nil) + req.Header.Set("Proxy-Authorization", authorizationHeader("foo", "bar")) + // No X-Forwarded-Proto and no TLS — must be rejected before credentials are checked. + router.ServeHTTP(w, req) + + assert.False(t, called) + assert.Equal(t, http.StatusForbidden, w.Code) +}