Bundle swagger-ui assets locally for /docs

- Download swagger-ui-dist@5.32.0 and embed
  swagger-ui-bundle.js and swagger-ui.css into
  web/static/ so they are served from the same origin
- Update docs.html to reference /static/ paths instead
  of unpkg.com CDN URLs
- Add GET /static/swagger-ui-bundle.js and
  GET /static/swagger-ui.css handlers serving the
  embedded bytes with correct Content-Type headers
- Fixes /docs breakage caused by CSP default-src 'self'
  blocking external CDN scripts and stylesheets

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
2026-03-15 19:19:12 -07:00
parent cb661bb8f5
commit 8bf5c9033f
4 changed files with 25 additions and 2 deletions

View File

@@ -232,6 +232,14 @@ func (s *Server) Handler() http.Handler {
if err != nil {
panic(fmt.Sprintf("server: read openapi.yaml: %v", err))
}
swaggerJS, err := fs.ReadFile(staticFS, "swagger-ui-bundle.js")
if err != nil {
panic(fmt.Sprintf("server: read swagger-ui-bundle.js: %v", err))
}
swaggerCSS, err := fs.ReadFile(staticFS, "swagger-ui.css")
if err != nil {
panic(fmt.Sprintf("server: read swagger-ui.css: %v", err))
}
// Security (DEF-09): apply defensive HTTP headers to the docs handlers.
// The Swagger UI page at /docs loads JavaScript from the same origin
// and renders untrusted content (API descriptions), so it benefits from
@@ -246,6 +254,16 @@ func (s *Server) Handler() http.Handler {
w.WriteHeader(http.StatusOK)
_, _ = w.Write(specYAML)
})))
mux.Handle("GET /static/swagger-ui-bundle.js", docsSecurityHeaders(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/javascript")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(swaggerJS)
})))
mux.Handle("GET /static/swagger-ui.css", docsSecurityHeaders(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/css")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(swaggerCSS)
})))
// Vault endpoints (exempt from sealed middleware and auth).
unsealRateLimit := middleware.RateLimit(3, 5, trustedProxy)