mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-06 17:08:27 +02:00
feat(security): set X-Content-Type-Options: nosniff by default (#37354)
Fixes #37316. --------- Signed-off-by: SAY-5 <SAY-5@users.noreply.github.com> Co-authored-by: SAY-5 <SAY-5@users.noreply.github.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
parent
1483291a87
commit
6826321570
@ -525,8 +525,11 @@ INTERNAL_TOKEN =
|
||||
;; Set to "enforced", to force users to enroll into Two-Factor Authentication, users without 2FA have no access to repositories via API or web.
|
||||
;TWO_FACTOR_AUTH =
|
||||
;;
|
||||
;; The value of the X-Frame-Options HTTP header for HTML responses. Use "unset" to remove the header.
|
||||
;; The value of the X-Frame-Options HTTP header for all responses. Use "unset" to remove the header.
|
||||
;X_FRAME_OPTIONS = SAMEORIGIN
|
||||
;;
|
||||
;; The value of the X-Content-Type-Options HTTP header for all responses. Use "unset" to remove the header.
|
||||
;X_CONTENT_TYPE_OPTIONS = nosniff
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@ -16,9 +16,11 @@ import (
|
||||
// Security settings
|
||||
var Security = struct {
|
||||
// TODO: move more settings to this struct in future
|
||||
XFrameOptions string
|
||||
XFrameOptions string
|
||||
XContentTypeOptions string
|
||||
}{
|
||||
XFrameOptions: "SAMEORIGIN",
|
||||
XFrameOptions: "SAMEORIGIN",
|
||||
XContentTypeOptions: "nosniff",
|
||||
}
|
||||
|
||||
var (
|
||||
@ -154,6 +156,8 @@ func loadSecurityFrom(rootCfg ConfigProvider) {
|
||||
Security.XFrameOptions = rootCfg.Section("cors").Key("X_FRAME_OPTIONS").MustString(Security.XFrameOptions)
|
||||
}
|
||||
|
||||
Security.XContentTypeOptions = sec.Key("X_CONTENT_TYPE_OPTIONS").MustString(Security.XContentTypeOptions)
|
||||
|
||||
twoFactorAuth := sec.Key("TWO_FACTOR_AUTH").String()
|
||||
switch twoFactorAuth {
|
||||
case "":
|
||||
|
||||
@ -865,7 +865,6 @@ func checkDeprecatedAuthMethods(ctx *context.APIContext) {
|
||||
func Routes() *web.Router {
|
||||
m := web.NewRouter()
|
||||
|
||||
m.BeforeRouting(securityHeaders())
|
||||
if setting.CORSConfig.Enabled {
|
||||
m.BeforeRouting(cors.Handler(cors.Options{
|
||||
AllowedOrigins: setting.CORSConfig.AllowDomain,
|
||||
@ -1749,14 +1748,3 @@ func Routes() *web.Router {
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func securityHeaders() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
// CORB: https://www.chromium.org/Home/chromium-security/corb-for-developers
|
||||
// http://stackoverflow.com/a/3146618/244009
|
||||
resp.Header().Set("x-content-type-options", "nosniff")
|
||||
next.ServeHTTP(resp, req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,10 +33,6 @@ func renderServerErrorPage(w http.ResponseWriter, req *http.Request, respCode in
|
||||
}
|
||||
|
||||
httpcache.SetCacheControlInHeader(w.Header(), &httpcache.CacheControlOptions{NoTransform: true})
|
||||
if setting.Security.XFrameOptions != "unset" {
|
||||
w.Header().Set(`X-Frame-Options`, setting.Security.XFrameOptions)
|
||||
}
|
||||
|
||||
tmplCtx := context.NewTemplateContextForWeb(reqctx.FromContext(req.Context()), req, middleware.Locale(w, req))
|
||||
w.WriteHeader(respCode)
|
||||
|
||||
|
||||
@ -28,6 +28,7 @@ func ProtocolMiddlewares() (handlers []any) {
|
||||
// the order is important
|
||||
handlers = append(handlers, ChiRoutePathHandler()) // make sure chi has correct paths
|
||||
handlers = append(handlers, RequestContextHandler()) // prepare the context and panic recovery
|
||||
handlers = append(handlers, SecurityHeadersHandler())
|
||||
|
||||
if setting.ReverseProxyLimit > 0 && len(setting.ReverseProxyTrustedProxies) > 0 {
|
||||
handlers = append(handlers, ForwardedHeadersHandler(setting.ReverseProxyLimit, setting.ReverseProxyTrustedProxies))
|
||||
@ -48,6 +49,21 @@ func ProtocolMiddlewares() (handlers []any) {
|
||||
return handlers
|
||||
}
|
||||
|
||||
// SecurityHeadersHandler sets headers globally for every response that leaves Gitea.
|
||||
func SecurityHeadersHandler() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
if setting.Security.XContentTypeOptions != "unset" {
|
||||
resp.Header().Set("X-Content-Type-Options", setting.Security.XContentTypeOptions)
|
||||
}
|
||||
if setting.Security.XFrameOptions != "unset" {
|
||||
resp.Header().Set("X-Frame-Options", setting.Security.XFrameOptions)
|
||||
}
|
||||
next.ServeHTTP(resp, req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func RequestContextHandler() func(h http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(respOrig http.ResponseWriter, req *http.Request) {
|
||||
|
||||
@ -196,10 +196,6 @@ func Contexter() func(next http.Handler) http.Handler {
|
||||
|
||||
httpcache.SetCacheControlInHeader(ctx.Resp.Header(), &httpcache.CacheControlOptions{NoTransform: true})
|
||||
|
||||
if setting.Security.XFrameOptions != "unset" {
|
||||
ctx.Resp.Header().Set(`X-Frame-Options`, setting.Security.XFrameOptions)
|
||||
}
|
||||
|
||||
ctx.Data["SystemConfig"] = setting.Config()
|
||||
|
||||
ctx.Data["ShowTwoFactorRequiredMessage"] = ctx.DoerNeedTwoFactorAuth()
|
||||
|
||||
@ -12,9 +12,14 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRenderFileSVGIsInImgTag(t *testing.T) {
|
||||
func TestView(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
t.Run("RenderFileSVGIsInImgTag", testRenderFileSVGIsInImgTag)
|
||||
t.Run("CommitListActions", testCommitListActions)
|
||||
t.Run("SecurityHeadersDefaults", testSecurityHeadersDefaults)
|
||||
}
|
||||
|
||||
func testRenderFileSVGIsInImgTag(t *testing.T) {
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
req := NewRequest(t, "GET", "/user2/repo2/src/branch/master/line.svg")
|
||||
@ -26,8 +31,7 @@ func TestRenderFileSVGIsInImgTag(t *testing.T) {
|
||||
assert.Equal(t, "/user2/repo2/raw/branch/master/line.svg", src)
|
||||
}
|
||||
|
||||
func TestCommitListActions(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
func testCommitListActions(t *testing.T) {
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
t.Run("WikiRevisionList", func(t *testing.T) {
|
||||
@ -65,3 +69,15 @@ func TestCommitListActions(t *testing.T) {
|
||||
AssertHTMLElement(t, htmlDoc, `.commit-list .view-commit-path`, true)
|
||||
})
|
||||
}
|
||||
|
||||
func testSecurityHeadersDefaults(t *testing.T) {
|
||||
assertSecurityHeaders := func(t *testing.T, uri string) {
|
||||
req := NewRequest(t, "GET", uri)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
assert.Equal(t, "nosniff", resp.Header().Get("X-Content-Type-Options"))
|
||||
assert.Equal(t, "SAMEORIGIN", resp.Header().Get("X-Frame-Options"))
|
||||
}
|
||||
assertSecurityHeaders(t, "/")
|
||||
assertSecurityHeaders(t, "/api/v1/version")
|
||||
assertSecurityHeaders(t, "/assets/img/favicon.png")
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user