opf/openproject · error · Import::JiraClient::SsrfError
Connection blocked: the Jira host resolves to a private IP a
Error message
Connection blocked: the Jira host resolves to a private IP address. If your Jira instance runs on an internal network, allow its IP via the OPENPROJECT_SSRF__PROTECTION__IP__ALLOWLIST environment variable.
What it means
Import::JiraClient#download_attachment streams Jira attachments through OpenProject::SsrfProtection.get (a wrapper over the SsrfFilter gem). The wrapper resolves the hostname first and raises SsrfFilter::PrivateIPAddress when every resolved IP falls in a private/reserved range (loopback, link-local, RFC1918); the client re-raises it as Import::JiraClient::SsrfError with this message. The check exists to prevent server-side request forgery, so it fires even for legitimate internal Jira hosts unless they are explicitly allowlisted.
Source
Thrown at app/services/import/jira_client.rb:269
# @raise [ApiError] If the server returns a non-success response
def download_attachment(content_url, filename) # rubocop:disable Metrics/AbcSize
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: {})View on GitHub (pinned to d9742c43f3)
Solutions
- Set OPENPROJECT_SSRF__PROTECTION__IP__ALLOWLIST to the Jira host's internal IP (comma-separated list, CIDR ranges supported) in the OpenProject environment and restart the app/container
- Check what the host actually resolves to from inside the OpenProject container (e.g. getent hosts jira.corp.local) and allowlist exactly that IP
- If the Jira instance should be reachable publicly, fix DNS/split-horizon resolution or point the import configuration at the public hostname instead of allowlisting
- Keep the allowlist as narrow as possible (single IP or small CIDR); never widen it to all private ranges
Example fix
# before — Jira attachment host resolves to 10.0.0.15, download raises SsrfError # docker-compose.yml environment: # (no SSRF allowlist set) # after — allowlist the internal Jira IP and restart environment: OPENPROJECT_SSRF__PROTECTION__IP__ALLOWLIST: "10.0.0.15"
Defensive patterns
Strategy: validation
Validate before calling
# before importing or downloading attachments, pre-flight the host
host = URI.parse(content_url).host
unless OpenProject::SsrfProtection.safe_ip?(host)
raise ArgumentError,
"#{host} resolves to a blocked private IP — allowlist it via " \
"OPENPROJECT_SSRF__PROTECTION__IP__ALLOWLIST before importing"
end
client.download_attachment(content_url, filename) { |tf| attach(tf) } Prevention
- Decide before the import whether the Jira host is internal, and set OPENPROJECT_SSRF__PROTECTION__IP__ALLOWLIST in the deployment rather than reacting to the error
- Document which IP was allowlisted and why, so the security exception is reviewable
- Use safe_ip? in a pre-flight check script to fail fast with a clear message instead of a mid-import failure
When it happens
Trigger: Calling client.download_attachment(content_url, filename) where the attachment URL returned by the Jira REST API points at a host that resolves to a private IP: http://jira.internal:8080/..., http://10.x.x.x/..., http://localhost/..., or a public-looking name whose DNS (or the one allowed redirect, max_redirects: 1) lands on an internal address.
Common situations: Self-hosted Jira Server/Data Center on the same LAN as OpenProject; Docker/Kubernetes deployments where the Jira service name resolves to a cluster-internal IP; split-horizon DNS that returns an internal IP inside the OpenProject container; an SSO/reverse proxy redirecting attachment downloads to an internal host.
Related errors
- Failed to connect to Jira server: %{message}
- SSL error connecting to Jira server: %{message}
- Connection to Jira server timed out: %{message}
- 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/c514347a250bcc2d.
Report an issue: GitHub.