apache/superset · error · ValidationError
CSS contains a disallowed construct ({label}).
Error message
CSS contains a disallowed construct ({label}). What it means
Custom CSS validator on dashboards: stored CSS is re-served into the dashboard page, so constructs that can execute script or load remote resources are rejected at validation time. Matching patterns include script schemes (javascript:, vbscript:, livescript:, mocha:), legacy IE expression(), and remote url() imports. The error names the offending construct label.
Source
Thrown at superset/dashboards/schemas.py:173
Lightweight input hardening for the user-supplied ``css`` field, which is
persisted and re-served into the dashboard page. Blocks ``expression(``,
script-scheme URIs (e.g. ``javascript:``), ``@import``, and ``url(...)``
referencing a script scheme, while leaving ordinary styling intact.
CSS escape sequences (e.g. ``\\6a avascript:``) are not expanded before
matching, so this validator is a first-line filter and not a complete XSS
sanitiser; it should not be treated as a substitute for other defences.
"""
if not value:
return
if isinstance(value, (bytes, bytearray)):
text = value.decode("utf-8", errors="ignore")
else:
text = value
for label, pattern in _DANGEROUS_CSS_PATTERNS:
if pattern.search(text):
raise ValidationError(f"CSS contains a disallowed construct ({label}).")
class SharedLabelsColorsField(fields.Field):
"""
A custom field that accepts either a list of strings or a dictionary.
"""
def _deserialize(
self,
value: Union[list[str], dict[str, str]],
attr: Union[str, None],
data: Union[Mapping[str, Any], None],
**kwargs: dict[str, Any],
) -> list[str]:
if isinstance(value, list):
if all(isinstance(item, str) for item in value):
return value
elif isinstance(value, dict):View on GitHub (pinned to f4587218dd)
Solutions
- Remove the construct named in the error message label (e.g. drop the expression() rule or the javascript: URL).
- Host assets locally or use data: URIs / relative paths for images, which the validator explicitly allows.
- If a false positive (the pattern appears in an innocent comment/string), reword the comment or remove the literal token.
- Note the documented limitation: CSS escapes are not expanded, so treat this as a first-line filter and keep CSP as a defence in depth.
Example fix
/* before */
.chart { width: expression(document.body.clientWidth); background: url(javascript:alert(1)); }
/* after */
.chart { width: 95%; background: url('/static/img/bg.png'); } Defensive patterns
Strategy: validation
Validate before calling
import re
_BAD = [re.compile(r"expression\s*\(", re.I), re.compile(r"(?:javascript|vbscript|livescript|mocha)\s*:")]
def css_looks_safe(css: str) -> bool:
return not any(p.search(css) for p in _BAD) Try / catch
from marshmallow import ValidationError
try:
validate_css(css)
except ValidationError as ex:
label = str(ex.messages) # names the disallowed construct
raise ValueError(f"remove forbidden CSS construct: {label}") Prevention
- Never put javascript:/vbscript: or expression( in dashboard CSS, even in comments.
- Use relative paths or data: URIs for background images.
- Keep CSP enabled as a second layer — the validator is a first-line filter only.
When it happens
Trigger: PUT/POST a dashboard with css containing 'expression(' anywhere, a javascript:/vbscript: scheme token, or a url() pointing at a remote stylesheet (e.g. @import url(https://evil.com/x.css)).
Common situations: Pasting CSS from old IE-era snippets; attempting to load web fonts or external stylesheets via url(); obfuscated CSS where a scheme string appears inside a comment or string value (the check is textual, so false positives are possible).
Related errors
- security_error
- Dashboard %(dashboard_id)s not found
- Dashboard parameters are invalid.
- Changing this Dashboard is forbidden
- Dashboard parameters are invalid.
AI-assisted analysis of apache/superset@f4587218dd (2026-08-14).
Data as JSON: /api/errors/e4cd786ec9f9716a.
Report an issue: GitHub.