security: suppress Allow-Credentials on wildcard CORS origin (CORS-1) (#3980)

fix(security): suppress Allow-Credentials on wildcard CORS origin (CORS-1)

Per CORS spec §3.2, Access-Control-Allow-Credentials must not be
"true" when Access-Control-Allow-Origin is the wildcard "*".

ACHeadersMiddleware (pkg/common/http_server.go) and
getUIHeadersHandler (pkg/api/routes.go) now only emit the
credentials header when an explicit, non-empty AllowOrigin is
configured.  Deployments that leave AllowOrigin blank (default
wildcard) no longer produce a contradictory header pair.

Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com>
This commit is contained in:
Ramkumar Chinchani
2026-04-18 11:14:52 -07:00
committed by GitHub
parent eadc9b65ed
commit bfc59ad120
3 changed files with 44 additions and 5 deletions
+7 -3
View File
@@ -248,9 +248,12 @@ func getUIHeadersHandler(config *config.Config, allowedMethods ...string) func(h
response.Header().Set("Access-Control-Allow-Headers",
"Authorization,content-type,"+constants.SessionClientHeaderName)
// Get auth config safely
// Access-Control-Allow-Credentials must not be "true" when
// Access-Control-Allow-Origin is the wildcard "*" (CORS spec §3.2).
// Only advertise credentials support when an explicit origin is set.
authConfig := config.CopyAuthConfig()
if authConfig.IsBasicAuthnEnabled() {
allowOrigin := strings.TrimSpace(config.GetAllowOrigin())
if authConfig.IsBasicAuthnEnabled() && allowOrigin != "" && allowOrigin != "*" {
response.Header().Set("Access-Control-Allow-Credentials", "true")
}
@@ -517,7 +520,8 @@ type ExtensionList struct {
func (rh *RouteHandler) GetManifest(response http.ResponseWriter, request *http.Request) {
// Get auth config safely
authConfig := rh.c.Config.CopyAuthConfig()
if authConfig.IsBasicAuthnEnabled() {
allowOrigin := strings.TrimSpace(rh.c.Config.GetAllowOrigin())
if authConfig.IsBasicAuthnEnabled() && allowOrigin != "" && allowOrigin != "*" {
response.Header().Set("Access-Control-Allow-Credentials", "true")
}
+32
View File
@@ -241,6 +241,38 @@ func TestRoutes(t *testing.T) {
defer resp.Body.Close()
So(resp, ShouldNotBeNil)
So(resp.Header.Get("Access-Control-Allow-Credentials"), ShouldEqual, "")
So(resp.StatusCode, ShouldEqual, http.StatusNotFound)
})
Convey("Get manifest with explicit AllowOrigin emits credentials header", func() {
ctlr.StoreController.DefaultStore = &mocks.MockedImageStore{
GetImageManifestFn: func(repo string, reference string) ([]byte, godigest.Digest, string, error) {
return []byte{}, "", "", zerr.ErrRepoBadVersion
},
}
originalAllowOrigin := ctlr.Config.HTTP.AllowOrigin
ctlr.Config.HTTP.AllowOrigin = "https://example.com"
defer func() {
ctlr.Config.HTTP.AllowOrigin = originalAllowOrigin
}()
request, _ := http.NewRequestWithContext(context.TODO(), http.MethodGet, baseURL, nil)
request = mux.SetURLVars(request, map[string]string{
"name": "test",
"reference": "b8b1231908844a55c251211c7a67ae3c809fb86a081a8eeb4a715e6d7d65625c",
})
response := httptest.NewRecorder()
rthdlr.GetManifest(response, request)
resp := response.Result()
defer resp.Body.Close()
So(resp, ShouldNotBeNil)
So(resp.Header.Get("Access-Control-Allow-Credentials"), ShouldEqual, "true")
So(resp.StatusCode, ShouldEqual, http.StatusNotFound)
})
+5 -2
View File
@@ -38,9 +38,12 @@ func ACHeadersMiddleware(config *config.Config, allowedMethods ...string) mux.Mi
resp.Header().Set("Access-Control-Allow-Methods", allowedMethodsValue)
resp.Header().Set("Access-Control-Allow-Headers", "Authorization,content-type,"+constants.SessionClientHeaderName)
// Get auth config safely
// Access-Control-Allow-Credentials must not be "true" when
// Access-Control-Allow-Origin is the wildcard "*" (CORS spec §3.2).
// Only advertise credentials support when an explicit origin is set.
authConfig := config.CopyAuthConfig()
if authConfig.IsBasicAuthnEnabled() {
allowOrigin := strings.TrimSpace(config.GetAllowOrigin())
if authConfig.IsBasicAuthnEnabled() && allowOrigin != "" && allowOrigin != "*" {
resp.Header().Set("Access-Control-Allow-Credentials", "true")
}