diff --git a/CHANGELOG.md b/CHANGELOG.md index 5923feb..37cbeb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - Remove the no longer needed escape_once helper call in node.haml, relying on HAML's global escape_html instead (@robertcheramy) - Update web libraries to the latest versions (@robertcheramy) - Encode JSON output with the standard library instead of Sinatra's json helper (@robertcheramy) +- Set HAML's `attr_quote` explicitly to `"` for stable output across HAML versions (@robertcheramy) ### Fixed - Fix XSS vulnerability (CWE-79) by enabling HAML's escape_html globally; user-controlled values in node names, group names, model names, and URL parameters are now HTML-escaped in all templates (@mattimustang) diff --git a/lib/oxidized/web/webapp.rb b/lib/oxidized/web/webapp.rb index edaa77f..c3bc1e0 100644 --- a/lib/oxidized/web/webapp.rb +++ b/lib/oxidized/web/webapp.rb @@ -10,7 +10,10 @@ module API class WebApp < Sinatra::Base helpers Sinatra::UrlForHelper set :public_folder, proc { File.join(root, 'public') } - set :haml, { escape_html: true } + # attr_quote: use double quotes for HTML attributes. Set explicitly so the + # output is stable across Haml versions. Haml 7.3.0 changed the default + # from "'" to '"' (haml/haml#1188); older versions still default to "'". + set :haml, { escape_html: true, attr_quote: '"' } get '/' do redirect url_for('/nodes') diff --git a/oxidized-web.gemspec b/oxidized-web.gemspec index 20d2652..ce3e1f9 100644 --- a/oxidized-web.gemspec +++ b/oxidized-web.gemspec @@ -37,7 +37,7 @@ Gem::Specification.new do |s| s.add_dependency 'emk-sinatra-url-for', '~> 0.2' # HAML 7.0.0 changed default attr_quote from ' to ". # Updating needs the unit tests to be fixed. - s.add_dependency 'haml', '>= 6', '<7' + s.add_dependency 'haml', '>= 6', '< 8' s.add_dependency 'htmlentities', '~> 4.3' s.add_dependency 'json', '~> 2.3' # Only depend on a minimal version of Oxidized so we don't need to diff --git a/spec/web/node/version_spec.rb b/spec/web/node/version_spec.rb index 87b1852..9dcc24b 100644 --- a/spec/web/node/version_spec.rb +++ b/spec/web/node/version_spec.rb @@ -27,26 +27,26 @@ def app get '/node/version?node_full=sw5' _(last_response.ok?).must_equal true _(last_response.body.include?( - "\n3\n" \ + "\n3\n" \ "2025-02-05 19:49:00 +0100\n" )).must_equal true _(last_response.body.include?( - "href='/node/version/view?node=sw5&group=&oid=C006&" \ - "epoch=1738781340&num=3' title='configuration'>" + "href=\"/node/version/view?node=sw5&group=&oid=C006&" \ + "epoch=1738781340&num=3\" title=\"configuration\">" )).must_equal true _(last_response.body.include?( - "href='/node/version/diffs?node=sw5&group=&oid=C006&" \ - "epoch=1738781340&num=3' title='Compare with previous version'>" + "href=\"/node/version/diffs?node=sw5&group=&oid=C006&" \ + "epoch=1738781340&num=3\" title=\"Compare with previous version\">" )).must_equal true _(last_response.body.include?( - "href='/node/version/view?node=sw5&group=&oid=C001&" \ - "epoch=1738778460&num=1' title='configuration'>" + "href=\"/node/version/view?node=sw5&group=&oid=C001&" \ + "epoch=1738778460&num=1\" title=\"configuration\">" )).must_equal true # Compare to the version previous 1 is not possible, so don't display it _(last_response.body.include?( - "href='/node/version/diffs?node=sw5&group=&oid=C001&" \ - "epoch=1738778460&num=1' title='Compare with previous version'>" + "href=\"/node/version/diffs?node=sw5&group=&oid=C001&" \ + "epoch=1738778460&num=1\" title=\"Compare with previous version\">" )).must_equal false end @@ -56,7 +56,7 @@ def app get '/node/version?node_full=group1/sw5' _(last_response.ok?).must_equal true _(last_response.body.include?( - "\n3\n" \ + "\n3\n" \ "2025-02-05 19:49:00 +0100\n" )).must_equal true end @@ -67,7 +67,7 @@ def app get '/node/version?node_full=gr/oup1/sw5' _(last_response.ok?).must_equal true _(last_response.body.include?( - "\n3\n" \ + "\n3\n" \ "2025-02-05 19:49:00 +0100\n" )).must_equal true end @@ -84,7 +84,7 @@ def app # right string _(last_response.body.include?( "Date of version:\n" \ - "" \ + "" \ "#{Time.at(1738781340)}" )).must_equal true end @@ -153,7 +153,7 @@ def app # right string _(last_response.body.include?( "Date of version:\n" \ - "" \ + "" \ "#{Time.at(1738781340)}" )).must_equal true end