CVE-2024-23654 (Ruby, published 2024-02-21T21:15Z)
Description: value: discourse-ai is the AI plugin for the open-source discussion platform Discourse. Prior to commit 94ba0dadc2cf38e8f81c3936974c167219878edd, interactions with different AI services are vulnerable to admin-initiated SSRF attacks. Versions of the plugin that include commit 94ba0dadc2cf38e8f81c3936974c167219878edd contain a patch. As a workaround, one may disable the discourse-ai plugin.

Severity: nan (CVSS v3 base score: )
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/discourse/discourse-ai
Files changed:
lib/completions/endpoints/base.rb
lib/inference/cloudflare_workers_ai.rb
lib/inference/discourse_classifier.rb
lib/inference/discourse_reranker.rb
lib/inference/gemini_embeddings.rb
lib/inference/hugging_face_text_embeddings.rb
lib/inference/open_ai_embeddings.rb
lib/inference/open_ai_image_generator.rb
lib/inference/stability_generator.rb
spec/lib/completions/endpoints/endpoint_compliance.rb
Commit message: SECURITY: Place a SSRF protection when calling services from the plugin. (#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.:

Vulnerable code:
          Net::HTTP.start(
        response = Faraday.post(endpoint, content.to_json, headers)
        response = Faraday.post(endpoint, { model: model, content: content }.to_json, headers)
          Faraday.post(
        response = Faraday.post(url, body.to_json, headers)
        response = Faraday.post(api_endpoint, body, headers)
        response = Faraday.post(SiteSetting.ai_openai_embeddings_url, payload.to_json, headers)
        Net::HTTP.start(
        response = Faraday.post("#{api_url}/#{endpoint}", payload.to_json, headers)
    @original_net_http = ::Net.send(:remove_const, :HTTP)
    ::Net.send(:const_set, :HTTP, mock)
    ::Net.send(:remove_const, :HTTP)
    ::Net.send(:const_set, :HTTP, @original_net_http)
    Class.new(::Net::HTTP) do

Fixed code:
          FinalDestination::HTTP.start(
        conn = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
        response = conn.post(endpoint, content.to_json, headers)
        conn = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
        response = conn.post(endpoint, { model: model, content: content }.to_json, headers)
        conn = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
          conn.post(
        conn = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
        response = conn.post(url, body.to_json, headers)
        conn = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
        response = conn.post(api_endpoint, body, headers)
        conn = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
        response = conn.post(SiteSetting.ai_openai_embeddings_url, payload.to_json, headers)
        FinalDestination::HTTP.start(
        conn = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
        response = conn.post("#{api_url}/#{endpoint}", payload.to_json, headers)
    @original_net_http = ::FinalDestination.send(:remove_const, :HTTP)
    ::FinalDestination.send(:const_set, :HTTP, mock)
    ::FinalDestination.send(:remove_const, :HTTP)
    ::FinalDestination.send(:const_set, :HTTP, @original_net_http)
    Class.new(FinalDestination::HTTP) do