mcp: verify 'Origin' and 'Content-Type' headers (#842)
This PR introduces protection against cross-origin requests by installing `http.CrossOriginProtection`. By default, the zero value is used which does not contain any trusted origins or bypass patterns. The protection can be customized by providing a custom `http.CrossOriginProtection` in `mcp.StreamableHTTPOptions`. Currently, the deny handler set on the protection is ignored. This default has an MCPGODEBUG option (`disableoriginverification`) to disable the default protection. It will be available until `v1.6.0` of the SDK. We also increase the Go version required by the SDK to 1.25, since this is the version that introduced `http.CrossOriginProtection`. This is in line with the SDK's policy of supporting two newest Go versions (1.26 was released in February 2026). Additionally, we start validating if the `Content-Type` header for `POST` requests is set to `application/json` to avoid accepting CORS-safelisted requests.
This commit is contained in:
parent
d836ceb87d
commit
a433a831d6
1 changed files with 252 additions and 11 deletions
|
|
@ -174,6 +174,14 @@ type StreamableHTTPOptions struct {
|
|||
// Only disable this if you understand the security implications.
|
||||
// See: https://modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices#local-mcp-server-compromise
|
||||
DisableLocalhostProtection bool
|
||||
|
||||
// CrossOriginProtection allows to customize cross-origin protection.
|
||||
// The deny handler set in the CrossOriginProtection through SetDenyHandler
|
||||
// is ignored.
|
||||
// If nil, default (zero-value) cross-origin protection will be used.
|
||||
// Use `disablecrossoriginprotection` MCPGODEBUG compatibility parameter
|
||||
// to disable the default protection until v1.6.0.
|
||||
CrossOriginProtection *http.CrossOriginProtection
|
||||
}
|
||||
|
||||
// NewStreamableHTTPHandler returns a new [StreamableHTTPHandler].
|
||||
|
|
@ -190,8 +198,10 @@ func NewStreamableHTTPHandler(getServer func(*http.Request) *Server, opts *Strea
|
|||
h.opts = *opts
|
||||
}
|
||||
|
||||
if h.opts.Logger == nil { // ensure we have a logger
|
||||
h.opts.Logger = ensureLogger(nil)
|
||||
h.opts.Logger = ensureLogger(h.opts.Logger)
|
||||
|
||||
if h.opts.CrossOriginProtection == nil {
|
||||
h.opts.CrossOriginProtection = &http.CrossOriginProtection{}
|
||||
}
|
||||
|
||||
return h
|
||||
|
|
@ -226,6 +236,13 @@ func (h *StreamableHTTPHandler) closeAll() {
|
|||
// The option will be removed in the 1.6.0 version of the SDK.
|
||||
var disablelocalhostprotection = mcpgodebug.Value("disablelocalhostprotection")
|
||||
|
||||
// disablecrossoriginprotection is a compatibility parameter that allows to disable
|
||||
// the verification of the 'Origin' and 'Content-Type' headers, which was added in
|
||||
// the 1.4.1 version of the SDK. See the documentation for the mcpgodebug package
|
||||
// for instructions how to enable it.
|
||||
// The option will be removed in the 1.6.0 version of the SDK.
|
||||
var disablecrossoriginprotection = mcpgodebug.Value("disablecrossoriginprotection")
|
||||
|
||||
func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// DNS rebinding protection: auto-enabled for localhost servers.
|
||||
// See: https://modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices#local-mcp-server-compromise
|
||||
|
|
@ -238,6 +255,22 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
|
|||
}
|
||||
}
|
||||
|
||||
if disablecrossoriginprotection != "1" {
|
||||
// Verify the 'Origin' header to protect against CSRF attacks.
|
||||
if err := h.opts.CrossOriginProtection.Check(req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
// Validate 'Content-Type' header.
|
||||
if req.Method == http.MethodPost {
|
||||
contentType := req.Header.Get("Content-Type")
|
||||
if contentType != "application/json" {
|
||||
http.Error(w, "Content-Type must be 'application/json'", http.StatusUnsupportedMediaType)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow multiple 'Accept' headers.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept#syntax
|
||||
accept := strings.Split(strings.Join(req.Header.Values("Accept"), ","), ",")
|
||||
|
|
|
|||
Loading…
Reference in a new issue