Skip to content

Flag innerHTML/outerHTML += append sink in security-guidance#76475

Open
winklemad wants to merge 1 commit into
anthropics:mainfrom
winklemad:fix/security-guidance-innerhtml-append-sink
Open

Flag innerHTML/outerHTML += append sink in security-guidance#76475
winklemad wants to merge 1 commit into
anthropics:mainfrom
winklemad:fix/security-guidance-innerhtml-append-sink

Conversation

@winklemad

Copy link
Copy Markdown

No linked issue — found while reading the security-guidance patterns.

Summary

The innerHTML_xss and outerHTML_xss rules in plugins/security-guidance/hooks/patterns.py match by substring:

"substrings": [".innerHTML =", ".innerHTML="],

This misses el.innerHTML += userInput — the most common DOM-XSS append sink. += desugars to x = x + …, which re-parses the whole value as HTML, so it's an innerHTML assignment and squarely within the rule's stated scope ("Setting innerHTML with untrusted content can lead to XSS"). Tab- and multi-space-separated assignments (el.innerHTML = x) are missed too. Separately, the substring .innerHTML = also matches .innerHTML == / === comparisons, which are reads rather than sinks — a false positive.

Observed through the shipped check_patterns:

el.innerHTML = x            -> innerHTML_xss   (ok)
el.innerHTML += x           -> (nothing)       <- missed sink
container.outerHTML += x    -> (nothing)       <- missed sink
node.innerHTML  =  x        -> (nothing)       <- missed sink
if (el.innerHTML === '')    -> innerHTML_xss   <- false positive

Fix

Replace the two substring lists with a regex, matching the plugin's own regex-rule style (used by ~20 other rules):

"regex": r"\.innerHTML\s*\+?=(?!=)",

\s* absorbs tabs/extra spaces, \+? catches +=, and the (?!=) lookahead skips ==/=== comparisons (fixing the false positive too). After the change all five cases above behave correctly, and read-only access (const x = el.innerHTML) is still not flagged.

innerHTML_xss and outerHTML_xss were the only two rules using the assignment-substring form [".X =", ".X="], so both are covered here. I also checked the other regex rules that match = (verify=False, shell=True, weights_only=True, …): those are boolean/kwarg flags with no meaningful += form, so they don't share this gap.

Test

Adds plugins/security-guidance/hooks/tests/test_innerhtml_patterns.py (the plugin previously shipped no tests). It parametrizes both rules across every sink form — plain =, +=, no-space +=, multi-space, tab, and optional chaining (obj?.innerHTML =) — and every non-sink form that must stay quiet — ==, ===, and read-only access. 20 cases; it fails on the current code (12 of 20) and passes with the fix.

The innerHTML_xss and outerHTML_xss rules matched by substring
(".innerHTML =" / ".innerHTML="), so `el.innerHTML += userInput` - the most
common DOM XSS append sink - was never flagged, along with tab and
multi-space assignment variants. The same substrings also false-positived on
`==`/`===` comparisons. Replace them with a regex that catches `=` and `+=`
with any whitespace and uses a negative lookahead to skip comparisons.

Adds regression tests (the plugin previously shipped none).
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.

1 participant