Skip to content

Add embedded JavaScript DOM XSS scanning - #54

Open
juangaitanv wants to merge 9 commits into
mainfrom
feat/dom-xss-cwe-79
Open

Add embedded JavaScript DOM XSS scanning#54
juangaitanv wants to merge 9 commits into
mainfrom
feat/dom-xss-cwe-79

Conversation

@juangaitanv

Copy link
Copy Markdown
Contributor

Summary

Add DOM XSS analysis for executable JavaScript embedded in HTML and Django templates.

The scanner now parses eligible inline <script> bodies as JavaScript while preserving the original template path and line coordinates. It reuses the canonical frontend DOM XSS taint rules, tracks browser-controlled values such as location.hash and promise callback values from fetch(...).then(...), and prefers precise taint findings over the broader HTML fallback finding.

This closes a coverage gap where Sighthound simple analysis missed document.write and innerHTML flows inside HTML templates. External, empty, non-executable, and unknown script types remain excluded, safe sanitizer cases remain clean, and native JavaScript scanning behavior is unchanged.

Validation covered the two held-out benchmark cases:

  • DjangoAt: location.hash at line 15 reaches document.write at line 20.
  • PyGoat: fetch(*).then at line 36 reaches innerHTML at line 42.

The full Rust suite passed with 211 tests, the acceptance suite passed with 8 scenarios and 31 steps, and the release and HTML-only builds passed. Corpus comparison added only the two expected detection keys; measured overhead was 0.08 seconds for DjangoAt and 0.04 seconds for PyGoat.

Related issue

None.

Checklist

  • make ci passes locally (the same command CI runs — see CONTRIBUTING.md)
  • Ran make bootstrap once so pre-commit/pre-push hooks are active
  • Added/updated tests for the change
  • Updated docs/rules where relevant

- Match the canonical frontend DOM-XSS rule by id instead of a
  tag heuristic, removing the ambiguity error path
- Pass embedded-JS mode as an explicit parameter instead of
  deriving it from path inequality
- Gate the inline-script range walk to files that need it
- Drop the derivable has_embedded_dom_xss_rules field
- Add Finding::has_tag and use it at all tag-check sites
- Cut redundant allocations in promise-source tracking,
  descendant-sink walk, and fallback preference
- Flatten parse_with_included_ranges error handling
@juangaitanv
juangaitanv marked this pull request as ready for review July 22, 2026 15:50
Comment thread src/scanner/scanning_logic.rs
Comment thread src/scanner/scanning_logic.rs
Comment thread src/scanner/scanning_logic.rs
Comment thread src/rules.rs
@juangaitanv
juangaitanv requested a review from yhoztak July 24, 2026 08:20
- Flag direct assignment flows (element.innerHTML = location.hash) by
  making assignment nodes actionable for the bare source-to-sink path,
  matching sources against the assigned value only
- Scope anonymous callback taint to a positional closure identity so
  reused parameter names across callbacks cannot leak taint
- Apply sanitizer evaluation to resolved promise expressions
  (Promise.resolve(DOMPurify.sanitize(...)))
- Load the canonical DOM-XSS taint rule for html/django in file-rules
  mode, matching embedded-rules behavior, with dedup across languages
- Consolidate canonical DOM-XSS rule selection into
  Rules::frontend_dom_xss_rule_only, shared by embedded and file loaders
- Fold the html/django check into load_file_dom_xss_taint_rule so both
  call sites are a single guarded push
- Read assignment RHS from the AST right field instead of splitting on =
- Defer node text and function context computation off the per-node hot
  path until a matching branch or sink needs them
- Replace the keep-mask retain in prefer_precise_html_dom_xss with owned
  keys and a direct retain
- Dedupe repeated test scaffolding in embedded DOM XSS and modes tests
Propagation could overwrite a variable's direct source record (e.g.
userInput = urlParams.get(...) re-recorded from urlParams as
URLSearchParams), shifting rule selection from the DOM-XSS rule to the
CWE-116 encoding rule for the same flow. Skip propagation when the
target already has a taint record; differential scan vs main restores
CWE-79 attribution in xss_comprehensive_test.js with no lost findings.
Comment thread src/scanner/scanning_logic.rs Outdated
@juangaitanv
juangaitanv requested a review from leenk7991 July 31, 2026 12:14
Comment thread src/scanner/scanning_logic.rs Outdated
@juangaitanv
juangaitanv requested a review from leenk7991 August 5, 2026 10:57
}

let has_embedded_dom_xss_rules = !rules.embedded_dom_xss_rules.is_empty();
if matches!(detected_language, "html" | "django")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

detect_language_from_path never returns "django"

Comment thread src/scanner/modes.rs
return None;
}
let javascript_rules_dir = format!("{}/javascript", base_rules_dir);
Rules::load_from_directory(&javascript_rules_dir).ok()?.frontend_dom_xss_rule_only()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

.ok()? here turns a missing/broken rules/javascript/ into a silent zero-coverage scan that still exits 0

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