{"record":{"id":"c26b134220f01064","repo":"opf/openproject","slug":"failed-to-connect-to-jira-server-message","errorCode":null,"errorMessage":"Failed to connect to Jira server: %{message}","messagePattern":"Failed to connect to Jira server: %(.+?)","errorType":"exception","errorClass":"Import::JiraClient::ConnectionError","httpStatus":null,"severity":"error","filePath":"app/services/import/jira_client.rb","lineNumber":271,"sourceCode":"      tempfile = nil\n      OpenProject::SsrfProtection.get(content_url, headers: @headers, http_options: HTTP_OPTIONS, max_redirects: 1) do |response|\n        case response\n        when Net::HTTPSuccess\n          tempfile = Tempfile.create(filename, binmode: true)\n          response.read_body do |chunk|\n            tempfile.write chunk\n          end\n          yield tempfile\n        else\n          status = response.code.to_i\n          raise ApiError.new(I18n.t(\"admin.jira.client.api_error\", status:), status:, response_body: response.body)\n        end\n      end\n      nil\n    rescue SsrfFilter::PrivateIPAddress\n      raise SsrfError, I18n.t(\"admin.jira.client.ssrf_blocked\")\n    rescue SsrfFilter::Error => e\n      raise ConnectionError, I18n.t(\"admin.jira.client.connection_error\", message: e.message)\n    rescue OpenSSL::SSL::SSLError => e\n      raise ConnectionError, I18n.t(\"admin.jira.client.ssl_error\", message: e.message)\n    rescue Timeout::Error => e\n      raise ConnectionError, I18n.t(\"admin.jira.client.connection_timeout\", message: e.message)\n    ensure\n      File.unlink(tempfile) if tempfile\n    end\n\n    private\n\n    def get(path, params: {})\n      response = get_response(path, params:)\n      handle_response(response)\n    end\n\n    def get_response(path, params: {})\n      OpenProject::SsrfProtection.get(\n        \"#{@url}#{path}\",","sourceCodeStart":253,"sourceCodeEnd":289,"githubUrl":"https://github.com/opf/openproject/blob/d9742c43f3424c34b63550f8c03f201fe5c3040c/app/services/import/jira_client.rb#L253-L289","documentation":"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}).","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"# before\nclient.download_attachment(content_url, filename) { |tf| attach(tf) }\n\n# after — reject unfetchable URLs before the SSRF-guarded request\nuri = URI.parse(content_url)\nunless uri.is_a?(URI::HTTP) && uri.host.present?\n  raise ArgumentError, \"Not a fetchable attachment URL: #{content_url}\"\nend\nclient.download_attachment(content_url, filename) { |tf| attach(tf) }","handlingStrategy":"try-catch","validationCode":"uri = URI.parse(content_url)\nraise ArgumentError, \"unfetchable attachment URL #{content_url}\" unless uri.is_a?(URI::HTTP) && uri.host.present?\nclient.download_attachment(content_url, filename) { |tf| attach(tf) }","typeGuard":null,"tryCatchPattern":"begin\n  client.download_attachment(content_url, filename) { |tf| attach(tf) }\nrescue Import::JiraClient::ConnectionError => e\n  # e.message embeds the original SsrfFilter reason (DNS, redirects, scheme)\n  Rails.logger.warn(\"Jira attachment download failed: #{e.message}\")\n  mark_attachment_skipped(content_url, reason: e.message) # continue the import, record the gap\nend","preventionTips":["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)"],"tags":["jira","network","import","dns","url-validation","attachments"],"backgroundTag":"outbound-http-request-failed","analyzedSha":"d9742c43f3424c34b63550f8c03f201fe5c3040c","analyzedAt":"2026-08-21T14:40:06.829Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}