From b10be235336ade0f9c6091734955d80503bc870f Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 19 Nov 2024 11:44:17 +0000 Subject: [PATCH] FIX: Ensure artifacts are sandboxed, even when visited directly (#921) It's important that artifacts are never given 'same origin' access to the forum domain, so that they cannot access cookies, or make authenticated HTTP requests. So even when visiting the URL directly, we need to wrap them in a sandboxed iframe. --- .../ai_bot/artifacts_controller.rb | 28 +++++++++++++++++-- .../ai_bot/artifacts_controller_spec.rb | 13 ++++++--- 2 files changed, 34 insertions(+), 7 deletions(-) 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