diff --git a/app/controllers/discourse_ai/ai_bot/artifacts_controller.rb b/app/controllers/discourse_ai/ai_bot/artifacts_controller.rb index 251d765e..63a04a1b 100644 --- a/app/controllers/discourse_ai/ai_bot/artifacts_controller.rb +++ b/app/controllers/discourse_ai/ai_bot/artifacts_controller.rb @@ -19,8 +19,8 @@ module DiscourseAi raise Discourse::NotFound if !guardian.can_see?(post) end - # Prepare the HTML document - html = <<~HTML + # Prepare the inner (untrusted) HTML document + untrusted_html = <<~HTML @@ -39,11 +39,33 @@ module DiscourseAi HTML + # Prepare the outer (trusted) HTML document + trusted_html = <<~HTML + + + + + #{ERB::Util.html_escape(artifact.name)} + + + + + + + HTML + response.headers.delete("X-Frame-Options") response.headers["Content-Security-Policy"] = "script-src 'unsafe-inline';" # Render the content - render html: html.html_safe, layout: false, content_type: "text/html" + render html: trusted_html.html_safe, layout: false, content_type: "text/html" end private diff --git a/spec/requests/ai_bot/artifacts_controller_spec.rb b/spec/requests/ai_bot/artifacts_controller_spec.rb index 0899db08..10f7bcde 100644 --- a/spec/requests/ai_bot/artifacts_controller_spec.rb +++ b/spec/requests/ai_bot/artifacts_controller_spec.rb @@ -18,6 +18,10 @@ RSpec.describe DiscourseAi::AiBot::ArtifactsController do ) end + def parse_srcdoc(html) + Nokogiri.HTML5(html).at_css("iframe")["srcdoc"] + end + before do SiteSetting.discourse_ai_enabled = true SiteSetting.ai_artifact_security = "strict" @@ -46,9 +50,10 @@ RSpec.describe DiscourseAi::AiBot::ArtifactsController do sign_in(user) get "/discourse-ai/ai-bot/artifacts/#{artifact.id}" expect(response.status).to eq(200) - expect(response.body).to include(artifact.html) - expect(response.body).to include(artifact.css) - expect(response.body).to include(artifact.js) + untrusted_html = parse_srcdoc(response.body) + expect(untrusted_html).to include(artifact.html) + expect(untrusted_html).to include(artifact.css) + expect(untrusted_html).to include(artifact.js) end end @@ -58,7 +63,7 @@ RSpec.describe DiscourseAi::AiBot::ArtifactsController do it "shows artifact without authentication" do get "/discourse-ai/ai-bot/artifacts/#{artifact.id}" expect(response.status).to eq(200) - expect(response.body).to include(artifact.html) + expect(parse_srcdoc(response.body)).to include(artifact.html) end end