mirror of
https://github.com/chatwoot/chatwoot.git
synced 2026-06-04 21:02:35 +08:00
Self-hosted installations can now opt SafeFetch into private-network access after SSRF hardening. The default remains unchanged: private IP destinations are blocked unless the instance owner explicitly enables private-network requests with `SAFE_FETCH_ALLOW_PRIVATE_NETWORK=true`. Fixes https://linear.app/chatwoot/issue/CW-7131 Fixes https://github.com/chatwoot/chatwoot/issues/14489 Fixes https://github.com/chatwoot/chatwoot/issues/14494 ## How to use For self-hosted installations that need API inbox webhooks, or other SafeFetch-backed requests, to call trusted private services, enable private-network access with a single environment variable: ```bash SAFE_FETCH_ALLOW_PRIVATE_NETWORK=true ``` This is disabled by default. Enable it only when the instance owner controls the deployment network and trusts the configured URLs.
42 lines
1.3 KiB
Ruby
42 lines
1.3 KiB
Ruby
require 'ssrf_filter'
|
|
|
|
module SafeFetch
|
|
DEFAULT_ALLOWED_CONTENT_TYPE_PREFIXES = %w[image/ video/].freeze
|
|
DEFAULT_ALLOWED_CONTENT_TYPES = [].freeze
|
|
DEFAULT_SENSITIVE_HEADERS = %w[authorization cookie proxy-authorization].freeze
|
|
DEFAULT_OPEN_TIMEOUT = 2
|
|
DEFAULT_READ_TIMEOUT = 20
|
|
DEFAULT_MAX_BYTES_FALLBACK_MB = 40
|
|
|
|
Result = Data.define(:tempfile, :filename, :content_type) do
|
|
def original_filename
|
|
filename
|
|
end
|
|
end
|
|
|
|
class Error < StandardError; end
|
|
class InvalidUrlError < Error; end
|
|
class UnsafeUrlError < Error; end
|
|
class FetchError < Error; end
|
|
class HttpError < Error; end
|
|
class FileTooLargeError < Error; end
|
|
class UnsupportedContentTypeError < Error; end
|
|
class UnsupportedMethodError < Error; end
|
|
|
|
def self.fetch(url, **, &)
|
|
raise ArgumentError, 'block required' unless block_given?
|
|
|
|
SafeFetch::Fetcher.new(SafeFetch::RequestOptions.new(url: url, **)).fetch(&)
|
|
rescue SsrfFilter::InvalidUriScheme, URI::InvalidURIError => e
|
|
raise InvalidUrlError, e.message
|
|
rescue SsrfFilter::Error, Resolv::ResolvError => e
|
|
raise UnsafeUrlError, e.message
|
|
rescue Net::OpenTimeout, Net::ReadTimeout, SocketError, OpenSSL::SSL::SSLError => e
|
|
raise FetchError, e.message
|
|
end
|
|
|
|
def self.allow_private_network?
|
|
ActiveModel::Type::Boolean.new.cast(ENV.fetch('SAFE_FETCH_ALLOW_PRIVATE_NETWORK', false))
|
|
end
|
|
end
|