diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 000000000..957633d6c --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2026-06-11 - [XSS in Default Error Handler] +**Vulnerability:** Reflected Cross-Site Scripting (XSS) in default HTML error response. +**Learning:** The default error controller generated raw HTML incorporating the unescaped Exception message. In Crystal, `Exception#message` can be `nil` (`String?`), so safely interpolating it into HTML requires `HTML.escape(@ex.message || "")` and explicitly requiring the `"html"` module. +**Prevention:** Always escape user-controllable or dynamic string data, including exception messages, before interpolating it into an HTML template or string. diff --git a/spec/amber/pipes/error_spec.cr b/spec/amber/pipes/error_spec.cr index 6148f531c..66280406a 100644 --- a/spec/amber/pipes/error_spec.cr +++ b/spec/amber/pipes/error_spec.cr @@ -25,6 +25,19 @@ module Amber response.status_code.should eq 500 end + + it "escapes exception messages in the HTML response body" do + error = Error.new + error.next = ->(_context : HTTP::Server::Context) { raise "" } + request = HTTP::Request.new("GET", "/") + request.headers["Accept"] = "text/html" + + response = create_request_and_return_io(error, request) + + response.status_code.should eq 500 + response.body.should contain("<script>alert('xss')</script>") + response.body.should_not contain("