CVE-2018-3769
Description
Grape gem for Ruby fails to escape user-controlled input in the 'format' parameter when returning HTML error messages, leading to XSS.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Grape gem for Ruby fails to escape user-controlled input in the 'format' parameter when returning HTML error messages, leading to XSS.
Vulnerability
CVE-2018-3769 is a cross-site scripting (XSS) vulnerability in the grape gem for Ruby [1]. The bug exists in the error handling middleware: when the server returns an HTML error response, the user-supplied format parameter is directly embedded in the error message without HTML escaping. This affects versions prior to the fix that introduced ERB::Util.html_escape for HTML content types [2]. The vulnerable code path is reachable by requesting an unsupported or invalid format (e.g., format=txt).
Exploitation
An attacker does not need authentication or any special access. The only requirement is that the vulnerable application uses the Grape framework and that an attacker can control the format query parameter or route extension. By sending a request with a malicious payload in the format parameter (e.g., format=), the server will reflect that payload in the HTML error response. The attacker would need to trick a user into visiting such a crafted URL; no user interaction beyond normal browsing is required for the reflected XSS to trigger in the victim's browser.
Impact
Successful exploitation allows an attacker to execute arbitrary JavaScript in the context of the victim's browser. This can lead to session hijacking, theft of sensitive cookies, phishing redirections, or defacement. The impact depends on the application's session and security context, but because the attack is Reflected XSS, it typically requires social engineering (e.g., sending a crafted link) to achieve full compromise.
Mitigation
The Grape project fixed this issue in commit 6876b71 [2]. The fix adds ActiveSupport::CoreExt::String::OutputSafety and uses ERB::Util.html_escape on the error message when the response content type is HTML. The advisory database indicates the fix was included in an unspecified release following the commit [4]. Users should upgrade to Grape version 1.0.3 or later, which contains the patch [3]. No known workarounds were provided, and the vulnerability is not listed in CISA's Known Exploited Vulnerabilities (KEV) catalog as of the last check.
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
grapeRubyGems | < 1.1.0 | 1.1.0 |
Affected products
2- Ruby Grape/ruby-grape ruby gemv5Range: >= 1.0.3
Patches
16876b71efc7bWhen returning an HTML error, make sure it's safe (#1763)
4 files changed · +36 −6
CHANGELOG.md+2 −0 modified@@ -6,6 +6,8 @@ #### Fixes + +* [#1762](https://github.com/ruby-grape/grape/pull/1763): Fix unsafe HTML rendering on errors - [@ctennis](https://github.com/ctennis). * [#1759](https://github.com/ruby-grape/grape/pull/1759): Update appraisal for rails_edge - [@zvkemp](https://github.com/zvkemp). * [#1758](https://github.com/ruby-grape/grape/pull/1758): Fix expanding load_path in gemspec - [@2maz](https://github.com/2maz). * Your contribution here.
lib/grape/middleware/error.rb+4 −0 modified@@ -1,4 +1,5 @@ require 'grape/middleware/base' +require 'active_support/core_ext/string/output_safety' module Grape module Middleware @@ -69,6 +70,9 @@ def error_response(error = {}) end def rack_response(message, status = options[:default_status], headers = { Grape::Http::Headers::CONTENT_TYPE => content_type }) + if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML + message = ERB::Util.html_escape(message) + end Rack::Response.new([message], status, headers).finish end
spec/grape/api_spec.rb+26 −2 modified@@ -2142,7 +2142,11 @@ def self.call(message, _backtrace, _option, _env, _original_exception) end get '/excel.json' expect(last_response.status).to eq(406) - expect(last_response.body).to eq("The requested format 'txt' is not supported.") + if ActiveSupport::VERSION::MAJOR == 3 + expect(last_response.body).to eq('The requested format 'txt' is not supported.') + else + expect(last_response.body).to eq('The requested format 'txt' is not supported.') + end end end @@ -3524,7 +3528,27 @@ def before end get '/something' expect(last_response.status).to eq(406) - expect(last_response.body).to eq("{\"error\":\"The requested format 'txt' is not supported.\"}") + if ActiveSupport::VERSION::MAJOR == 3 + expect(last_response.body).to eq('{"error":"The requested format 'txt' is not supported."}') + else + expect(last_response.body).to eq('{"error":"The requested format 'txt' is not supported."}') + end + end + end + + context 'with unsafe HTML format specified' do + it 'escapes the HTML' do + subject.content_type :json, 'application/json' + subject.get '/something' do + 'foo' + end + get '/something?format=<script>blah</script>' + expect(last_response.status).to eq(406) + if ActiveSupport::VERSION::MAJOR == 3 + expect(last_response.body).to eq('The requested format '<script>blah</script>' is not supported.') + else + expect(last_response.body).to eq('The requested format '<script>blah</script>' is not supported.') + end end end
spec/grape/middleware/exception_spec.rb+4 −4 modified@@ -192,7 +192,7 @@ def app end it 'is possible to return errors in jsonapi format' do get '/' - expect(last_response.body).to eq('{"error":"rain!"}') + expect(last_response.body).to eq('{"error":"rain!"}') end end @@ -207,8 +207,8 @@ def app it 'is possible to return hash errors in jsonapi format' do get '/' - expect(['{"error":"rain!","detail":"missing widget"}', - '{"detail":"missing widget","error":"rain!"}']).to include(last_response.body) + expect(['{"error":"rain!","detail":"missing widget"}', + '{"detail":"missing widget","error":"rain!"}']).to include(last_response.body) end end @@ -258,7 +258,7 @@ def app end it 'is possible to specify a custom formatter' do get '/' - expect(last_response.body).to eq('{:custom_formatter=>"rain!"}') + expect(last_response.body).to eq('{:custom_formatter=>"rain!"}') end end
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-f599-5m7p-hcpfghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2018-3769ghsaADVISORY
- github.com/ruby-grape/grape/commit/6876b71efc7b03f7ce1be3f075eaa4e7e6de19afghsax_refsource_CONFIRMWEB
- github.com/ruby-grape/grape/issues/1762ghsax_refsource_CONFIRMWEB
- github.com/ruby-grape/grape/pull/1763ghsax_refsource_CONFIRMWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/grape/CVE-2018-3769.ymlghsaWEB
News mentions
0No linked articles in our index yet.