{"record":{"id":"d15194af42b32ebd","repo":"postalserver/postal","slug":"no-host-was-given-for-the-request","errorCode":null,"errorMessage":"No host was given for the request","messagePattern":"No host was given for the request","errorType":"exception","errorClass":"Postal::HTTP::BlockedDestinationError","httpStatus":null,"severity":"error","filePath":"lib/postal/http/address_guard.rb","lineNumber":97,"sourceCode":"        def local_families\n          families = []\n          Socket.ip_address_list.each do |address|\n            families << :ipv4 if address.ipv4? && !address.ipv4_loopback?\n            families << :ipv6 if address.ipv6? && !address.ipv6_loopback? && !address.ipv6_linklocal?\n          end\n          families.uniq\n        end\n\n      end\n\n      # @param [String] host\n      def initialize(host)\n        @host = host.to_s\n      end\n\n      def safe_connect_address\n        if @host.empty?\n          raise BlockedDestinationError, \"No host was given for the request\"\n        end\n\n        addresses = resolve\n        if addresses.empty?\n          raise BlockedDestinationError, \"Could not resolve '#{@host}' to any IP address\"\n        end\n\n        # Reject the whole request if *any* resolved address is blocked. This is\n        # checked before the reachability filtering below so that a blocked\n        # destination is always reported as such, regardless of which address\n        # families this particular server can reach. It also defeats DNS\n        # responses that mix a public and a private address to slip past.\n        addresses.each do |address|\n          next unless blocked?(address)\n\n          raise BlockedDestinationError,\n                \"Destination '#{@host}' (#{address}) is not permitted\"\n        end","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/postalserver/postal/blob/d038eaa8c763d3cafa797ccd6f773d53470bd336/lib/postal/http/address_guard.rb#L79-L115","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Fix the endpoint URL to include a scheme and hostname, e.g. https://hooks.example.com/path","Validate at save time on the HTTPEndpoint form/model: parse with URI() and require a non-empty host","If the URL is assembled from ENV/config, check the interpolated values are present","Handle BlockedDestinationError where calling Postal::HTTP (it maps to result code -4) and surface it to the endpoint owner"],"exampleFix":"# before\nendpoint.url = params[:url] # accepts \"https:///hooks\"\n\n# after (model validation on the endpoint)\nvalidate :url_has_host\ndef url_has_host\n  parsed = URI(url.to_s)\n  errors.add(:url, \"must include a scheme and host\") if parsed.host.to_s.empty?\nrescue URI::InvalidURIError\n  errors.add(:url, \"is not a valid URL\")\nend","handlingStrategy":"validation","validationCode":"# before saving an HTTPEndpoint or calling Postal::HTTP.request\nuri = URI(url.to_s)\nraise ArgumentError, \"URL must include a scheme and host\" if uri.host.to_s.empty?","typeGuard":"def http_url_with_host?(value)\n  uri = URI(value.to_s)\n  uri.is_a?(URI::HTTP) && !uri.host.to_s.empty?\nrescue URI::InvalidURIError\n  false\nend","tryCatchPattern":"begin\n  Postal::HTTP.request(...)\nrescue Postal::HTTP::BlockedDestinationError => e\n  # malformed/blocked destination: fail the endpoint, tell its owner; do not retry\nend","preventionTips":["Require scheme + host on every endpoint URL at input time (model validation, not just form placeholder)","Never build request URLs by string concatenation with unchecked variables","Log the exact URL (host included) when an endpoint is created for later diagnosis","Treat BlockedDestinationError result code -4 as a permanent delivery failure, not a retryable one"],"tags":["postal","http-endpoint","webhook","url-parsing","ssrf-guard"],"backgroundTag":"invalid-url","analyzedSha":"d038eaa8c763d3cafa797ccd6f773d53470bd336","analyzedAt":"2026-08-21T13:52:57.446Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}