0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-06 13:08:40 +08:00
discourse/app/models/embeddable_host.rb
Juan David Martínez Cubillos 1dd0603581
FIX: Validate and safely handle malformed embed allowlist regexes (#42327)
## Summary

EmbeddableHost allowed_paths values are stored as regular expression
source and compiled during request handling without validation or error
handling. Now new or updated allowlists are validated on save, and
legacy invalid or timed-out patterns are safely skipped as non-matches
rather than causing HTTP 500 errors on public embed endpoints. This
prevents an administrator from accidentally breaking embed functionality
across their site.

## Source

- Patch Triage: `patch-triage/1556`

Co-authored-by: discourse-patch-triage
<272280883+discourse-patch-triage[bot]@users.noreply.github.com>
2026-08-05 12:54:43 -05:00

99 lines
2.5 KiB
Ruby
Vendored

# frozen_string_literal: true
class EmbeddableHost < ActiveRecord::Base
validate :host_must_be_valid
validate :allowed_paths_must_be_valid
belongs_to :category
belongs_to :user, optional: true
has_many :embeddable_host_tags
has_many :tags, through: :embeddable_host_tags
after_destroy :reset_embedding_settings
before_validation do
host.sub!(%r{\Ahttps?://}, "")
host.sub!(%r{/.*\z}, "")
end
def self.record_for_url(uri)
if uri.is_a?(String)
uri =
begin
URI(UrlHelper.normalized_encode(uri))
rescue URI::Error, Addressable::URI::InvalidURIError
end
end
return false if uri.blank?
host = uri.host
return false if host.blank?
host << ":#{uri.port}" if uri.port.present? && uri.port != 80 && uri.port != 443
path = uri.path
path << "?" << uri.query if uri.query.present?
where("lower(host) = ?", host).each do |eh|
return eh if eh.allowed_paths.blank?
begin
path_regexp = Regexp.new(eh.allowed_paths)
return eh if path_regexp.match(path) || path_regexp.match(UrlHelper.unencode(path))
rescue RegexpError
next
end
end
nil
end
def self.url_allowed?(url)
return false if url.nil?
uri =
begin
URI(UrlHelper.normalized_encode(url))
rescue URI::Error
end
uri.present? && record_for_url(uri).present?
end
private
def reset_embedding_settings
unless EmbeddableHost.exists?
Embedding.settings.each { |s| SiteSetting.set(s.to_s, SiteSetting.defaults[s]) }
end
end
def allowed_paths_must_be_valid
return if allowed_paths.blank?
Regexp.new(allowed_paths)
rescue RegexpError
errors.add(:allowed_paths, I18n.t("errors.messages.invalid"))
end
def host_must_be_valid
if host !~ /\A[a-z0-9]+([\-\.]+{1}[a-z0-9]+)*\.[a-z]{2,24}(:[0-9]{1,5})?(\/.*)?\Z/i &&
host !~ /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(:[0-9]{1,5})?(\/.*)?\Z/ &&
host !~ /\A([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.)?localhost(\:[0-9]{1,5})?(\/.*)?\Z/i
errors.add(:host, I18n.t("errors.messages.invalid"))
end
end
end
# == Schema Information
#
# Table name: embeddable_hosts
#
# id :integer not null, primary key
# allowed_paths :string
# class_name :string
# host :string not null
# created_at :datetime not null
# updated_at :datetime not null
# category_id :integer not null
# user_id :integer
#