mirror of https://github.com/miniflux/v2.git
Add CONTENT_SECURITY_POLICY
This commit is contained in:
parent
2c4c845cd2
commit
19fb9675a4
|
@ -2109,3 +2109,21 @@ func TestParseConfigDumpOutput(t *testing.T) {
|
||||||
t.Fatal(err)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -85,6 +85,7 @@ const (
|
||||||
defaultWatchdog = true
|
defaultWatchdog = true
|
||||||
defaultInvidiousInstance = "yewtu.be"
|
defaultInvidiousInstance = "yewtu.be"
|
||||||
defaultWebAuthn = false
|
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)"
|
var defaultHTTPClientUserAgent = "Mozilla/5.0 (compatible; Miniflux/" + version.Version + "; +https://miniflux.app)"
|
||||||
|
@ -169,6 +170,7 @@ type Options struct {
|
||||||
invidiousInstance string
|
invidiousInstance string
|
||||||
mediaProxyPrivateKey []byte
|
mediaProxyPrivateKey []byte
|
||||||
webAuthn bool
|
webAuthn bool
|
||||||
|
contentSecurityPolicy string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOptions returns Options with default values.
|
// NewOptions returns Options with default values.
|
||||||
|
@ -244,6 +246,7 @@ func NewOptions() *Options {
|
||||||
invidiousInstance: defaultInvidiousInstance,
|
invidiousInstance: defaultInvidiousInstance,
|
||||||
mediaProxyPrivateKey: crypto.GenerateRandomBytes(16),
|
mediaProxyPrivateKey: crypto.GenerateRandomBytes(16),
|
||||||
webAuthn: defaultWebAuthn,
|
webAuthn: defaultWebAuthn,
|
||||||
|
contentSecurityPolicy: defaultContentSecurityPolicy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -620,6 +623,11 @@ func (o *Options) FilterEntryMaxAgeDays() int {
|
||||||
return o.filterEntryMaxAgeDays
|
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.
|
// SortedOptions returns options as a list of key value pairs, sorted by keys.
|
||||||
func (o *Options) SortedOptions(redactSecret bool) []*Option {
|
func (o *Options) SortedOptions(redactSecret bool) []*Option {
|
||||||
var keyValues = map[string]interface{}{
|
var keyValues = map[string]interface{}{
|
||||||
|
@ -697,6 +705,7 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
|
||||||
"WORKER_POOL_SIZE": o.workerPoolSize,
|
"WORKER_POOL_SIZE": o.workerPoolSize,
|
||||||
"YOUTUBE_EMBED_URL_OVERRIDE": o.youTubeEmbedUrlOverride,
|
"YOUTUBE_EMBED_URL_OVERRIDE": o.youTubeEmbedUrlOverride,
|
||||||
"WEBAUTHN": o.webAuthn,
|
"WEBAUTHN": o.webAuthn,
|
||||||
|
"CONTENT_SECURITY_POLICY": o.contentSecurityPolicy,
|
||||||
}
|
}
|
||||||
|
|
||||||
keys := make([]string, 0, len(keyValues))
|
keys := make([]string, 0, len(keyValues))
|
||||||
|
|
|
@ -271,6 +271,8 @@ func (p *Parser) parseLines(lines []string) (err error) {
|
||||||
p.opts.invidiousInstance = parseString(value, defaultInvidiousInstance)
|
p.opts.invidiousInstance = parseString(value, defaultInvidiousInstance)
|
||||||
case "WEBAUTHN":
|
case "WEBAUTHN":
|
||||||
p.opts.webAuthn = parseBool(value, defaultWebAuthn)
|
p.opts.webAuthn = parseBool(value, defaultWebAuthn)
|
||||||
|
case "CONTENT_SECURITY_POLICY":
|
||||||
|
p.opts.contentSecurityPolicy = parseString(value, defaultContentSecurityPolicy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,8 +36,13 @@
|
||||||
|
|
||||||
{{ if and .user .user.Stylesheet }}
|
{{ if and .user .user.Stylesheet }}
|
||||||
{{ $stylesheetNonce := nonce }}
|
{{ $stylesheetNonce := nonce }}
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src * data:; media-src *; frame-src *; style-src 'self' 'nonce-{{ $stylesheetNonce }}'; require-trusted-types-for 'script'; trusted-types ttpolicy;">
|
{{ $containsNonce := contains .contentSecurityPolicy "nonce-%s" }}
|
||||||
<style nonce="{{ $stylesheetNonce }}">{{ .user.Stylesheet | safeCSS }}</style>
|
{{ if $containsNonce }}
|
||||||
|
{{ noescape ( printf "<meta http-equiv=\"Content-Security-Policy\" content=\"%s\">" (printf .contentSecurityPolicy $stylesheetNonce ) ) }}
|
||||||
|
{{ else }}
|
||||||
|
{{ noescape ( printf "<meta http-equiv=\"Content-Security-Policy\" content=\"%s\">" .contentSecurityPolicy ) }}
|
||||||
|
{{ end }}
|
||||||
|
<style {{ if $containsNonce }}nonce="{{ $stylesheetNonce }}"{{end}}>{{ .user.Stylesheet | safeCSS }}</style>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src * data:; media-src *; frame-src *; require-trusted-types-for 'script'; trusted-types ttpolicy;">
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src * data:; media-src *; frame-src *; require-trusted-types-for 'script'; trusted-types ttpolicy;">
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -58,7 +63,6 @@
|
||||||
data-webauthn-delete-all-url="{{ route "webauthnDeleteAll" }}"
|
data-webauthn-delete-all-url="{{ route "webauthnDeleteAll" }}"
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ if .user }}{{ if not .user.KeyboardShortcuts }}data-disable-keyboard-shortcuts="true"{{ end }}{{ end }}>
|
{{ if .user }}{{ if not .user.KeyboardShortcuts }}data-disable-keyboard-shortcuts="true"{{ end }}{{ end }}>
|
||||||
|
|
||||||
{{ if .user }}
|
{{ if .user }}
|
||||||
<a class="skip-to-content-link" href="#main">{{ t "skip_to_content" }}</a>
|
<a class="skip-to-content-link" href="#main">{{ t "skip_to_content" }}</a>
|
||||||
<header class="header">
|
<header class="header">
|
||||||
|
|
|
@ -46,5 +46,6 @@ func New(tpl *template.Engine, r *http.Request, sess *session.Session) *View {
|
||||||
"sw_js_checksum": static.JavascriptBundleChecksums["service-worker"],
|
"sw_js_checksum": static.JavascriptBundleChecksums["service-worker"],
|
||||||
"webauthn_js_checksum": static.JavascriptBundleChecksums["webauthn"],
|
"webauthn_js_checksum": static.JavascriptBundleChecksums["webauthn"],
|
||||||
"webAuthnEnabled": config.Opts.WebAuthn(),
|
"webAuthnEnabled": config.Opts.WebAuthn(),
|
||||||
|
"contentSecurityPolicy": config.Opts.ContentSecurityPolicy(),
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
|
@ -534,6 +534,15 @@ Default is 16 workers\&.
|
||||||
YouTube URL which will be used for embeds\&.
|
YouTube URL which will be used for embeds\&.
|
||||||
.br
|
.br
|
||||||
Default is https://www.youtube-nocookie.com/embed/\&.
|
Default is https://www.youtube-nocookie.com/embed/\&.
|
||||||
|
.TP
|
||||||
|
.B CONTENT_SECURITY_POLICY
|
||||||
|
Set custom value for Content-Security-Policy meta tag. Used when custom CSS is applied.
|
||||||
|
.br
|
||||||
|
It may contain "nonce-%s", where nonce will be placed\&.
|
||||||
|
.br
|
||||||
|
Default is "default-src 'self'; img-src * data:; media-src *; frame-src *; style-src 'self' 'nonce-%s'; require-trusted-types-for 'script'; trusted-types ttpolicy;"\&.
|
||||||
|
.TP
|
||||||
|
|
||||||
.SH AUTHORS
|
.SH AUTHORS
|
||||||
.P
|
.P
|
||||||
Miniflux is written and maintained by Fr\['e]d\['e]ric Guillot\&.
|
Miniflux is written and maintained by Fr\['e]d\['e]ric Guillot\&.
|
||||||
|
|
Loading…
Reference in New Issue