mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 19:42:26 -05:00 
			
		
		
		
	* Set frame-ancestors in the CSP This ensures we can't be loaded/embedded in an iframe. It also sets the older X-Frame-Options for fallback. * Disable MIME type sniffing * Set Referrer-Policy This sets the policy such that browsers will never send the Referer header along with a request, unless it's a request to the same protocol, host/domain and port. Basically, only send it when navigating through our own UI, but not anything external. The default is strict-origin-when-cross-origin when unset, which sends the Referer header for requests unless it's going from HTTPS to HTTP (i.e a security downgrade, hence the 'strict').
		
			
				
	
	
		
			153 lines
		
	
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			153 lines
		
	
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // GoToSocial
 | |
| // Copyright (C) GoToSocial Authors admin@gotosocial.org
 | |
| // SPDX-License-Identifier: AGPL-3.0-or-later
 | |
| //
 | |
| // This program is free software: you can redistribute it and/or modify
 | |
| // it under the terms of the GNU Affero General Public License as published by
 | |
| // the Free Software Foundation, either version 3 of the License, or
 | |
| // (at your option) any later version.
 | |
| //
 | |
| // This program is distributed in the hope that it will be useful,
 | |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| // GNU Affero General Public License for more details.
 | |
| //
 | |
| // You should have received a copy of the GNU Affero General Public License
 | |
| // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | |
| 
 | |
| package middleware
 | |
| 
 | |
| import (
 | |
| 	"strings"
 | |
| 
 | |
| 	"codeberg.org/gruf/go-debug"
 | |
| 	"github.com/gin-gonic/gin"
 | |
| )
 | |
| 
 | |
| func ContentSecurityPolicy(extraURIs ...string) gin.HandlerFunc {
 | |
| 	csp := BuildContentSecurityPolicy(extraURIs...)
 | |
| 
 | |
| 	return func(c *gin.Context) {
 | |
| 		// Inform the browser we only load
 | |
| 		// CSS/JS/media using the given policy.
 | |
| 		c.Header("Content-Security-Policy", csp)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func BuildContentSecurityPolicy(extraURIs ...string) string {
 | |
| 	const (
 | |
| 		defaultSrc = "default-src"
 | |
| 		objectSrc  = "object-src"
 | |
| 		imgSrc     = "img-src"
 | |
| 		mediaSrc   = "media-src"
 | |
| 		frames     = "frame-ancestors"
 | |
| 
 | |
| 		self = "'self'"
 | |
| 		none = "'none'"
 | |
| 		blob = "blob:"
 | |
| 	)
 | |
| 
 | |
| 	// CSP values keyed by directive.
 | |
| 	values := make(map[string][]string, 4)
 | |
| 
 | |
| 	/*
 | |
| 		default-src
 | |
| 		https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src
 | |
| 	*/
 | |
| 
 | |
| 	if !debug.DEBUG {
 | |
| 		// Restrictive 'self' policy
 | |
| 		values[defaultSrc] = []string{self}
 | |
| 	} else {
 | |
| 		// If debug is enabled, allow
 | |
| 		// serving things from localhost
 | |
| 		// as well (regardless of port).
 | |
| 		values[defaultSrc] = []string{
 | |
| 			self,
 | |
| 			"localhost:*",
 | |
| 			"ws://localhost:*",
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 		object-src
 | |
| 		https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/object-src
 | |
| 	*/
 | |
| 
 | |
| 	// Disallow object-src as recommended.
 | |
| 	values[objectSrc] = []string{none}
 | |
| 
 | |
| 	/*
 | |
| 		img-src
 | |
| 		https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/img-src
 | |
| 	*/
 | |
| 
 | |
| 	// Restrictive 'self' policy,
 | |
| 	// include extraURIs, and 'blob:'
 | |
| 	// for previewing uploaded images
 | |
| 	// (header, avi, emojis) in settings.
 | |
| 	values[imgSrc] = append(
 | |
| 		[]string{self, blob},
 | |
| 		extraURIs...,
 | |
| 	)
 | |
| 
 | |
| 	/*
 | |
| 		media-src
 | |
| 		https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/media-src
 | |
| 	*/
 | |
| 
 | |
| 	// Restrictive 'self' policy,
 | |
| 	// include extraURIs.
 | |
| 	values[mediaSrc] = append(
 | |
| 		[]string{self},
 | |
| 		extraURIs...,
 | |
| 	)
 | |
| 
 | |
| 	/*
 | |
| 		frame-ancestors
 | |
| 		https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors
 | |
| 	*/
 | |
| 
 | |
| 	// Don't allow embedding us in an iframe
 | |
| 	values[frames] = []string{none}
 | |
| 
 | |
| 	/*
 | |
| 		Assemble policy directives.
 | |
| 	*/
 | |
| 
 | |
| 	// Iterate through an ordered slice rather than
 | |
| 	// iterating through the map, since we want these
 | |
| 	// policyDirectives in a determinate order.
 | |
| 	policyDirectives := make([]string, 4)
 | |
| 	for i, directive := range []string{
 | |
| 		defaultSrc,
 | |
| 		objectSrc,
 | |
| 		imgSrc,
 | |
| 		mediaSrc,
 | |
| 	} {
 | |
| 		// Each policy directive should look like:
 | |
| 		// `[directive] [value1] [value2] [etc]`
 | |
| 
 | |
| 		// Get assembled values
 | |
| 		// for this directive.
 | |
| 		values := values[directive]
 | |
| 
 | |
| 		// Prepend values with
 | |
| 		// the directive name.
 | |
| 		directiveValues := append(
 | |
| 			[]string{directive},
 | |
| 			values...,
 | |
| 		)
 | |
| 
 | |
| 		// Space-separate them.
 | |
| 		policyDirective := strings.Join(directiveValues, " ")
 | |
| 
 | |
| 		// Done.
 | |
| 		policyDirectives[i] = policyDirective
 | |
| 	}
 | |
| 
 | |
| 	// Content-security-policy looks like this:
 | |
| 	// `Content-Security-Policy: <policy-directive>; <policy-directive>`
 | |
| 	// So join each policy directive appropriately.
 | |
| 	return strings.Join(policyDirectives, "; ")
 | |
| }
 |