Skip to content

Fix XSS vulnerability (CWE-79) via globally disabled HTML escaping in HAML - #443

Merged
robertcheramy merged 4 commits into
ytti:masterfrom
mattimustang:fix/xss-escape-html
Jul 31, 2026
Merged

Fix XSS vulnerability (CWE-79) via globally disabled HTML escaping in HAML#443
robertcheramy merged 4 commits into
ytti:masterfrom
mattimustang:fix/xss-escape-html

Conversation

@mattimustang

Copy link
Copy Markdown
Contributor

Summary

  • Enable escape_html: true globally in webapp.rb (root fix); all #{} interpolations in HAML now auto-escape, closing reflected XSS via URL parameters (node, group, num, node_full) and stored XSS via node/model/group names
  • Fix layout.haml: change =yield to != yield so the pre-rendered child template HTML is not double-escaped by the global setting
  • Fix node.haml: change =escape_once(...) to != escape_once(...) so already HTML-encoded JSON output is not double-encoded
  • Add four security regression tests in spec/web/xss_spec.rb covering reflected XSS on /node/version, /node/version/view, /node/version/diffs, and stored XSS on /nodes

Fixes CWE-79 / CVSSv4.0 6.9 (reflected XSS) and 5.3 (stored XSS) as reported in the security vulnerability report.

All existing tests continue to pass (39 pre-existing + 4 new = 43 total).

Notes on existing != usage

All pre-existing != operators in templates are intentional and remain safe:

  • version.haml and diffs.haml: != escape_once("#{line}") renders content that has already been through HTMLEntities.new.encode()!= is required to avoid double-encoding the HTML entities
  • layout.haml: !=haml :head and !=haml :footer render trusted internal HAML partials

Test plan

  • Run rake test — 43 runs, 0 failures
  • Verify reflected XSS PoC URLs from the report no longer execute script (node name appears HTML-encoded in response)
  • Verify node list page renders correctly with normal node names
  • Verify version view and diff pages render device configuration correctly (pre-encoded content still displays properly)

…plates

Enable escape_html: true globally in webapp.rb (was false), which HTML-escapes
all #{} interpolations in HAML templates. This fixes reflected XSS via URL
parameters (node, group, num, node_full) and stored XSS via node/model/group
names from the Oxidized configuration.

Two follow-on fixes prevent regressions from the global change:
- layout.haml: change =yield to != yield so the pre-rendered child template
  HTML is not double-escaped
- node.haml: change =escape_once(...) to != escape_once(...) so the already
  HTML-encoded JSON output is not double-encoded

Adds four security regression tests in spec/web/xss_spec.rb covering the
reflected and stored XSS vectors.

Fixes CWE-79 / CVSSv4.0 6.9 (reflected) and 5.3 (stored).
@mattimustang

Copy link
Copy Markdown
Contributor Author

Hi @ytti, just checking on the status of this security fix?

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the Oxidized Sinatra/HAML web UI against CWE-79 (XSS) by enabling global HAML escaping and adjusting a couple of templates to avoid double-escaping, along with adding regression tests to prevent reintroduction.

Changes:

  • Enable global HAML escape_html in WebApp to ensure interpolated values are HTML-escaped by default.
  • Update layout.haml and node.haml to use != where content is already-rendered/escaped to prevent double-encoding.
  • Add security regression specs covering reflected and stored XSS scenarios, and document the fix in the changelog.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
spec/web/xss_spec.rb Adds regression coverage for reflected and stored XSS in key endpoints/views.
lib/oxidized/web/webapp.rb Enables global HAML HTML escaping to close template-level XSS vectors.
lib/oxidized/web/views/node.haml Prevents double-escaping for already-escaped JSON content display.
lib/oxidized/web/views/layout.haml Prevents double-escaping of rendered child template output.
CHANGELOG.md Documents the security fix and added regression tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread spec/web/xss_spec.rb
Comment on lines +57 to +69
it 'escapes node names in /nodes list' do
malicious = '<script>alert(1)</script>'
@nodes.expects(:list).returns(
[{ name: malicious, ip: '10.0.0.1', model: 'ios',
full_name: malicious, group: 'default', time: Time.now, mtime: Time.now }]
)

get '/nodes'

_(last_response.ok?).must_equal true
_(last_response.body).must_include('&lt;script&gt;alert(1)&lt;/script&gt;')
_(last_response.body).wont_include('<script>alert(1)</script>')
end
@robertcheramy

Copy link
Copy Markdown
Collaborator

I hope more from the AI Review, including a possible regression analysis 😅

I don't understand how we could have a XSS here - if the node name is not stored in oxidized, we will get a Oxidized::NodeNotFound, not the malicious code.

Anyway, setting escape_html: true is good, so I'll look at this PR anyway.

@robertcheramy
robertcheramy merged commit ab7a473 into ytti:master Jul 31, 2026
7 checks passed
@mattimustang
mattimustang deleted the fix/xss-escape-html branch July 31, 2026 23:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants