diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb index ea0e940..9d59f08 100644 --- a/app/controllers/messages_controller.rb +++ b/app/controllers/messages_controller.rb @@ -89,6 +89,18 @@ class MessagesController < ApplicationController end def html_raw + override_content_security_policy_directives( + default_src: %w('none'), + script_src: %w('none'), + style_src: %w('unsafe-inline'), + img_src: %w(* data:), + font_src: %w(*), + frame_ancestors: %w('self'), + form_action: %w('none'), + base_uri: %w('none') + ) + response.headers["X-Content-Type-Options"] = "nosniff" + response.headers["Referrer-Policy"] = "no-referrer" render html: @message.html_body_without_tracking_image.html_safe end diff --git a/app/views/messages/html.html.haml b/app/views/messages/html.html.haml index 357f2cb..826f7e1 100644 --- a/app/views/messages/html.html.haml +++ b/app/views/messages/html.html.haml @@ -14,4 +14,4 @@ This means that we no longer store the raw data for this e-mail or the e-mail didn't include a HTML part. - else - %iframe{:width => "100%", :height => "100%", :src => html_raw_organization_server_message_path(organization, @server, @message.id)} + %iframe{:width => "100%", :height => "100%", :sandbox => "allow-popups allow-popups-to-escape-sandbox", :referrerpolicy => "no-referrer", :src => html_raw_organization_server_message_path(organization, @server, @message.id)} diff --git a/spec/requests/messages_controller_spec.rb b/spec/requests/messages_controller_spec.rb new file mode 100644 index 0000000..524d32b --- /dev/null +++ b/spec/requests/messages_controller_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "MessagesController", type: :request do + let(:user) { create(:user, admin: true) } + let(:organization) { create(:organization, owner: user) } + let(:server) { create(:server, organization: organization) } + + before do + post "/login", params: { email_address: user.email_address, password: "passw0rd" } + end + + describe "GET /org/:org/servers/:server/messages/:id/html_raw" do + let(:xss_payload) { %() } + let(:message) do + payload = xss_payload + MessageFactory.incoming(server) do |_msg, mail| + mail.html_part = Mail::Part.new do + content_type "text/html; charset=UTF-8" + body %(

hello

#{payload}) + end + end + end + + before do + get "/org/#{organization.permalink}/servers/#{server.permalink}/messages/#{message.id}/html_raw" + end + + it "returns the stored email HTML" do + expect(response).to have_http_status(:ok) + expect(response.body).to include("hello") + end + + it "serves a restrictive Content-Security-Policy that blocks scripts" do + csp = response.headers["Content-Security-Policy"] + expect(csp).to include("script-src 'none'") + expect(csp).to include("default-src 'none'") + expect(csp).to include("form-action 'none'") + expect(csp).to include("base-uri 'none'") + end + + it "sets X-Content-Type-Options and Referrer-Policy on the response" do + expect(response.headers["X-Content-Type-Options"]).to eq "nosniff" + expect(response.headers["Referrer-Policy"]).to eq "no-referrer" + end + end + + describe "messages/html view template" do + # We assert against the template source rather than rendering it in a + # request spec because the full application layout depends on the asset + # pipeline which is not configured in this test environment. + it "embeds the html_raw view inside a sandboxed iframe" do + template = Rails.root.join("app/views/messages/html.html.haml").read + expect(template).to match(/%iframe\{[^}]*:sandbox\s*=>/) + end + end +end