Use safe_join instead of html_safe

The name `html_safe` is very confusing, and many developers (including
me a few years ago) think what that method does is convert the HTML
contents to safe content. It's actually quite the opposite: it marks the
string as safe, so the HTML inside it isn't stripped out by Rails.

In some cases we were marking strings as safe because we wanted to add
some HTML. However, it meant the whole string was considered safe, and
not just the contents which were under our control.

In particular, some translations added by admins to the database or
through crowding were marked as safe, when it wasn't necessarily the
case.

Although AFAIK crowdin checks for potential cross-site scripting
attacks, it's a good practice to sanitize parts of a string potentially
out of our control before marking the string as HTML safe.
This commit is contained in:
Javi Martín
2019-10-01 18:50:17 +02:00
parent eb16b9df48
commit db1ccb18c7
4 changed files with 27 additions and 8 deletions

View File

@@ -10,7 +10,7 @@ module BudgetInvestmentsHelper
translation = t("admin.budget_investments.index.list.#{column}")
link_to(
"#{translation} <span class='icon-sortable #{icon}'></span>".html_safe,
safe_join([translation, content_tag(:span, "", class: "icon-sortable #{icon}")]),
admin_budget_budget_investments_path(sort_by: column, direction: direction)
)
end

View File

@@ -50,10 +50,11 @@ module DocumentsHelper
end
def document_item_link(document)
link_to "#{document.title} <small>(#{document.humanized_content_type} | \
#{number_to_human_size(document.attachment_file_size)}</small>)".html_safe,
document.attachment.url,
target: "_blank",
title: t("shared.target_blank")
info_text = "#{document.humanized_content_type} | #{number_to_human_size(document.attachment_file_size)}"
link_to safe_join([document.title, content_tag(:small, "(#{info_text})")], " "),
document.attachment.url,
target: "_blank",
title: t("shared.target_blank")
end
end

View File

@@ -26,9 +26,9 @@ module TranslatableFormHelper
visible_locales.map do |locale|
@translations[locale] = translation_for(locale)
end
visible_locales.map do |locale|
safe_join(visible_locales.map do |locale|
Globalize.with_locale(locale) { fields_for_locale(locale, &block) }
end.join.html_safe
end)
end
private

View File

@@ -12,4 +12,22 @@ describe "Cross-Site Scripting protection", :js do
expect(page.text).not_to be_empty
end
scenario "document title" do
process = create(:legislation_process)
create(:document, documentable: process, title: attack_code)
visit legislation_process_path(process)
expect(page.text).not_to be_empty
end
scenario "hacked translations" do
I18nContent.create(key: "admin.budget_investments.index.list.title", value: attack_code)
login_as(create(:administrator).user)
visit admin_budget_budget_investments_path(create(:budget_investment).budget)
expect(page.text).not_to be_empty
end
end