Allow overriding the default wide img-src content security policy

https://community.openproject.org/projects/openproject/work_packages/74648/activity
This commit is contained in:
Oliver Günther
2026-05-04 11:04:56 +02:00
parent d932c3c869
commit e9e1e5db96
5 changed files with 47 additions and 1 deletions
+7
View File
@@ -957,6 +957,13 @@ module Settings
writable: false,
description: "Host the frontend uses to download files, which has to be added to the CSP."
},
# Content Security Policy
csp_img_src: {
format: :array,
default: %w(* data: blob:),
writable: false,
description: "Allowed sources for the CSP img-src directive."
},
report_incoming_email_errors: {
description: "Respond to incoming mails with error details",
default: true
@@ -34,6 +34,7 @@
# See the Securing Rails Applications Guide for more information:
# https://guides.rubyonrails.org/security.html#content-security-policy-header
# rubocop:disable Lint/PercentStringArray
Rails.application.config.after_initialize do
Rails.application.configure do
config.content_security_policy do |policy|
@@ -125,7 +126,9 @@ Rails.application.config.after_initialize do
policy.form_action(*form_action)
policy.frame_src(*frame_src, "'self'")
policy.frame_ancestors("'self'")
policy.img_src("*", "data:", "blob:")
img_src = %w('self') + Array(OpenProject::Configuration.csp_img_src)
img_src << asset_host if asset_host.present?
policy.img_src(*img_src.compact.uniq)
policy.script_src(*script_src)
policy.script_src_attr("'none'")
policy.style_src(*assets_src, "'unsafe-inline'")
@@ -149,3 +152,4 @@ Rails.application.config.after_initialize do
config.content_security_policy_nonce_directives = %w(script-src)
end
end
# rubocop:enable Lint/PercentStringArray
@@ -715,6 +715,26 @@ To disable rendering the badge, uncheck the setting at Administration &gt; Syste
OPENPROJECT_SECURITY__BADGE__DISPLAYED="false"
```
### Content Security Policy image sources
Configure the allowed sources for the `img-src` CSP directive.
*default: `["*", "data:", "blob:"]`*
OpenProject always adds `'self'` and `rails_asset_host` (if configured) to `img-src` automatically, so same-origin and asset-hosted images remain allowed even if not listed in this setting.
Example to only allow secure remote images (plus data/blob):
```yaml
OPENPROJECT_CSP__IMG__SRC="https: data: blob:"
```
Example to restrict to specific hosts:
```yaml
OPENPROJECT_CSP__IMG__SRC="https://cdn.example.com https://images.example.com data: blob:"
```
### Cache configuration options
@@ -180,6 +180,7 @@ OPENPROJECT_COST__REPORTING__CACHE__FILTER__CLASSES (default=true)
OPENPROJECT_COSTS__CURRENCY (default="EUR") Currency
OPENPROJECT_COSTS__CURRENCY__FORMAT (default="%n %u") Format of currency
OPENPROJECT_CROSS__PROJECT__WORK__PACKAGE__RELATIONS (default=true) Allow cross-project work package relations
OPENPROJECT_CSP__IMG__SRC (default=["*", "data:", "blob:"]) Allowed sources for the CSP img-src directive.
OPENPROJECT_DATABASE__CIPHER__KEY (default=nil) Encryption key for repository credentials
OPENPROJECT_DATE__FORMAT (default=nil) Date
OPENPROJECT_DAYS__PER__MONTH (default=20) This will define what is considered a “month” when displaying duration in a more natural way (for example, if a month is 20 days, 60 days would be 3 months.
@@ -74,5 +74,19 @@ RSpec.describe "" do
csp = parse_csp(last_response.headers["Content-Security-Policy"])
expect(csp["font-src"].count("'self'")).to eq(1)
end
it "includes 'self' in img-src CSP directive" do
get "/"
csp = parse_csp(last_response.headers["Content-Security-Policy"])
expect(csp["img-src"]).to include("'self'")
end
it "does not duplicate 'self' in img-src CSP directive" do
get "/"
csp = parse_csp(last_response.headers["Content-Security-Policy"])
expect(csp["img-src"].count("'self'")).to eq(1)
end
end
end