Skip to content

CWE-95 + frontend/template XSS precision - #73

Merged
leenk7991 merged 11 commits into
mainfrom
feat/cwe95-xss-sink-precision
Sep 17, 2026
Merged

leenk7991 merged 11 commits into
mainfrom
feat/cwe95-xss-sink-precision

Conversation

@leenk7991

@leenk7991 leenk7991 commented Aug 19, 2026

Copy link
Copy Markdown
Member

Summary

Stop Sighthound from reporting DOM APIs as CWE-95 eval injection, and cut Django/HTMX XSS noise without dropping real DOM XSS.

Problem: CWE-95 findings were innerHTML/htmx/DOMPurify writes, rated Critical. HTML-only files failed combined mode (No taint flow rules found) so Django |safe never ran. vm.runInNewContext(userInput) was silent (taint-only sink; frontend sources are DOM, not generic params; search prefilter dropped regex-escaped vm.runIn* names). escapeHtml (textContent then read innerHTML) was CWE-79.

Root cause: CWE-95 sink list too broad (any HTML write ≈ eval). Combined mode required taint rules even when the language only has search rules. HTML language did not visit text nodes. No search-mode vm pattern that survived the call-name prefilter.

Changes

  • eval sinks: eval / Function / setTimeout(string literal) / setInterval(string literal) / vm.runIn* only — not HTML writes, not setTimeout(handler, n)
  • Search timers match a quote after ( (setTimeout('…')); identifier callbacks stay quiet. Taint still flags a timer when the first arg is a known source
  • Dummy vm.runInNewContext= (and This/Context) names so the search prefilter keeps vm.runIn* (escaped regex dots are not a substring of the call name)
  • XSS: taint + unsafe HTML sink; autoescape / DOMPurify / json_script / hx-swap allow
  • escapeHtml: textContent write then innerHTML read is a sanitizer, not CWE-79
  • Django: |safe / |mark_safe on request.GET/POST/COOKIES only
  • Combined mode skips taint when there are 0 taint rules (HTML/Django search still runs)
  • HTML language visits text nodes so |safe matches when .html auto-detects as html
  • Backend SSTI (ejs.render / handlebars / mustache) is no longer CWE-95 (eval-family only; CWE-94 follow-up)
  • build.rs reruns compile when rules/ change
  • Ignore /sighthound_release/ (local platform export; do not commit the binary)
  • fixtures: TN innerHTML helper, escapeHtml, setTimeout/setInterval callbacks and setTimeout(handler); TP eval / Function / string-literal timers / vm / |safe

Test plan

  • existing JS XSS TPs still fire
  • new TNs do not fire CWE-95 (escapeHtml, DOMPurify, template.innerHTML, htmx.trigger, callback timers)
  • escapeHtml is not CWE-79
  • innerHTML = location.hash remains CWE-79
  • string setTimeout(userInput) is CWE-95; setTimeout(function () {…}) / setTimeout(handler) is not
  • string setInterval(userInput) is CWE-95; setInterval(function () {…}) is not
  • eval / new Function / Function('return '+x) / eval(location.hash) / vm.runInNewContext(userInput) are CWE-95
  • Django |safe and | mark_safe on request data are CWE-79; autoescape / json_script / hx-swap are not
  • scanning the same template as language html still flags |safe
  • cargo test --test strictness_tests -- cwe95_xss_sink_precision

E2E (fixture Django + JS app, this branch vs main):

  • eval / Function reported as CWE-95 (were CWE-94 on main)
  • setTimeout(userInput) with a string argument reported as CWE-95 (missing on main)
  • innerHTML = location.hash still reported as CWE-79
  • escapeHtml / createElement / parse-only innerHTML helpers no longer reported as CWE-95
  • setTimeout(function () {…}) callback not CWE-95
  • Django |safe / |mark_safe on request data reported as CWE-79 (missing on main)

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

linear ticket: https://linear.app/corgea/issue/COR-1802/

leenk7991 and others added 3 commits August 19, 2026 15:56
Skip the taint pass when a pack has no taint rules, and match template text on auto-detected HTML so CLI/Fusion no longer drop search findings.

Co-authored-by: Cursor <cursoragent@cursor.com>
Search-mode vm patterns and setInterval(string) TPs; treat textContent then innerHTML as a sanitizer. Identifier setTimeout callbacks stay quiet.

Co-authored-by: Cursor <cursoragent@cursor.com>
@leenk7991
leenk7991 marked this pull request as ready for review August 20, 2026 08:45
Comment thread build.rs Outdated
Comment thread build.rs Outdated
Comment thread src/scanner/utils.rs
@leenk7991
leenk7991 requested a review from juangaitanv August 24, 2026 09:36
Comment thread rules/javascript/frontend_security.ron
Comment thread rules/backend_javascript/backend_security.ron
Comment thread src/language.rs
leenk7991 and others added 2 commits August 26, 2026 16:59
Keep search-only rule packs skipping taint with a warning instead of aborting.

Co-authored-by: Cursor <cursoragent@cursor.com>
@leenk7991
leenk7991 requested a review from juangaitanv August 26, 2026 14:17
Comment thread rules/html/django.ron Outdated
Comment thread rules/javascript/frontend_security.ron
Comment thread build.rs Outdated
Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread src/scanner/scanning_logic.rs
Comment thread src/common.rs
Comment thread src/scanner/utils.rs Outdated
Comment thread rules/html/django.ron Outdated

@juangaitanv juangaitanv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm

@leenk7991
leenk7991 merged commit 38e0f97 into main Sep 17, 2026
4 checks passed
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.

2 participants