Repository: rack/rack (Ruby)

Issue:
HTML escape of slash is not recommended by OWASP
`Rack::Utils.escape_html` escapes `/` to `&#x2F;`, but OWASP doesn't mention it now:

https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html

So `Rack::Utils.escape_html` should escape `&<>'"` only.

## Motivation

There is no difference in the code's safety whether you leave this specification as it is or make changes. However, I spent much time investigating why `Rack::Utils.escape_html` escapes slashes and whether there is any significance to escaping them. Unnecessary escaping can confuse users like me.

Additionally, I found that `CGI.escapeHTML` only escapes the characters `'&"<>`. It also made me confused and investigate why the two similar methods have different specifications.

https://docs.ruby-lang.org/en/3.2/CGI/Util.html#method-i-escapeHTML

## Related pages

- https://github.com/rack/rack/issues/27
- https://github.com/rack/rack/commit/37ca2539c423f29337486cf0469dd49a019cfea3
- https://security.stackexchange.com/a/249423
- https://github.com/OWASP/CheatSheetSeries/commit/d7a6823db92af82da3f9538b23b03851d7556d78


Discussion:
I agree the security should not be impacted by this change, and making it consistent with `CGI.escapeHTML` should allow us to use that implementation for improved performance.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5dc4d41ca..4e7dbb06d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. For info on
 ### SPEC Changes
 
 - `rack.input` is now optional. ([#1997](https://github.com/rack/rack/pull/1997), [@ioquatix])
+- `Rack::Utils.escape_html` doesn't escape forward slash (`/`) now. ([#2097](https://github.com/rack/rack/pull/2097), [@JunichiIto])
 
 ### Changed
 
diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb
index d77fb9bcf..79cbbefc2 100644
--- a/lib/rack/utils.rb
+++ b/lib/rack/utils.rb
@@ -179,15 +179,14 @@ def best_q_match(q_value_header, available_mimes)
       "<" => "&lt;",
       ">" => "&gt;",
       "'" => "&#x27;",
-      '"' => "&quot;",
-      "/" => "&#x2F;"
+      '"' => "&quot;"
     }
 
     ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
 
     # Escape ampersands, brackets and quotes to their HTML/XML entities.
     def escape_html(string)
-      string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
+      string.to_s.gsub(ESCAPE_HTML_PATTERN, ESCAPE_HTML)
     end
 
     def select_best_encoding(available_encodings, accept_encoding)


Test patch:
diff --git a/test/spec_utils.rb b/test/spec_utils.rb
index 64e070ed8..6cef53c6e 100644
--- a/test/spec_utils.rb
+++ b/test/spec_utils.rb
@@ -477,8 +477,7 @@ def initialize(*)
     Rack::Utils.escape_html("f>o").must_equal "f&gt;o"
     Rack::Utils.escape_html("f'o").must_equal "f&#x27;o"
     Rack::Utils.escape_html('f"o').must_equal "f&quot;o"
-    Rack::Utils.escape_html("f/o").must_equal "f&#x2F;o"
-    Rack::Utils.escape_html("<foo></foo>").must_equal "&lt;foo&gt;&lt;&#x2F;foo&gt;"
+    Rack::Utils.escape_html("<foo></foo>").must_equal "&lt;foo&gt;&lt;/foo&gt;"
   end
 
   it "escape html entities even on MRI when it's bugged" do