Skip to content

Commit 06eedc2

Browse files
wxiaoguangGiteaBot
andauthored
Fix attachment Content-Security-Policy (go-gitea#37455)
See the comments. Others are not changed, only added a new rule for medias: `serveHeaderCspMedia` --------- Co-authored-by: Giteabot <teabot@gitea.io>
1 parent 6640e4f commit 06eedc2

2 files changed

Lines changed: 64 additions & 14 deletions

File tree

modules/httplib/serve.go

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,42 @@ type ServeHeaderOptions struct {
3737
LastModified time.Time
3838
}
3939

40+
const (
41+
// Disable JS execution on the same origin, since we serve the file from the same origin as Gitea server.
42+
// This rule can be relaxed in the future as long as it is properly sandboxed.
43+
// "style-src" is for SVG inline styles (from Display SVG files as images instead of text #14101)
44+
serveHeaderCspDefault = "default-src 'none'; style-src 'unsafe-inline'; sandbox"
45+
46+
// No sandbox attribute for PDF as it breaks rendering in at least Safari.
47+
// This should generally be safe as scripts inside PDF can not escape the PDF document.
48+
// See https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion.
49+
// HINT: PDF-RENDER-SANDBOX: PDF won't render in sandboxed context
50+
serveHeaderCspPdf = "default-src 'none'; style-src 'unsafe-inline'"
51+
52+
// For audios and videos, actually it doesn't really need CSP (just like Gitea <= 1.25)
53+
serveHeaderCspAudioVideo = ""
54+
)
55+
56+
func serveSetHeaderContentRelated(w http.ResponseWriter, contentType string) {
57+
header := w.Header()
58+
contentType = util.IfZero(contentType, typesniffer.MimeTypeApplicationOctetStream)
59+
header.Set("Content-Type", contentType)
60+
header.Set("X-Content-Type-Options", "nosniff")
61+
62+
csp := serveHeaderCspDefault
63+
if strings.HasPrefix(contentType, "application/pdf") {
64+
csp = serveHeaderCspPdf
65+
}
66+
if strings.HasPrefix(contentType, "video/") || strings.HasPrefix(contentType, "audio/") {
67+
csp = serveHeaderCspAudioVideo
68+
}
69+
if csp != "" {
70+
header.Set("Content-Security-Policy", csp)
71+
} else {
72+
header.Del("Content-Security-Policy")
73+
}
74+
}
75+
4076
// ServeSetHeaders sets necessary content serve headers
4177
func ServeSetHeaders(w http.ResponseWriter, opts ServeHeaderOptions) {
4278
header := w.Header()
@@ -46,24 +82,11 @@ func ServeSetHeaders(w http.ResponseWriter, opts ServeHeaderOptions) {
4682
w.Header().Add(gzhttp.HeaderNoCompression, "1")
4783
}
4884

49-
contentType := util.IfZero(opts.ContentType, typesniffer.MimeTypeApplicationOctetStream)
50-
header.Set("Content-Type", contentType)
51-
header.Set("X-Content-Type-Options", "nosniff")
85+
serveSetHeaderContentRelated(w, opts.ContentType)
5286

5387
if opts.ContentLength != nil {
5488
header.Set("Content-Length", strconv.FormatInt(*opts.ContentLength, 10))
5589
}
56-
57-
// Disable script execution of HTML/SVG files, since we serve the file from the same origin as Gitea server
58-
header.Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
59-
if strings.Contains(contentType, "application/pdf") {
60-
// no sandbox attribute for PDF as it breaks rendering in at least safari. this
61-
// should generally be safe as scripts inside PDF can not escape the PDF document
62-
// see https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion
63-
// HINT: PDF-RENDER-SANDBOX: PDF won't render in sandboxed context
64-
header.Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'")
65-
}
66-
6790
if opts.Filename != "" && opts.ContentDisposition != "" {
6891
header.Set("Content-Disposition", encodeContentDisposition(opts.ContentDisposition, path.Base(opts.Filename)))
6992
header.Set("Access-Control-Expose-Headers", "Content-Disposition")

modules/httplib/serve_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"strings"
1313
"testing"
1414

15+
"code.gitea.io/gitea/modules/typesniffer"
16+
1517
"github.com/stretchr/testify/assert"
1618
"github.com/stretchr/testify/require"
1719
)
@@ -106,3 +108,28 @@ func TestServeUserContentByFile(t *testing.T) {
106108
test(t, http.StatusPartialContent, data[1:])
107109
})
108110
}
111+
112+
func TestServeSetHeaderContentRelated(t *testing.T) {
113+
cases := []struct {
114+
contentType string
115+
csp string
116+
}{
117+
{"", serveHeaderCspDefault},
118+
{"any", serveHeaderCspDefault},
119+
{"application/pdf", serveHeaderCspPdf},
120+
{"application/pdf; other", serveHeaderCspPdf},
121+
{"audio/mp4", serveHeaderCspAudioVideo},
122+
{"video/ogg; other", serveHeaderCspAudioVideo},
123+
{typesniffer.MimeTypeImageSvg, serveHeaderCspDefault},
124+
}
125+
for _, c := range cases {
126+
w := httptest.NewRecorder()
127+
serveSetHeaderContentRelated(w, c.contentType)
128+
csp := w.Header().Get("Content-Security-Policy")
129+
assert.Equal(t, c.csp, csp, "content-type: %s", c.contentType)
130+
assert.Equal(t, "nosniff", w.Header().Get("X-Content-Type-Options")) // it should always be there
131+
}
132+
133+
// make sure sandboxed
134+
require.Contains(t, serveHeaderCspDefault, "; sandbox")
135+
}

0 commit comments

Comments
 (0)