feat(security): set X-Content-Type-Options: nosniff by default#37354
feat(security): set X-Content-Type-Options: nosniff by default#37354wxiaoguang merged 11 commits intogo-gitea:mainfrom
Conversation
Gitea already sends X-Frame-Options: SAMEORIGIN on HTML responses but never emits X-Content-Type-Options. Browsers then fall back to content sniffing and can execute user-uploaded files as scripts when the Content-Type is missing or ambiguous - a widely-recommended security default that go-gitea#37316 requested. Add Security.XContentTypeOptions defaulting to 'nosniff', emit it from services/context alongside X-Frame-Options, and expose X_CONTENT_TYPE_OPTIONS in app.example.ini. 'unset' removes the header, matching the existing X_FRAME_OPTIONS escape hatch. Fixes go-gitea#37316 Signed-off-by: SAY-5 <SAY-5@users.noreply.github.com>
Co-Authored-By: Claude (Opus 4.7) <noreply@anthropic.com>
|
Fixup done in 4ca393d.
|
Co-Authored-By: Claude (Opus 4.7) <noreply@anthropic.com>
|
Note that API responses already set this header in https://github.com/go-gitea/gitea/blob/main/routers/api/v1/api.go#L1749, so this change fills the gap for web router responses. The following endpoints still don't set it after this change: I don't see why not set this header on all responses without exceptions, maybe it should be refactored to really apply globally, so maybe we should move this |
Because your code was only added to "web" middlewares. |
|
Doing it on web routes is an improvement, no doubt. But such a header ideally wants to be configured on all routes without exceptions. |
|
See ProtocolMiddlewares |
|
Well, Your AI already used that middleware: |
… handler Per @wxiaoguang and @silverwind on go-gitea#37354: X-Content-Type-Options should be applied on *every* response Gitea emits, not just the web UI and /api/v1. The gaps called out in review — /api/internal/*, /api/healthz, /captcha/*, /metrics, /robots.txt, /ssh_info — all go through ProtocolMiddlewares (installed by routers/init.go and routers/install/routes.go), so that is the right home. - New routers/common.SecurityHeadersHandler sets X-Content-Type-Options (honouring the existing "unset" escape hatch on setting.Security.XContentTypeOptions) globally and is now registered from ProtocolMiddlewares. - Remove the duplicate X-Content-Type-Options Set in services/context/context.go; the web middleware now only sets X-Frame-Options, which remains web-UI-specific. - Remove the private securityHeaders() helper in routers/api/v1/api.go and the m.BeforeRouting(securityHeaders()) registration: both are subsumed by the global handler. No behaviour change for the X-Frame-Options header (still web-UI only) and no change for operators who set X_CONTENT_TYPE_OPTIONS = unset. `go build ./routers/... ./services/...` + `go test ./routers/common/...` green locally.
|
Thanks @wxiaoguang — lifted the handler into
|
Co-Authored-By: Claude (Opus 4.7) <noreply@anthropic.com>
|
@SAY-5 Thank you for your PR, but please discuss the issues by human. https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md#ai-contribution-policy |
|
It's fine to post AI responses to PRs but those should always be paired with an actual human response too. @wxiaoguang approve first. |
Oops, forgot. Approved. |
Signed-off-by: silverwind <me@silverwind.io>
Signed-off-by: silverwind <me@silverwind.io>
Signed-off-by: silverwind <me@silverwind.io>
* origin/main: feat(security): set X-Content-Type-Options: nosniff by default (go-gitea#37354) Refactor pull request view (1) (go-gitea#37380) Improve AGENTS.md (go-gitea#37382) Remove dead CSS (go-gitea#37376) Add pr-review e2e test and speed up e2e tests (go-gitea#37345) Drop Fomantic tab, checkbox and form patches (go-gitea#37377)
* origin/main: Stabilize e2e logout propagation test (go-gitea#37403) refactor: serve site manifest via `/assets/site-manifest.json` endpoint (go-gitea#37405) feat(security): set X-Content-Type-Options: nosniff by default (go-gitea#37354) # Conflicts: # tests/e2e/events.test.ts
* origin/main: (51 commits) Fix color regressions, add `priority` color (go-gitea#37417) [skip ci] Updated translations via Crowdin Stabilize e2e logout propagation test (go-gitea#37403) refactor: serve site manifest via `/assets/site-manifest.json` endpoint (go-gitea#37405) feat(security): set X-Content-Type-Options: nosniff by default (go-gitea#37354) Refactor pull request view (1) (go-gitea#37380) Improve AGENTS.md (go-gitea#37382) Remove dead CSS (go-gitea#37376) Add pr-review e2e test and speed up e2e tests (go-gitea#37345) Drop Fomantic tab, checkbox and form patches (go-gitea#37377) fix: dump with default zip type produces uncompressed zip (go-gitea#37401) Allow fast-forward-only merge when signed commits are required (go-gitea#37335) Introduce `ActionRunAttempt` to represent each execution of a run (go-gitea#37119) Move review request functions to a standalone file (go-gitea#37358) Fix repo init README EOL (go-gitea#37388) Fix org team assignee/reviewer lookups for team member permissions (go-gitea#37365) Remove external service dependencies in migration tests (go-gitea#36866) Extend issue context popup beyond markdown content (go-gitea#36908) fix: commit status reporting (go-gitea#37372) Support for Custom URI Schemes in OAuth2 Redirect URIs (go-gitea#37356) ...
Summary
Fixes #37316.
Gitea already sends
X-Frame-Options: SAMEORIGINon HTML responses by default but never emitsX-Content-Type-Options. Without that header, browsers fall back to content sniffing and can execute user-uploaded files as scripts when theContent-Typeis missing or ambiguous. Per every modern hardening guide (OWASP, MDN, Mozilla Observatory),nosniffis the correct default.Fix
Security.XContentTypeOptionssetting, defaulting to"nosniff".services/contextalongsideX-Frame-Optionsif the configured value is not"unset".X_CONTENT_TYPE_OPTIONSin[security]ofapp.example.ini.unsetremoves the header, matching the existingX_FRAME_OPTIONSescape hatch for operators who want the raw HTTP behaviour.Test plan
go build ./modules/setting/... ./services/context/...cleango vet ./modules/setting/... ./services/context/...cleango test ./modules/setting/...greencurl -Iand confirmX-Content-Type-Options: nosniffis in the response by default; setX_CONTENT_TYPE_OPTIONS = unsetin[security]and confirm it's absent.No behaviour change for operators who already set their own response header via a reverse proxy — the nosniff value is a safe no-op when upstream sets the same header.