jnunemaker/httparty · error · HTTParty::UnsafeURIError
Requested URI '#{new_uri}' has host '#{new_uri.host}' but th
Error message
Requested URI '#{new_uri}' has host '#{new_uri.host}' but the configured base_uri '#{normalized_base}' has host '#{normalized_base.host}'. This request could send credentials to an unintended server. What it means
HTTParty raises UnsafeURIError from validate_uri_safety! when a request uses an absolute URI whose host differs from the configured base_uri host. This is an SSRF guard: if a class pins base_uri (typically alongside basic_auth/digest_auth or headers with secrets), a full URL pointing at another host would silently send those credentials elsewhere, so httparty aborts. The check is skipped for redirects and when options[:skip_uri_validation] is set, and only runs when options[:base_uri] is present.
Source
Thrown at lib/httparty/request.rb:460
text,
content_type: content_type,
assume_utf16_is_big_endian: assume_utf16_is_big_endian
).call
end
def validate_uri_safety!(new_uri)
return if options[:skip_uri_validation]
configured_base_uri = options[:base_uri]
return unless configured_base_uri
normalized_base = options[:uri_adapter].parse(
HTTParty.normalize_base_uri(configured_base_uri)
)
return if new_uri.host == normalized_base.host
raise UnsafeURIError,
"Requested URI '#{new_uri}' has host '#{new_uri.host}' but the " \
"configured base_uri '#{normalized_base}' has host '#{normalized_base.host}'. " \
"This request could send credentials to an unintended server."
end
end
end
View on GitHub (pinned to 8f4a09e343)
Solutions
- Keep hosts consistent: request relative paths against the pinned base_uri (`Api.get('/resource')`).
- If the different host is intentional and trusted, move that call to a separate HTTParty class with the correct base_uri (or none).
- Only for vetted URLs, bypass explicitly per request: `Api.get(url, skip_uri_validation: true)` — this re-enables credential leakage risk, so scope it narrowly.
- When consuming pagination 'next' links, parse them and re-issue relative paths instead of following absolute URLs.
Example fix
# before
class Api
include HTTParty
base_uri 'https://api.example.com'
basic_auth 'user', 'pass'
end
Api.get('https://cdn.example.net/file') # UnsafeURIError
# after (relative path against pinned host)
Api.get('/file')
# after (intentional different host: separate class, no shared creds)
class Cdn
include HTTParty
end
Cdn.get('https://cdn.example.net/file') Defensive patterns
Strategy: try-catch
Validate before calling
base = URI.parse(HTTParty.normalize_base_uri(base_uri_string))
raise HTTParty::UnsafeURIError, "#{uri.host} != pinned #{base.host}" if uri.host && uri.host != base.host Type guard
host_matches_base = ->(url, base) do URI.parse(url.to_s).host == URI.parse(HTTParty.normalize_base_uri(base)).host end
Try / catch
begin
Api.get(url)
rescue HTTParty::UnsafeURIError => e
Rails.logger.warn("host mismatch blocked: #{e.message}")
OtherClient.get(url) # separate class without shared credentials
end Prevention
- Request relative paths whenever base_uri is pinned.
- Create one HTTParty class per host instead of one class with bypasses.
- If you must follow absolute pagination/next links, either strip credentials or consciously pass skip_uri_validation: true for that single vetted call.
- Never enable skip_uri_validation for user-supplied URLs.
When it happens
Trigger: `class Api; include HTTParty; base_uri 'https://api.example.com'; basic_auth 'u','p'; end` then `Api.get('https://other-host.io/resource')`; or dynamic absolute URLs read from a field that no longer matches the pinned base host (e.g. environment mismatch where base_uri points at staging but the URL is production).
Common situations: API clients that pin base_uri for safety but sometimes receive absolute URLs (pagination next links, webhooks, entity hrefs), multi-environment configs where base_uri and the passed URL drift, and refactors that add base_uri to an existing class already passing full URLs.
Related errors
- #{response}
- The URI adapter should respond to #parse
- uri must be a #{uri_adapter}, not a #{uri.class}
- bad argument (expected #{uri_adapter} object or URI string)
- '#{new_uri}' Must be HTTP, HTTPS or Generic
AI-assisted analysis of jnunemaker/httparty@8f4a09e343 (2026-08-21).
Data as JSON: /api/errors/d1f91108ba1847f8.
Report an issue: GitHub.