diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index bcf58da3..b86a53e4 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -2109,3 +2109,21 @@ func TestParseConfigDumpOutput(t *testing.T) {
t.Fatal(err)
}
}
+
+func TestContentSecurityPolicy(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("CONTENT_SECURITY_POLICY", "default-src 'self' fonts.googleapis.com fonts.gstatic.com; img-src * data:; media-src *; frame-src *; style-src 'self' fonts.googleapis.com fonts.gstatic.com 'nonce-%s'")
+
+ parser := NewParser()
+ opts, err := parser.ParseEnvironmentVariables()
+ if err != nil {
+ t.Fatalf(`Parsing failure: %v`, err)
+ }
+
+ expected := "default-src 'self' fonts.googleapis.com fonts.gstatic.com; img-src * data:; media-src *; frame-src *; style-src 'self' fonts.googleapis.com fonts.gstatic.com 'nonce-%s'"
+ result := opts.ContentSecurityPolicy()
+
+ if result != expected {
+ t.Fatalf(`Unexpected CONTENT_SECURITY_POLICY value, got %v instead of %v`, result, expected)
+ }
+}
diff --git a/internal/config/options.go b/internal/config/options.go
index 89bff536..744af88b 100644
--- a/internal/config/options.go
+++ b/internal/config/options.go
@@ -85,6 +85,7 @@ const (
defaultWatchdog = true
defaultInvidiousInstance = "yewtu.be"
defaultWebAuthn = false
+ defaultContentSecurityPolicy = "default-src 'self'; img-src * data:; media-src *; frame-src *; style-src 'self' 'nonce-%s'; require-trusted-types-for 'script'; trusted-types ttpolicy;"
)
var defaultHTTPClientUserAgent = "Mozilla/5.0 (compatible; Miniflux/" + version.Version + "; +https://miniflux.app)"
@@ -169,6 +170,7 @@ type Options struct {
invidiousInstance string
mediaProxyPrivateKey []byte
webAuthn bool
+ contentSecurityPolicy string
}
// NewOptions returns Options with default values.
@@ -244,6 +246,7 @@ func NewOptions() *Options {
invidiousInstance: defaultInvidiousInstance,
mediaProxyPrivateKey: crypto.GenerateRandomBytes(16),
webAuthn: defaultWebAuthn,
+ contentSecurityPolicy: defaultContentSecurityPolicy,
}
}
@@ -620,6 +623,11 @@ func (o *Options) FilterEntryMaxAgeDays() int {
return o.filterEntryMaxAgeDays
}
+// ContentSecurityPolicy returns value for Content-Security-Policy meta tag.
+func (o *Options) ContentSecurityPolicy() string {
+ return o.contentSecurityPolicy
+}
+
// SortedOptions returns options as a list of key value pairs, sorted by keys.
func (o *Options) SortedOptions(redactSecret bool) []*Option {
var keyValues = map[string]interface{}{
@@ -697,6 +705,7 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
"WORKER_POOL_SIZE": o.workerPoolSize,
"YOUTUBE_EMBED_URL_OVERRIDE": o.youTubeEmbedUrlOverride,
"WEBAUTHN": o.webAuthn,
+ "CONTENT_SECURITY_POLICY": o.contentSecurityPolicy,
}
keys := make([]string, 0, len(keyValues))
diff --git a/internal/config/parser.go b/internal/config/parser.go
index 24704710..6ed0be8e 100644
--- a/internal/config/parser.go
+++ b/internal/config/parser.go
@@ -271,6 +271,8 @@ func (p *Parser) parseLines(lines []string) (err error) {
p.opts.invidiousInstance = parseString(value, defaultInvidiousInstance)
case "WEBAUTHN":
p.opts.webAuthn = parseBool(value, defaultWebAuthn)
+ case "CONTENT_SECURITY_POLICY":
+ p.opts.contentSecurityPolicy = parseString(value, defaultContentSecurityPolicy)
}
}
diff --git a/internal/template/templates/common/layout.html b/internal/template/templates/common/layout.html
index 19019c1e..728f26b5 100644
--- a/internal/template/templates/common/layout.html
+++ b/internal/template/templates/common/layout.html
@@ -36,8 +36,13 @@
{{ if and .user .user.Stylesheet }}
{{ $stylesheetNonce := nonce }}
-
-
+ {{ $containsNonce := contains .contentSecurityPolicy "nonce-%s" }}
+ {{ if $containsNonce }}
+ {{ noescape ( printf "" (printf .contentSecurityPolicy $stylesheetNonce ) ) }}
+ {{ else }}
+ {{ noescape ( printf "" .contentSecurityPolicy ) }}
+ {{ end }}
+
{{ else }}
{{ end }}
@@ -58,7 +63,6 @@
data-webauthn-delete-all-url="{{ route "webauthnDeleteAll" }}"
{{ end }}
{{ if .user }}{{ if not .user.KeyboardShortcuts }}data-disable-keyboard-shortcuts="true"{{ end }}{{ end }}>
-
{{ if .user }}
{{ t "skip_to_content" }}