fix: Add X-Frame-Options and CSP headers to Swagger UI endpoints (#26521)

Signed-off-by: rohansood10 <rohansood10@users.noreply.github.com>
Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: rohansood10 <rohansood10@users.noreply.github.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Rohan Sood 2026-04-03 17:58:32 -07:00 committed by GitHub
parent e21d471965
commit 68cbd05e52
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 28 additions and 4 deletions

View file

@ -11,20 +11,29 @@ import (
// filename of ReDoc script in UI's assets/scripts path
const redocScriptName = "redoc.standalone.js"
// withFrameOptions wraps an http.Handler to set headers that prevent iframe embedding (clickjacking protection).
func withFrameOptions(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Content-Security-Policy", "frame-ancestors 'none'")
h.ServeHTTP(w, r)
})
}
// ServeSwaggerUI serves the Swagger UI and JSON spec.
func ServeSwaggerUI(mux *http.ServeMux, swaggerJSON string, uiPath string, rootPath string) {
prefix := path.Dir(uiPath)
swaggerPath := path.Join(prefix, "swagger.json")
mux.HandleFunc(swaggerPath, func(w http.ResponseWriter, _ *http.Request) {
mux.Handle(swaggerPath, withFrameOptions(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = fmt.Fprint(w, swaggerJSON)
})
})))
specURL := path.Join(prefix, rootPath, "swagger.json")
scriptURL := path.Join(prefix, rootPath, "assets", "scripts", redocScriptName)
mux.Handle(uiPath, middleware.Redoc(middleware.RedocOpts{
mux.Handle(uiPath, withFrameOptions(middleware.Redoc(middleware.RedocOpts{
BasePath: prefix,
SpecURL: specURL,
Path: path.Base(uiPath),
RedocURL: scriptURL,
}, http.NotFoundHandler()))
}, http.NotFoundHandler())))
}

View file

@ -52,4 +52,19 @@ func TestSwaggerUI(t *testing.T) {
require.NoError(t, err)
require.Equalf(t, http.StatusOK, resp.StatusCode, "Was expecting status code 200 from swagger-ui, but got %d instead", resp.StatusCode)
require.NoError(t, resp.Body.Close())
// Verify clickjacking protection headers on swagger.json
require.Equal(t, "DENY", resp.Header.Get("X-Frame-Options"))
require.Equal(t, "frame-ancestors 'none'", resp.Header.Get("Content-Security-Policy"))
// Verify clickjacking protection headers on swagger-ui
uiReq, err := http.NewRequestWithContext(t.Context(), http.MethodGet, server+"/swagger-ui", http.NoBody)
require.NoError(t, err)
uiResp, err := http.DefaultClient.Do(uiReq)
require.NoError(t, err)
require.Equalf(t, http.StatusOK, uiResp.StatusCode, "Was expecting status code 200 from swagger-ui, but got %d instead", uiResp.StatusCode)
require.Equal(t, "DENY", uiResp.Header.Get("X-Frame-Options"))
require.Equal(t, "frame-ancestors 'none'", uiResp.Header.Get("Content-Security-Policy"))
require.NoError(t, uiResp.Body.Close())
}