diff --git a/Gemfile.lock b/Gemfile.lock
index de5f2bc92fa00..e37d749f22768 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -26,63 +26,63 @@ GIT
PATH
remote: .
specs:
- actioncable (5.2.7)
- actionpack (= 5.2.7)
+ actioncable (5.2.7.1)
+ actionpack (= 5.2.7.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
- actionmailer (5.2.7)
- actionpack (= 5.2.7)
- actionview (= 5.2.7)
- activejob (= 5.2.7)
+ actionmailer (5.2.7.1)
+ actionpack (= 5.2.7.1)
+ actionview (= 5.2.7.1)
+ activejob (= 5.2.7.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
- actionpack (5.2.7)
- actionview (= 5.2.7)
- activesupport (= 5.2.7)
+ actionpack (5.2.7.1)
+ actionview (= 5.2.7.1)
+ activesupport (= 5.2.7.1)
rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (5.2.7)
- activesupport (= 5.2.7)
+ actionview (5.2.7.1)
+ activesupport (= 5.2.7.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
- activejob (5.2.7)
- activesupport (= 5.2.7)
+ activejob (5.2.7.1)
+ activesupport (= 5.2.7.1)
globalid (>= 0.3.6)
- activemodel (5.2.7)
- activesupport (= 5.2.7)
- activerecord (5.2.7)
- activemodel (= 5.2.7)
- activesupport (= 5.2.7)
+ activemodel (5.2.7.1)
+ activesupport (= 5.2.7.1)
+ activerecord (5.2.7.1)
+ activemodel (= 5.2.7.1)
+ activesupport (= 5.2.7.1)
arel (>= 9.0)
- activestorage (5.2.7)
- actionpack (= 5.2.7)
- activerecord (= 5.2.7)
+ activestorage (5.2.7.1)
+ actionpack (= 5.2.7.1)
+ activerecord (= 5.2.7.1)
marcel (~> 1.0.0)
- activesupport (5.2.7)
+ activesupport (5.2.7.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
- rails (5.2.7)
- actioncable (= 5.2.7)
- actionmailer (= 5.2.7)
- actionpack (= 5.2.7)
- actionview (= 5.2.7)
- activejob (= 5.2.7)
- activemodel (= 5.2.7)
- activerecord (= 5.2.7)
- activestorage (= 5.2.7)
- activesupport (= 5.2.7)
+ rails (5.2.7.1)
+ actioncable (= 5.2.7.1)
+ actionmailer (= 5.2.7.1)
+ actionpack (= 5.2.7.1)
+ actionview (= 5.2.7.1)
+ activejob (= 5.2.7.1)
+ activemodel (= 5.2.7.1)
+ activerecord (= 5.2.7.1)
+ activestorage (= 5.2.7.1)
+ activesupport (= 5.2.7.1)
bundler (>= 1.3.0)
- railties (= 5.2.7)
+ railties (= 5.2.7.1)
sprockets-rails (>= 2.0.0)
- railties (5.2.7)
- actionpack (= 5.2.7)
- activesupport (= 5.2.7)
+ railties (5.2.7.1)
+ actionpack (= 5.2.7.1)
+ activesupport (= 5.2.7.1)
method_source
rake (>= 0.8.7)
thor (>= 0.19.0, < 2.0)
@@ -274,7 +274,7 @@ GEM
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2)
- loofah (2.14.0)
+ loofah (2.16.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
diff --git a/RAILS_VERSION b/RAILS_VERSION
index 32a40681516ab..120e232312c5c 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-5.2.7
+5.2.7.1
diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md
index cd3565bb83be1..932fa7d83192c 100644
--- a/actioncable/CHANGELOG.md
+++ b/actioncable/CHANGELOG.md
@@ -1,3 +1,8 @@
+## Rails 5.2.7.1 (April 26, 2022) ##
+
+* No changes.
+
+
## Rails 5.2.7 (March 10, 2022) ##
* No changes.
diff --git a/actioncable/lib/action_cable/gem_version.rb b/actioncable/lib/action_cable/gem_version.rb
index a06b023a21d0f..6d9aeafe7afe4 100644
--- a/actioncable/lib/action_cable/gem_version.rb
+++ b/actioncable/lib/action_cable/gem_version.rb
@@ -10,7 +10,7 @@ module VERSION
MAJOR = 5
MINOR = 2
TINY = 7
- PRE = nil
+ PRE = "1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actioncable/package.json b/actioncable/package.json
index 9e9baf30fac35..2c34f59d69973 100644
--- a/actioncable/package.json
+++ b/actioncable/package.json
@@ -1,6 +1,6 @@
{
"name": "actioncable",
- "version": "5.2.7",
+ "version": "5.2.7-1",
"description": "WebSocket framework for Ruby on Rails.",
"main": "lib/assets/compiled/action_cable.js",
"files": [
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 21b06632f88a3..bdf7a884bb4be 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,3 +1,8 @@
+## Rails 5.2.7.1 (April 26, 2022) ##
+
+* No changes.
+
+
## Rails 5.2.7 (March 10, 2022) ##
* No changes.
diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb
index 233a6c25e03da..1caf9f180787b 100644
--- a/actionmailer/lib/action_mailer/gem_version.rb
+++ b/actionmailer/lib/action_mailer/gem_version.rb
@@ -10,7 +10,7 @@ module VERSION
MAJOR = 5
MINOR = 2
TINY = 7
- PRE = nil
+ PRE = "1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index d67bd2645ac0c..1b94e862e39f6 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,8 +1,13 @@
+## Rails 5.2.7.1 (April 26, 2022) ##
+
+* Allow Content Security Policy DSL to generate for API responses.
+
+ *Tim Wade*
+
## Rails 5.2.7 (March 10, 2022) ##
* No changes.
-
## Rails 5.2.6.3 (March 08, 2022) ##
* No changes.
diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb
index 6f9fb11a2950b..69ac64c5a3142 100644
--- a/actionpack/lib/action_dispatch/http/content_security_policy.rb
+++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb
@@ -17,7 +17,6 @@ def call(env)
request = ActionDispatch::Request.new env
_, headers, _ = response = @app.call(env)
- return response unless html_response?(headers)
return response if policy_present?(headers)
if policy = request.content_security_policy
@@ -30,13 +29,6 @@ def call(env)
end
private
-
- def html_response?(headers)
- if content_type = headers[CONTENT_TYPE]
- content_type =~ /html/
- end
- end
-
def header_name(request)
if request.content_security_policy_report_only
POLICY_REPORT_ONLY
diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb
index 606c9149e70f0..98f4d702b1f36 100644
--- a/actionpack/lib/action_pack/gem_version.rb
+++ b/actionpack/lib/action_pack/gem_version.rb
@@ -10,7 +10,7 @@ module VERSION
MAJOR = 5
MINOR = 2
TINY = 7
- PRE = nil
+ PRE = "1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionpack/test/dispatch/content_security_policy_test.rb b/actionpack/test/dispatch/content_security_policy_test.rb
index 71ac63def2184..25feb4c258826 100644
--- a/actionpack/test/dispatch/content_security_policy_test.rb
+++ b/actionpack/test/dispatch/content_security_policy_test.rb
@@ -343,6 +343,11 @@ class PolicyController < ActionController::Base
content_security_policy_report_only only: :report_only
+ content_security_policy only: :api do |p|
+ p.default_src :none
+ p.frame_ancestors :none
+ end
+
def index
head :ok
end
@@ -367,6 +372,10 @@ def no_policy
head :ok
end
+ def api
+ render json: {}
+ end
+
private
def condition?
params[:condition] == "true"
@@ -382,6 +391,7 @@ def condition?
get "/report-only", to: "policy#report_only"
get "/script-src", to: "policy#script_src"
get "/no-policy", to: "policy#no_policy"
+ get "/api", to: "policy#api"
end
end
@@ -448,6 +458,11 @@ def test_generates_no_content_security_policy
assert_nil response.headers["Content-Security-Policy-Report-Only"]
end
+ def test_generates_api_security_policy
+ get "/api"
+ assert_policy "default-src 'none'; frame-ancestors 'none'"
+ end
+
private
def assert_policy(expected, report_only: false)
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 9d93af32fb90b..aa5e0cfebdd7b 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,15 @@
+## Rails 5.2.7.1 (April 26, 2022) ##
+
+* Fix and add protections for XSS in `ActionView::Helpers` and `ERB::Util`.
+
+ Escape dangerous characters in names of tags and names of attributes in the
+ tag helpers, following the XML specification. Rename the option
+ `:escape_attributes` to `:escape`, to simplify by applying the option to the
+ whole tag.
+
+ *Álvaro Martín Fraguas*
+
+
## Rails 5.2.7 (March 10, 2022) ##
* No changes.
diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb
index 3533f9410b792..d6a7ebb1e41cd 100644
--- a/actionview/lib/action_view/gem_version.rb
+++ b/actionview/lib/action_view/gem_version.rb
@@ -10,7 +10,7 @@ module VERSION
MAJOR = 5
MINOR = 2
TINY = 7
- PRE = nil
+ PRE = "1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index a6cec3f69cfaa..9f5d90fc854a3 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -41,18 +41,25 @@ def initialize(view_context)
@view_context = view_context
end
- def tag_string(name, content = nil, escape_attributes: true, **options, &block)
+ def tag_string(name, content = nil, **options, &block)
+ escape = handle_deprecated_escape_options(options)
content = @view_context.capture(self, &block) if block_given?
+
if VOID_ELEMENTS.include?(name) && content.nil?
- "<#{name.to_s.dasherize}#{tag_options(options, escape_attributes)}>".html_safe
+ "<#{name.to_s.dasherize}#{tag_options(options, escape)}>".html_safe
else
- content_tag_string(name.to_s.dasherize, content || "", options, escape_attributes)
+ content_tag_string(name.to_s.dasherize, content || "", options, escape)
end
end
def content_tag_string(name, content, options, escape = true)
tag_options = tag_options(options, escape) if options
- content = ERB::Util.unwrapped_html_escape(content) if escape
+
+ if escape
+ name = ERB::Util.xml_name_escape(name)
+ content = ERB::Util.unwrapped_html_escape(content)
+ end
+
"<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}#{name}>".html_safe
end
@@ -85,6 +92,8 @@ def boolean_tag_option(key)
end
def tag_option(key, value, escape)
+ key = ERB::Util.xml_name_escape(key) if escape
+
if value.is_a?(Array)
value = escape ? safe_join(value, " ".freeze) : value.join(" ".freeze)
else
@@ -106,8 +115,29 @@ def respond_to_missing?(*args)
true
end
- def method_missing(called, *args, &block)
- tag_string(called, *args, &block)
+ def handle_deprecated_escape_options(options)
+ # The option :escape_attributes has been merged into the options hash to be
+ # able to warn when it is used, so we need to handle default values here.
+ escape_option_provided = options.has_key?(:escape)
+ escape_attributes_option_provided = options.has_key?(:escape_attributes)
+
+ if escape_attributes_option_provided
+ ActiveSupport::Deprecation.warn(<<~MSG)
+ Use of the option :escape_attributes is deprecated. It currently \
+ escapes both names and values of tags and attributes and it is \
+ equivalent to :escape. If any of them are enabled, the escaping \
+ is fully enabled.
+ MSG
+ end
+
+ return true unless escape_option_provided || escape_attributes_option_provided
+ escape_option = options.delete(:escape)
+ escape_attributes_option = options.delete(:escape_attributes)
+ escape_option || escape_attributes_option
+ end
+
+ def method_missing(called, *args, **options, &block)
+ tag_string(called, *args, **options, &block)
end
end
@@ -236,6 +266,7 @@ def tag(name = nil, options = nil, open = false, escape = true)
if name.nil?
tag_builder
else
+ name = ERB::Util.xml_name_escape(name) if escape
"<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
end
end
diff --git a/actionview/package.json b/actionview/package.json
index 798fb04926b60..af01b0a73cabf 100644
--- a/actionview/package.json
+++ b/actionview/package.json
@@ -1,6 +1,6 @@
{
"name": "rails-ujs",
- "version": "5.2.7",
+ "version": "5.2.7-1",
"description": "Ruby on Rails unobtrusive scripting adapter",
"main": "lib/assets/compiled/rails-ujs.js",
"files": [
diff --git a/actionview/test/template/tag_helper_test.rb b/actionview/test/template/tag_helper_test.rb
index 9a6226fd04f1a..2f53826c9cfa7 100644
--- a/actionview/test/template/tag_helper_test.rb
+++ b/actionview/test/template/tag_helper_test.rb
@@ -7,6 +7,8 @@ class TagHelperTest < ActionView::TestCase
tests ActionView::Helpers::TagHelper
+ COMMON_DANGEROUS_CHARS = "&<>\"' %*+,/;=^|"
+
def test_tag
assert_equal "
", tag("br")
assert_equal "
", tag(:br, clear: "left")
@@ -79,6 +81,84 @@ def test_tag_builder_options_converts_boolean_option
tag.p(disabled: true, itemscope: true, multiple: true, readonly: true, allowfullscreen: true, seamless: true, typemustmatch: true, sortable: true, default: true, inert: true, truespeed: true)
end
+ def test_tag_builder_do_not_modify_html_safe_options
+ html_safe_str = '"'.html_safe
+ assert_equal "
<script>evil_js</script>
", tag.p("") assert_equal "", - tag.p("", escape_attributes: false) + tag.p("", escape: false) end def test_tag_builder_nested @@ -213,10 +293,10 @@ def test_content_tag_with_unescaped_array_class end def test_tag_builder_with_unescaped_array_class - str = tag.p "limelight", class: ["song", "play>"], escape_attributes: false + str = tag.p "limelight", class: ["song", "play>"], escape: false assert_equal "\">limelight
", str - str = tag.p "limelight", class: ["song", ["play>"]], escape_attributes: false + str = tag.p "limelight", class: ["song", ["play>"]], escape: false assert_equal "\">limelight
", str end @@ -235,7 +315,7 @@ def test_content_tag_with_unescaped_empty_array_class end def test_tag_builder_with_unescaped_empty_array_class - str = tag.p "limelight", class: [], escape_attributes: false + str = tag.p "limelight", class: [], escape: false assert_equal 'limelight
', str end @@ -306,11 +386,11 @@ def test_disable_escaping end def test_tag_builder_disable_escaping - assert_equal '', tag.a(href: "&", escape_attributes: false) - assert_equal 'cnt', tag.a(href: "&", escape_attributes: false) { "cnt" } - assert_equal '