opf/openproject · error · Import::JiraClient::ConnectionError
Failed to connect to Jira server: %{message}
Error message
Failed to connect to Jira server: %{message} What it means
In download_attachment, any SsrfFilter::Error other than PrivateIPAddress is wrapped into Import::JiraClient::ConnectionError with this message. SsrfFilter::Error is the parent of InvalidUriScheme, UnresolvedHostname, CRLFInjection and TooManyRedirects, so this is the generic failure of the SSRF-guarded HTTP fetch of a Jira attachment (the original gem message is preserved in %{message}).
Source
Thrown at app/services/import/jira_client.rb:271
tempfile = nil
OpenProject::SsrfProtection.get(content_url, headers: @headers, http_options: HTTP_OPTIONS, max_redirects: 1) do |response|
case response
when Net::HTTPSuccess
tempfile = Tempfile.create(filename, binmode: true)
response.read_body do |chunk|
tempfile.write chunk
end
yield tempfile
else
status = response.code.to_i
raise ApiError.new(I18n.t("admin.jira.client.api_error", status:), status:, response_body: response.body)
end
end
nil
rescue SsrfFilter::PrivateIPAddress
raise SsrfError, I18n.t("admin.jira.client.ssrf_blocked")
rescue SsrfFilter::Error => e
raise ConnectionError, I18n.t("admin.jira.client.connection_error", message: e.message)
rescue OpenSSL::SSL::SSLError => e
raise ConnectionError, I18n.t("admin.jira.client.ssl_error", message: e.message)
rescue Timeout::Error => e
raise ConnectionError, I18n.t("admin.jira.client.connection_timeout", message: e.message)
ensure
File.unlink(tempfile) if tempfile
end
private
def get(path, params: {})
response = get_response(path, params:)
handle_response(response)
end
def get_response(path, params: {})
OpenProject::SsrfProtection.get(
"#{@url}#{path}",View on GitHub (pinned to d9742c43f3)
Solutions
- Read %{message} to identify the subclass: 'hostname' wording means DNS failure, 'redirect' means the redirect limit, 'scheme' means a non-http(s) URL
- Verify DNS resolution and scheme of the failing content_url from the OpenProject host (curl -sIL '<content_url>')
- If an SSO/proxy redirect chain is the cause, bypass it for Jira REST/attachment paths or make the final hop resolvable in one redirect
- Validate the content_url is an absolute http(s) URI with a host before attempting the download
Example fix
# before
client.download_attachment(content_url, filename) { |tf| attach(tf) }
# after — reject unfetchable URLs before the SSRF-guarded request
uri = URI.parse(content_url)
unless uri.is_a?(URI::HTTP) && uri.host.present?
raise ArgumentError, "Not a fetchable attachment URL: #{content_url}"
end
client.download_attachment(content_url, filename) { |tf| attach(tf) } Defensive patterns
Strategy: try-catch
Validate before calling
uri = URI.parse(content_url)
raise ArgumentError, "unfetchable attachment URL #{content_url}" unless uri.is_a?(URI::HTTP) && uri.host.present?
client.download_attachment(content_url, filename) { |tf| attach(tf) } Try / catch
begin
client.download_attachment(content_url, filename) { |tf| attach(tf) }
rescue Import::JiraClient::ConnectionError => e
# e.message embeds the original SsrfFilter reason (DNS, redirects, scheme)
Rails.logger.warn("Jira attachment download failed: #{e.message}")
mark_attachment_skipped(content_url, reason: e.message) # continue the import, record the gap
end Prevention
- Treat attachment URLs as untrusted external data: validate scheme and host before fetching
- Test attachment URL resolution from the OpenProject host during import setup, not only from a browser
- Keep redirects on the Jira side minimal (max_redirects: 1 is hardcoded in the client)
When it happens
Trigger: client.download_attachment called with a content_url that is not http/https (InvalidUriScheme), whose hostname does not resolve (UnresolvedHostname), that contains CR/LF characters (CRLFInjection), or whose download follows more than one redirect (max_redirects: 1 is hardcoded, so a second hop raises TooManyRedirects).
Common situations: Attachment URLs shortened/rewritten by an SSO proxy that chains two redirects; internal hostnames not present in the OpenProject container's DNS (docker-compose without the corporate DNS); copy-pasted attachment URLs with a wrong scheme; reverse proxies redirecting http→https→internal-host.
Related errors
- SSL error connecting to Jira server: %{message}
- Connection to Jira server timed out: %{message}
- Connection blocked: the Jira host resolves to a private IP a
- Failed to parse Jira API response: %{message}
- LDAP-Error: %{error_message}
AI-assisted analysis of opf/openproject@d9742c43f3 (2026-08-21).
Data as JSON: /api/errors/c26b134220f01064.
Report an issue: GitHub.