aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorr <r@freesoftwareextremist.com>2023-10-15 15:53:44 +0000
committerr <r@freesoftwareextremist.com>2023-10-15 15:53:44 +0000
commit67b13c71baea56eeb15532ca1b1377f6da8d18ac (patch)
treec10bc1f71e283b431076fd376acf170906fa0188
parented521dd33d0d002c577a75e349136fed25b7fda5 (diff)
downloadbloat-67b13c71baea56eeb15532ca1b1377f6da8d18ac.tar.gz
bloat-67b13c71baea56eeb15532ca1b1377f6da8d18ac.zip
Use CSP header to restrict resource loading
This helps mitigate XSS exploits. Users will have to save the settings again to make the custom CSS work.
-rw-r--r--model/session.go2
-rw-r--r--service/service.go16
-rw-r--r--service/transport.go25
3 files changed, 37 insertions, 6 deletions
diff --git a/model/session.go b/model/session.go
index f9e4287..61a409c 100644
--- a/model/session.go
+++ b/model/session.go
@@ -27,6 +27,7 @@ type Settings struct {
AntiDopamineMode bool `json:"adm,omitempty"`
HideUnsupportedNotifs bool `json:"hun,omitempty"`
CSS string `json:"css,omitempty"`
+ CSSHash string `json:"cssh,omitempty"`
}
func NewSettings() *Settings {
@@ -43,5 +44,6 @@ func NewSettings() *Settings {
AntiDopamineMode: false,
HideUnsupportedNotifs: false,
CSS: "",
+ CSSHash: "",
}
}
diff --git a/service/service.go b/service/service.go
index 2f87fa3..c925b83 100644
--- a/service/service.go
+++ b/service/service.go
@@ -1,6 +1,8 @@
package service
import (
+ "crypto/sha256"
+ "encoding/base64"
"errors"
"fmt"
"mime/multipart"
@@ -1014,8 +1016,18 @@ func (s *service) SaveSettings(c *client, settings *model.Settings) (err error)
default:
return errInvalidArgument
}
- if len(settings.CSS) > 1<<20 {
- return errInvalidArgument
+ if len(settings.CSS) > 0 {
+ if len(settings.CSS) > 1<<20 {
+ return errInvalidArgument
+ }
+ // For some reason, browsers convert CRLF to LF before calculating
+ // the hash of the inline resources.
+ settings.CSS = strings.ReplaceAll(settings.CSS, "\x0d\x0a", "\x0a")
+
+ h := sha256.Sum256([]byte(settings.CSS))
+ settings.CSSHash = base64.StdEncoding.EncodeToString(h[:])
+ } else {
+ settings.CSSHash = ""
}
c.s.Settings = *settings
return c.setSession(c.s)
diff --git a/service/transport.go b/service/transport.go
index 1182d6c..d032cce 100644
--- a/service/transport.go
+++ b/service/transport.go
@@ -26,6 +26,16 @@ const (
CSRF
)
+const csp = "default-src 'none';" +
+ " img-src *;" +
+ " media-src *;" +
+ " font-src *;" +
+ " child-src *;" +
+ " connect-src 'self';" +
+ " form-action 'self';" +
+ " script-src 'self';" +
+ " style-src 'self'"
+
func NewHandler(s *service, verbose bool, staticDir string) http.Handler {
r := mux.NewRouter()
@@ -58,14 +68,14 @@ func NewHandler(s *service, verbose bool, staticDir string) http.Handler {
}(time.Now())
}
- var ct string
+ h := c.w.Header()
switch rt {
case HTML:
- ct = "text/html; charset=utf-8"
+ h.Set("Content-Type", "text/html; charset=utf-8")
+ h.Set("Content-Security-Policy", csp)
case JSON:
- ct = "application/json"
+ h.Set("Content-Type", "application/json")
}
- c.w.Header().Add("Content-Type", ct)
err = c.authenticate(at, s.instance)
if err != nil {
@@ -73,6 +83,13 @@ func NewHandler(s *service, verbose bool, staticDir string) http.Handler {
return
}
+ // Override the CSP header to allow custom CSS
+ if rt == HTML && len(c.s.Settings.CSS) > 0 &&
+ len(c.s.Settings.CSSHash) > 0 {
+ v := fmt.Sprintf("%s 'sha256-%s'", csp, c.s.Settings.CSSHash)
+ h.Set("Content-Security-Policy", v)
+ }
+
err = f(c)
if err != nil {
writeError(c, err, rt, req.Method == http.MethodGet)