mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-30 05:07:26 +00:00
Fix attachment Content-Security-Policy (#37455)
See the comments. Others are not changed, only added a new rule for medias: `serveHeaderCspMedia` --------- Co-authored-by: Giteabot <teabot@gitea.io>
This commit is contained in:
@@ -37,6 +37,42 @@ type ServeHeaderOptions struct {
|
||||
LastModified time.Time
|
||||
}
|
||||
|
||||
const (
|
||||
// Disable JS execution on the same origin, since we serve the file from the same origin as Gitea server.
|
||||
// This rule can be relaxed in the future as long as it is properly sandboxed.
|
||||
// "style-src" is for SVG inline styles (from Display SVG files as images instead of text #14101)
|
||||
serveHeaderCspDefault = "default-src 'none'; style-src 'unsafe-inline'; sandbox"
|
||||
|
||||
// No sandbox attribute for PDF as it breaks rendering in at least Safari.
|
||||
// This should generally be safe as scripts inside PDF can not escape the PDF document.
|
||||
// See https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion.
|
||||
// HINT: PDF-RENDER-SANDBOX: PDF won't render in sandboxed context
|
||||
serveHeaderCspPdf = "default-src 'none'; style-src 'unsafe-inline'"
|
||||
|
||||
// For audios and videos, actually it doesn't really need CSP (just like Gitea <= 1.25)
|
||||
serveHeaderCspAudioVideo = ""
|
||||
)
|
||||
|
||||
func serveSetHeaderContentRelated(w http.ResponseWriter, contentType string) {
|
||||
header := w.Header()
|
||||
contentType = util.IfZero(contentType, typesniffer.MimeTypeApplicationOctetStream)
|
||||
header.Set("Content-Type", contentType)
|
||||
header.Set("X-Content-Type-Options", "nosniff")
|
||||
|
||||
csp := serveHeaderCspDefault
|
||||
if strings.HasPrefix(contentType, "application/pdf") {
|
||||
csp = serveHeaderCspPdf
|
||||
}
|
||||
if strings.HasPrefix(contentType, "video/") || strings.HasPrefix(contentType, "audio/") {
|
||||
csp = serveHeaderCspAudioVideo
|
||||
}
|
||||
if csp != "" {
|
||||
header.Set("Content-Security-Policy", csp)
|
||||
} else {
|
||||
header.Del("Content-Security-Policy")
|
||||
}
|
||||
}
|
||||
|
||||
// ServeSetHeaders sets necessary content serve headers
|
||||
func ServeSetHeaders(w http.ResponseWriter, opts ServeHeaderOptions) {
|
||||
header := w.Header()
|
||||
@@ -46,24 +82,11 @@ func ServeSetHeaders(w http.ResponseWriter, opts ServeHeaderOptions) {
|
||||
w.Header().Add(gzhttp.HeaderNoCompression, "1")
|
||||
}
|
||||
|
||||
contentType := util.IfZero(opts.ContentType, typesniffer.MimeTypeApplicationOctetStream)
|
||||
header.Set("Content-Type", contentType)
|
||||
header.Set("X-Content-Type-Options", "nosniff")
|
||||
serveSetHeaderContentRelated(w, opts.ContentType)
|
||||
|
||||
if opts.ContentLength != nil {
|
||||
header.Set("Content-Length", strconv.FormatInt(*opts.ContentLength, 10))
|
||||
}
|
||||
|
||||
// Disable script execution of HTML/SVG files, since we serve the file from the same origin as Gitea server
|
||||
header.Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
|
||||
if strings.Contains(contentType, "application/pdf") {
|
||||
// no sandbox attribute for PDF as it breaks rendering in at least safari. this
|
||||
// should generally be safe as scripts inside PDF can not escape the PDF document
|
||||
// see https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion
|
||||
// HINT: PDF-RENDER-SANDBOX: PDF won't render in sandboxed context
|
||||
header.Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'")
|
||||
}
|
||||
|
||||
if opts.Filename != "" && opts.ContentDisposition != "" {
|
||||
header.Set("Content-Disposition", encodeContentDisposition(opts.ContentDisposition, path.Base(opts.Filename)))
|
||||
header.Set("Access-Control-Expose-Headers", "Content-Disposition")
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/typesniffer"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -106,3 +108,28 @@ func TestServeUserContentByFile(t *testing.T) {
|
||||
test(t, http.StatusPartialContent, data[1:])
|
||||
})
|
||||
}
|
||||
|
||||
func TestServeSetHeaderContentRelated(t *testing.T) {
|
||||
cases := []struct {
|
||||
contentType string
|
||||
csp string
|
||||
}{
|
||||
{"", serveHeaderCspDefault},
|
||||
{"any", serveHeaderCspDefault},
|
||||
{"application/pdf", serveHeaderCspPdf},
|
||||
{"application/pdf; other", serveHeaderCspPdf},
|
||||
{"audio/mp4", serveHeaderCspAudioVideo},
|
||||
{"video/ogg; other", serveHeaderCspAudioVideo},
|
||||
{typesniffer.MimeTypeImageSvg, serveHeaderCspDefault},
|
||||
}
|
||||
for _, c := range cases {
|
||||
w := httptest.NewRecorder()
|
||||
serveSetHeaderContentRelated(w, c.contentType)
|
||||
csp := w.Header().Get("Content-Security-Policy")
|
||||
assert.Equal(t, c.csp, csp, "content-type: %s", c.contentType)
|
||||
assert.Equal(t, "nosniff", w.Header().Get("X-Content-Type-Options")) // it should always be there
|
||||
}
|
||||
|
||||
// make sure sandboxed
|
||||
require.Contains(t, serveHeaderCspDefault, "; sandbox")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user