Project

General

Profile

Feature #18367

Updated by mame (Yusuke Endoh) over 2 years ago

## Proposal 

 At the present time, the Ruby interpreter escapes some characters (*1) in error messages when an uncaught error is printed. I'd like to propose stopping this escaping behavior. 

 ``` 
 class MyError < StandardError 
   def message 
     "foo\\bar" 
   end 
 end 

 raise MyError 
 #=> current:    test.rb:7: in `<main>': foo\\bar (MyError) 
 #=> excepted: test.rb:7: in `<main>': foo\bar (MyError) 
 ``` 

 *1: Escaped characters are any control characters except `\t` `\r` and `\n`, and a backslash `\\`. 


 ## Motivation 

 This behavior prevents us from adding an attribution (color, underline, etc.) to the error message because it escapes escape sequences. Nowadays, such a rich presentation of terminal output is more and more important. 

 ``` 
 $ ruby -e 'raise "\e[31mRed\x1b[0m error"' 
 -e:1:in `<main>': \e[31mRed\x1b[0m error (RuntimeError) 
 ``` 

 Also, the behavior in question leads to rather confusing error printing. See the error output of `"\\".no_method`: 

 ``` 
 $ ruby -e '"\\".no_method' 
 -e:1:in `<main>': undefined method `no_method' for "\\\\":String (NoMethodError) 

 "\\\\".no_method 
     ^^^^^^^^^^ 
 ``` 

 The two occurrences of `"\\\\"` must be `"\\"`. Worse, the output of error_highlight `^^^^` points wrong position. 

 Note that this issue is never specific to error_highlight. The receiver of NoMethodError, `"\\\\":String`, is also wrongly escaped. It must be `"\\":String`. 


 ## Why the escaping behavior was introduced 

 AFAIK, the behavior was introduced because of a security concern. It is considered harmful for an attacker to be able to print arbitrary escape sequences to victim's terminal. (See [this article](https://marc.info/?l=bugtraq&m=104612710031920&w=2) in detail.) 

 However, I believe it is rare to see the error logs of an application that may be exposed to attacks (i.e. in production mode) in a terminal, as the error output of the Ruby interpreter. 

 Even if that is the case, I think such escaping should be done as a responsibility of the application, and not implicitly by the interpreter. I briefly surveyed other major languages than Ruby, and I could find no language that escapes error messages. This is the transcript of Python and Node.js. 

 ``` 
 $ python3 -c 'raise Exception("\x1b[31mRed\x1b[0m error")' 
 Traceback (most recent call last): 
   File "<string>", line 1, in <module> 
 Exception: Red error 

 $ node -e 'throw("\x1b[31mRed\x1b[0m error")' 

 [eval]:1 
 throw("\x1b[31mRed\x1b[0m error") 
 ^ 
 Red error 
 (Use `node --trace-uncaught ...` to show where the exception was thrown) 
 ``` 

 Just in case, I reported these behaviors to the security contacts of Python and Node.js, and both responded to me that this is not a securty issue. I think their decisions are quite reasonable. 

 ## Migration 

 It would be a good idea to first make the following behavior as a migration path. 

 * When an error message does not include a control character, no escaping is applied. 
 * When an error message does include a control character, "Warning: this error message is currently escaped because it includes a control character(s), but this will not be escaped in Ruby 3.X" is printed, and the escaping is applied. 

Back