From 29790c2c3029c63db43771a0f872259f0bfe49c4 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 08:20:47 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[HIGH]?= =?UTF-8?q?=20Fix=20XSS=20in=20default=20HTML=20error=20response?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The default HTML error response directly embedded the exception message without escaping it, leading to a Reflected Cross-Site Scripting (XSS) vulnerability. This commit properly requires the `html` standard library module and utilizes `HTML.escape` to neutralize the exception message before embedding it in the HTML template. It also properly handles the `String?` type of `@ex.message` in Crystal. Co-authored-by: renich <225115+renich@users.noreply.github.com> --- .jules/sentinel.md | 4 ++++ src/amber/controller/error.cr | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .jules/sentinel.md 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/src/amber/controller/error.cr b/src/amber/controller/error.cr index f709f7023..f714b87b5 100644 --- a/src/amber/controller/error.cr +++ b/src/amber/controller/error.cr @@ -1,5 +1,6 @@ require "./base" require "../exceptions/page" +require "html" module Amber::Controller class Error < Base @@ -48,7 +49,7 @@ module Amber::Controller if Amber.env.development? Amber::Exceptions::Page.new(context, @ex) else - "
#{@ex.message}
" + "
#{HTML.escape(@ex.message || "")}
" end end end From ca08704b514c9f9363358caf25fde797401a7436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9nich=20Bon=20=C4=86iri=C4=87?= Date: Tue, 16 Jun 2026 22:42:53 -0600 Subject: [PATCH 2/2] security: add spec for escaping HTML in default error response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a regression spec in error_spec.cr verifying that exception messages are properly escaped when text/html is requested in production mode. Co-developed-by: Gemini AI Signed-off-by: Rénich Bon Ćirić --- spec/amber/pipes/error_spec.cr | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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("