Skip to content

Commit

Permalink
SECURITY: Place a SSRF protection when calling services from the plug…
Browse files Browse the repository at this point in the history
…in. (#485)

The Faraday adapter and `FinalDestionation::HTTP` will protect us from admin-initiated SSRF attacks when interacting with the external services powering this plugin features.:
  • Loading branch information
romanrizzi committed Feb 21, 2024
1 parent 97f3cba commit 94ba0da
Show file tree
Hide file tree
Showing 10 changed files with 21 additions and 14 deletions.
2 changes: 1 addition & 1 deletion lib/completions/endpoints/base.rb
Expand Up @@ -69,7 +69,7 @@ def perform_completion!(dialect, user, model_params = {})

prompt = dialect.translate

Net::HTTP.start(
FinalDestination::HTTP.start(
model_uri.host,
model_uri.port,
use_ssl: true,
Expand Down
3 changes: 2 additions & 1 deletion lib/inference/cloudflare_workers_ai.rb
Expand Up @@ -14,7 +14,8 @@ def self.perform!(model, content)

endpoint = "#{base_url}#{model}"

response = Faraday.post(endpoint, content.to_json, headers)
conn = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
response = conn.post(endpoint, content.to_json, headers)

raise Net::HTTPBadResponse if ![200].include?(response.status)

Expand Down
3 changes: 2 additions & 1 deletion lib/inference/discourse_classifier.rb
Expand Up @@ -8,7 +8,8 @@ def self.perform!(endpoint, model, content, api_key)

headers["X-API-KEY"] = api_key if api_key.present?

response = Faraday.post(endpoint, { model: model, content: content }.to_json, headers)
conn = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
response = conn.post(endpoint, { model: model, content: content }.to_json, headers)

raise Net::HTTPBadResponse if ![200, 415].include?(response.status)

Expand Down
3 changes: 2 additions & 1 deletion lib/inference/discourse_reranker.rb
Expand Up @@ -8,8 +8,9 @@ def self.perform!(endpoint, model, content, candidates, api_key)

headers["X-API-KEY"] = api_key if api_key.present?

conn = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
response =
Faraday.post(
conn.post(
endpoint,
{ model: model, content: content, candidates: candidates }.to_json,
headers,
Expand Down
3 changes: 2 additions & 1 deletion lib/inference/gemini_embeddings.rb
Expand Up @@ -11,7 +11,8 @@ def self.perform!(content)

body = { content: { parts: [{ text: content }] } }

response = Faraday.post(url, body.to_json, headers)
conn = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
response = conn.post(url, body.to_json, headers)

raise Net::HTTPBadResponse if ![200].include?(response.status)

Expand Down
3 changes: 2 additions & 1 deletion lib/inference/hugging_face_text_embeddings.rb
Expand Up @@ -18,7 +18,8 @@ def self.perform!(content)
headers["X-API-KEY"] = SiteSetting.ai_hugging_face_tei_api_key
end

response = Faraday.post(api_endpoint, body, headers)
conn = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
response = conn.post(api_endpoint, body, headers)

raise Net::HTTPBadResponse if ![200].include?(response.status)

Expand Down
3 changes: 2 additions & 1 deletion lib/inference/open_ai_embeddings.rb
Expand Up @@ -15,7 +15,8 @@ def self.perform!(content, model:, dimensions: nil)
payload = { model: model, input: content }
payload[:dimensions] = dimensions if dimensions.present?

response = Faraday.post(SiteSetting.ai_openai_embeddings_url, payload.to_json, headers)
conn = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
response = conn.post(SiteSetting.ai_openai_embeddings_url, payload.to_json, headers)

case response.status
when 200
Expand Down
2 changes: 1 addition & 1 deletion lib/inference/open_ai_image_generator.rb
Expand Up @@ -28,7 +28,7 @@ def self.perform!(prompt, model: "dall-e-3", size: "1024x1024", api_key: nil, ap
response_format: "b64_json",
}

Net::HTTP.start(
FinalDestination::HTTP.start(
uri.host,
uri.port,
use_ssl: uri.scheme == "https",
Expand Down
3 changes: 2 additions & 1 deletion lib/inference/stability_generator.rb
Expand Up @@ -57,7 +57,8 @@ def self.perform!(

endpoint = "v1/generation/#{engine}/text-to-image"

response = Faraday.post("#{api_url}/#{endpoint}", payload.to_json, headers)
conn = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
response = conn.post("#{api_url}/#{endpoint}", payload.to_json, headers)

if response.status != 200
Rails.logger.error(
Expand Down
10 changes: 5 additions & 5 deletions spec/lib/completions/endpoints/endpoint_compliance.rb
Expand Up @@ -99,21 +99,21 @@ def tool

def with_chunk_array_support
mock = mocked_http
@original_net_http = ::Net.send(:remove_const, :HTTP)
::Net.send(:const_set, :HTTP, mock)
@original_net_http = ::FinalDestination.send(:remove_const, :HTTP)
::FinalDestination.send(:const_set, :HTTP, mock)

yield
ensure
::Net.send(:remove_const, :HTTP)
::Net.send(:const_set, :HTTP, @original_net_http)
::FinalDestination.send(:remove_const, :HTTP)
::FinalDestination.send(:const_set, :HTTP, @original_net_http)
end

protected

# Copied from https://github.com/bblimke/webmock/issues/629
# Workaround for stubbing a streamed response
def mocked_http
Class.new(::Net::HTTP) do
Class.new(FinalDestination::HTTP) do
def request(*)
super do |response|
response.instance_eval do
Expand Down

0 comments on commit 94ba0da

Please sign in to comment.