مراية لـ
https://github.com/postalserver/postal.git
تم المزامنة 2026-05-31 04:35:42 +00:00
fix(messages): sandbox rendered email HTML as extra XSS defence
The app-wide CSP already blocks inline script execution, but the HTML preview iframe for a stored email was same-origin and un-sandboxed, and the html_raw response had no per-action hardening. Add a sandbox on the iframe and tighten the CSP on html_raw to script-src 'none' with nosniff and no-referrer so the preview has defence in depth against a future CSP bypass or regression. Relates to GHSA-f6g9-8555-cw28.
هذا الالتزام موجود في:
@@ -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
|
||||
|
||||
|
||||
@@ -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)}
|
||||
|
||||
58
spec/requests/messages_controller_spec.rb
Normal file
58
spec/requests/messages_controller_spec.rb
Normal file
@@ -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) { %(<script>alert("XSS")</script>) }
|
||||
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 %(<html><body><p>hello</p>#{payload}</body></html>)
|
||||
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
|
||||
المرجع في مشكلة جديدة
حظر مستخدم