{"record":{"id":"053f0b024c3271e0","repo":"postalserver/postal","slug":"invalid-endpoint-class-name-class-name-053f0b","errorCode":null,"errorMessage":"Invalid endpoint class name '#{class_name}'","messagePattern":"Invalid endpoint class name '#(.+?)'","errorType":"exception","errorClass":"Postal::Error","httpStatus":500,"severity":"error","filePath":"app/models/additional_route_endpoint.rb","lineNumber":27,"sourceCode":"#  endpoint_type :string(255)\n#  endpoint_id   :integer\n#  created_at    :datetime         not null\n#  updated_at    :datetime         not null\n#\n\nclass AdditionalRouteEndpoint < ApplicationRecord\n\n  belongs_to :route\n  belongs_to :endpoint, polymorphic: true\n\n  validate :validate_endpoint_belongs_to_server\n  validate :validate_wildcard\n  validate :validate_uniqueness\n\n  def self.find_by_endpoint(endpoint)\n    class_name, id = endpoint.split(\"#\", 2)\n    unless Route::ENDPOINT_TYPES.include?(class_name)\n      raise Postal::Error, \"Invalid endpoint class name '#{class_name}'\"\n    end\n\n    return unless uuid = class_name.constantize.find_by_uuid(id)\n\n    where(endpoint_type: class_name, endpoint_id: uuid).first\n  end\n\n  def _endpoint\n    \"#{endpoint_type}##{endpoint.uuid}\"\n  end\n\n  def _endpoint=(value)\n    if value && value =~ /\\#/\n      class_name, id = value.split(\"#\", 2)\n      unless Route::ENDPOINT_TYPES.include?(class_name)\n        raise Postal::Error, \"Invalid endpoint class name '#{class_name}'\"\n      end\n","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/postalserver/postal/blob/d038eaa8c763d3cafa797ccd6f773d53470bd336/app/models/additional_route_endpoint.rb#L9-L45","documentation":"AdditionalRouteEndpoint.find_by_endpoint is the lookup that turns an 'ClassName#uuid' reference (as stored/exposed by _endpoint) back into the join record for a route's extra endpoints. It performs the same guard as Route#_endpoint=: the class part must be in Route::ENDPOINT_TYPES or Postal::Error is raised, so unknown class names cannot be constantized.","triggerScenarios":"Calling AdditionalRouteEndpoint.find_by_endpoint('SmtpEndpoint#abc') (wrong casing), 'WebhookEndpoint#abc' (invented name), or any 'X#id' string whose X is not SMTPEndpoint/HTTPEndpoint/AddressEndpoint - typically from admin tooling or API code resolving endpoint references submitted by clients.","commonSituations":"Client SDKs and scripts passing endpoint type names with different casing conventions; payloads reused from other Postal versions where names differ; debugging consoles pasting references with typos; code that builds the 'Class#uuid' pair from unvalidated input.","solutions":["Pass the exact class name: SMTPENDpoint is wrong, SMTPEndpoint is right (same for HTTPEndpoint, AddressEndpoint)","Guard before calling: split on '#' and check Route::ENDPOINT_TYPES.include?(class_name)","Derive the reference from a real object: \"#{endpoint.class.name}##{endpoint.uuid}\"","Return a 4xx/validation error to the client instead of letting the raise become a 500"],"exampleFix":"# before\nAdditionalRouteEndpoint.find_by_endpoint(\"HttpEndpoint##{id}\")  # -> Postal::Error\n\n# after\nAdditionalRouteEndpoint.find_by_endpoint(\"HTTPEndpoint##{id}\")\n# or guard first:\n# return nil unless Route::ENDPOINT_TYPES.include?(value.split(\"#\", 2).first)","handlingStrategy":"type-guard","validationCode":"# before calling find_by_endpoint\nclass_name, = endpoint.split(\"#\", 2)\nreturn nil unless Route::ENDPOINT_TYPES.include?(class_name)","typeGuard":"def lookupable_endpoint?(value)\n  class_name, id = value.to_s.split(\"#\", 2)\n  !class_name.nil? && Route::ENDPOINT_TYPES.include?(class_name) && id.present?\nend","tryCatchPattern":"begin\n  AdditionalRouteEndpoint.find_by_endpoint(ref)\nrescue Postal::Error => e\n  # unknown type in the reference: treat as not-found/bad-request, log the ref\n  Rails.logger.warn(\"rejected endpoint reference #{ref.inspect}: #{e.message}\")\n  nil\nend","preventionTips":["Pass through references produced by _endpoint rather than re-composing them","Validate external input against Route::ENDPOINT_TYPES at the API boundary","Keep casing consistent (exact constant names) in any client that builds these strings","Unit-test helper methods that parse 'Class#uuid' against the allowed list"],"tags":["postal","routes","polymorphic","lookup","input-validation"],"backgroundTag":"invalid-polymorphic-type","analyzedSha":"d038eaa8c763d3cafa797ccd6f773d53470bd336","analyzedAt":"2026-08-21T13:52:57.446Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}