Merge pull request #23555 from opf/precise-ssrf-error

Be more precise about SSRF errors
This commit is contained in:
Jan Sandbrink
2026-06-08 11:04:41 +02:00
committed by GitHub
6 changed files with 27 additions and 14 deletions
+8 -8
View File
@@ -2197,17 +2197,14 @@ en:
before: "must be before %{date}."
before_or_equal_to: "must be before or equal to %{date}."
blank: "can't be blank."
not_before_start_date: "must not be before the start date."
overlapping_range: "overlaps with an existing non-working day range."
blank_nested: "needs to have the property '%{property}' set."
cannot_delete_mapping: "is required. Cannot be deleted."
is_for_all_cannot_modify: "is for all projects and can therefore not be modified."
cant_link_a_work_package_with_a_descendant: "A work package cannot be linked to one of its subtasks."
circular_dependency: "This relation would create a circular dependency."
confirmation: "doesn't match %{attribute}."
could_not_be_copied: "%{dependency} could not be (fully) copied."
datetime_must_be_in_future: "must be in the future."
does_not_exist: "does not exist."
user_already_in_department: "User %{user_id} is already a member of department %{department_id}."
error_enterprise_only: "%{action} is only available in the OpenProject Enterprise edition."
error_unauthorized: "may not be accessed."
error_readonly: "was attempted to be written but is not writable."
@@ -2228,36 +2225,38 @@ en:
greater_than_or_equal_to: "must be greater than or equal to %{count}."
greater_than_or_equal_to_start_date: "must be greater than or equal to the start date."
greater_than_start_date: "must be greater than the start date."
hexcode_invalid: "is not a valid 6-digit hexadecimal color code."
inclusion: "is not set to one of the allowed values."
inclusion_nested: "is not set to one of the allowed values at path '%{path}'."
invalid: "is invalid."
invalid_uri: "must be a valid URI."
invalid_url: "is not a valid URL."
invalid_url_scheme: "is not a supported protocol (allowed: %{allowed_schemes})."
is_for_all_cannot_modify: "is for all projects and can therefore not be modified."
less_than_or_equal_to: "must be less than or equal to %{count}."
not_available: "is not available due to a system configuration."
not_before_start_date: "must not be before the start date."
not_deletable: "cannot be deleted."
not_editable: "cannot be edited because it is already in effect."
not_current_user: "is not the current user."
system_wide_non_working_day_exists: "conflicts with an existing system-wide non-working day for this date."
not_found: "not found."
not_a_date: "is not a valid date."
not_a_datetime: "is not a valid date time."
not_a_number: "is not a number."
not_allowed: "is invalid because of missing permissions."
host_not_allowed: "is not an allowed host."
not_json: "is not parseable as JSON."
not_json_object: "is not a JSON object."
not_an_integer: "is not an integer."
not_an_iso_date: "is not a valid date. Required format: YYYY-MM-DD."
not_same_project: "doesn't belong to the same project."
datetime_must_be_in_future: "must be in the future."
odd: "must be odd."
overlapping_range: "overlaps with an existing non-working day range."
regex_match_failed: "does not match the regular expression %{expression}."
regex_invalid: "could not be validated with the associated regular expression."
regex_list_invalid: "Lines %{invalid_lines} could not be parsed as regular expression."
hexcode_invalid: "is not a valid 6-digit hexadecimal color code."
smaller_than_or_equal_to_max_length: "must be smaller than or equal to maximum length."
ssrf_filtered: "violates the SSRF policy of this OpenProject instance."
system_wide_non_working_day_exists: "conflicts with an existing system-wide non-working day for this date."
taken: "has already been taken."
too_long: "is too long (maximum is %{count} characters)."
too_short: "is too short (minimum is %{count} characters)."
@@ -2269,6 +2268,7 @@ en:
unremovable: "cannot be removed."
url_not_secure_context: >
is not providing a "Secure Context". Either use HTTPS or a loopback address, such as localhost.
user_already_in_department: "User %{user_id} is already a member of department %{department_id}."
wrong_length: "is the wrong length (should be %{count} characters)."
models:
group:
+14 -1
View File
@@ -19,7 +19,20 @@ In these Release Notes, we will give an overview of important feature changes. A
## Important updates and breaking changes
<!-- Remove this section if empty, add to it in pull requests linking to tickets and provide information -->
### Integrations (e.g. Nextcloud and XWiki) respect global SSRF filters
To increase the security of OpenProject installations, we've added protections against server-side request forgery in previous releases
of OpenProject. These prevent OpenProject from making network requests into private IP address space.
Starting with OpenProject 17.6, these protections expand into the code that's responsible for web requests of storage and wiki integrations as well.
This means if you have a Nextcloud instance or an XWiki instance reachable via a private (i.e. not publicly routable) IP address, you need to
add it to the SSRF allowlist to be able to keep the integration working. This is usually achieved by defining the following environment variable:
```
OPENPROJECT_SSRF_PROTECTION_IP_ALLOWLIST=2001:db8:100::/48
```
The list accepts one or multiple IP addresses or ranges (in CIDR notation) that shall be exempt from SSRF filtering.
<!-- BEGIN SECURITY FIXES AUTOMATED SECTION -->
@@ -114,7 +114,7 @@ module OpenIDConnect
if host.present? && OpenProject::SsrfProtection.safe_ip?(host)
true
else
call.errors.add(:metadata_url, :host_not_allowed)
call.errors.add(:metadata_url, :ssrf_filtered)
call.success = false
false
end
@@ -92,7 +92,7 @@ RSpec.describe OpenIDConnect::Providers::UpdateService, type: :model do
result = service_call
expect(result).not_to be_success
expect(result.errors[:metadata_url]).to include("is not an allowed host.")
expect(result.errors[:metadata_url]).to include("violates the SSRF policy of this OpenProject instance.")
expect(httpx_session).not_to have_received(:get)
end
end
@@ -50,7 +50,7 @@ class NextcloudCompatibleHostValidator < ActiveModel::EachValidator
return false if host.blank?
return true if OpenProject::SsrfProtection.safe_ip?(host)
contract.errors.add(attribute, :host_not_allowed)
contract.errors.add(attribute, :ssrf_filtered)
false
rescue URI::InvalidURIError
false
@@ -275,7 +275,7 @@ RSpec.shared_examples_for "nextcloud storage contract", :storage_server_helpers,
context "when host is localhost" do
let(:storage_host) { "http://localhost:1234" }
include_examples "contract is invalid", host: :host_not_allowed
include_examples "contract is invalid", host: :ssrf_filtered
it "does not perform metadata discovery requests" do
contract.validate
@@ -288,7 +288,7 @@ RSpec.shared_examples_for "nextcloud storage contract", :storage_server_helpers,
context "when host uses https protocol" do
let(:storage_host) { "https://172.16.193.146" }
include_examples "contract is invalid", host: :host_not_allowed
include_examples "contract is invalid", host: :ssrf_filtered
end
end