mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-26 20:45:37 +02:00
fix: allow same-origin attachment previews (#4679)
This commit is contained in:
@@ -1,20 +1,43 @@
|
||||
package middleware
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const cspHeader = "default-src 'self'; " +
|
||||
const cspBaseHeader = "default-src 'self'; " +
|
||||
"script-src 'self'; " +
|
||||
"style-src 'self' 'unsafe-inline'; " +
|
||||
"img-src 'self' https: data:; " +
|
||||
"connect-src 'self' wss:; " +
|
||||
"connect-src 'self' wss:; "
|
||||
|
||||
const cspHeader = cspBaseHeader +
|
||||
"frame-ancestors 'none'; " +
|
||||
"object-src 'none'; " +
|
||||
"base-uri 'self'; " +
|
||||
"form-action 'self'"
|
||||
|
||||
const attachmentPreviewCSPHeader = cspBaseHeader +
|
||||
"frame-ancestors 'self'; " +
|
||||
"object-src 'none'; " +
|
||||
"base-uri 'self'; " +
|
||||
"form-action 'self'"
|
||||
|
||||
func ContentSecurityPolicy(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Security-Policy", cspHeader)
|
||||
w.Header().Set("Content-Security-Policy", contentSecurityPolicyForRequest(r))
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func contentSecurityPolicyForRequest(r *http.Request) string {
|
||||
if isAttachmentPreviewDocumentPath(r.URL.Path) {
|
||||
return attachmentPreviewCSPHeader
|
||||
}
|
||||
return cspHeader
|
||||
}
|
||||
|
||||
func isAttachmentPreviewDocumentPath(path string) bool {
|
||||
return strings.HasPrefix(path, "/api/attachments/") &&
|
||||
(strings.HasSuffix(path, "/download") || strings.HasSuffix(path, "/content"))
|
||||
}
|
||||
|
||||
@@ -17,16 +17,48 @@ func TestContentSecurityPolicy(t *testing.T) {
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
csp := rec.Header().Get("Content-Security-Policy")
|
||||
if csp == "" {
|
||||
t.Fatal("Content-Security-Policy header is missing")
|
||||
}
|
||||
|
||||
required := []string{
|
||||
assertCSPDirectives(t, csp, []string{
|
||||
"script-src 'self'",
|
||||
"object-src 'none'",
|
||||
"frame-ancestors 'none'",
|
||||
"base-uri 'self'",
|
||||
"form-action 'self'",
|
||||
})
|
||||
}
|
||||
|
||||
func TestContentSecurityPolicyAllowsSameOriginAttachmentPreviews(t *testing.T) {
|
||||
handler := ContentSecurityPolicy(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
for _, path := range []string{
|
||||
"/api/attachments/019f0dae-0315-79b7-b653-f55d6af90403/download",
|
||||
"/api/attachments/019f0dae-0315-79b7-b653-f55d6af90403/content",
|
||||
} {
|
||||
t.Run(path, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, path, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
csp := rec.Header().Get("Content-Security-Policy")
|
||||
assertCSPDirectives(t, csp, []string{
|
||||
"script-src 'self'",
|
||||
"object-src 'none'",
|
||||
"frame-ancestors 'self'",
|
||||
"base-uri 'self'",
|
||||
"form-action 'self'",
|
||||
})
|
||||
if strings.Contains(csp, "frame-ancestors 'none'") {
|
||||
t.Fatalf("attachment preview CSP must not block same-origin iframe embedding; got: %s", csp)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertCSPDirectives(t *testing.T, csp string, required []string) {
|
||||
t.Helper()
|
||||
if csp == "" {
|
||||
t.Fatal("Content-Security-Policy header is missing")
|
||||
}
|
||||
for _, directive := range required {
|
||||
if !strings.Contains(csp, directive) {
|
||||
|
||||
Reference in New Issue
Block a user