postalserver/postal · error · Postal::HTTP::BlockedDestinationError

No host was given for the request

Error message

No host was given for the request

What it means

Postal::HTTP::AddressGuard is the SSRF guard every outbound HTTP request (webhook delivery to HTTPEndpoints, etc.) passes through; safe_connect_address resolves the URL host and vetted addresses are pinned onto the connection. An empty host means the parsed URL had no hostname at all, which is treated as a blocked destination (BlockedDestinationError) rather than letting Net::HTTP attempt anything.

Source

Thrown at lib/postal/http/address_guard.rb:97

        def local_families
          families = []
          Socket.ip_address_list.each do |address|
            families << :ipv4 if address.ipv4? && !address.ipv4_loopback?
            families << :ipv6 if address.ipv6? && !address.ipv6_loopback? && !address.ipv6_linklocal?
          end
          families.uniq
        end

      end

      # @param [String] host
      def initialize(host)
        @host = host.to_s
      end

      def safe_connect_address
        if @host.empty?
          raise BlockedDestinationError, "No host was given for the request"
        end

        addresses = resolve
        if addresses.empty?
          raise BlockedDestinationError, "Could not resolve '#{@host}' to any IP address"
        end

        # Reject the whole request if *any* resolved address is blocked. This is
        # checked before the reachability filtering below so that a blocked
        # destination is always reported as such, regardless of which address
        # families this particular server can reach. It also defeats DNS
        # responses that mix a public and a private address to slip past.
        addresses.each do |address|
          next unless blocked?(address)

          raise BlockedDestinationError,
                "Destination '#{@host}' (#{address}) is not permitted"
        end

View on GitHub (pinned to d038eaa8c7)

Solutions

  1. Fix the endpoint URL to include a scheme and hostname, e.g. https://hooks.example.com/path
  2. Validate at save time on the HTTPEndpoint form/model: parse with URI() and require a non-empty host
  3. If the URL is assembled from ENV/config, check the interpolated values are present
  4. Handle BlockedDestinationError where calling Postal::HTTP (it maps to result code -4) and surface it to the endpoint owner

Example fix

# before
endpoint.url = params[:url] # accepts "https:///hooks"

# after (model validation on the endpoint)
validate :url_has_host
def url_has_host
  parsed = URI(url.to_s)
  errors.add(:url, "must include a scheme and host") if parsed.host.to_s.empty?
rescue URI::InvalidURIError
  errors.add(:url, "is not a valid URL")
end
Defensive patterns

Strategy: validation

Validate before calling

# before saving an HTTPEndpoint or calling Postal::HTTP.request
uri = URI(url.to_s)
raise ArgumentError, "URL must include a scheme and host" if uri.host.to_s.empty?

Type guard

def http_url_with_host?(value)
  uri = URI(value.to_s)
  uri.is_a?(URI::HTTP) && !uri.host.to_s.empty?
rescue URI::InvalidURIError
  false
end

Try / catch

begin
  Postal::HTTP.request(...)
rescue Postal::HTTP::BlockedDestinationError => e
  # malformed/blocked destination: fail the endpoint, tell its owner; do not retry
end

Prevention

When it happens

Trigger: Postal::HTTP.request (or an HTTPEndpoint delivery) with a URL whose host component is empty: 'https:///hooks', 'http://', 'example.com/path' (scheme-less, so URI parsing yields a nil/empty host), or a URL built by string interpolation where the host variable was blank.

Common situations: An HTTPEndpoint saved with a malformed URL because the create form did not require a scheme+host; ENV-based URL templates where the host variable is missing; trailing-colon or whitespace-corrupted URLs pasted into config; code building URLs by concatenation without validation.

Related errors


AI-assisted analysis of postalserver/postal@d038eaa8c7 (2026-08-21). Data as JSON: /api/errors/d15194af42b32ebd. Report an issue: GitHub.