fix: allow same-origin attachment previews (#4679)

This commit is contained in:
Dmitry
2026-06-29 12:10:52 +08:00
committed by GitHub
parent 78d668a2f2
commit 0c2f93bcd1
2 changed files with 64 additions and 9 deletions

View File

@@ -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"))
}

View File

@@ -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) {