Medium watch CVSS 4.3 chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper encoding or escaping of output in CSS
DescriptionImproper encoding or escaping of output in CSS
ComponentCSS
Bug ClassLogic Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker518082852
Fix commit9ef64552f77b (chromium/src) +10/-1
CWECWE-116
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-05-30 (per vendor advisory)
Fix landed2026-08-03 (public git)
Disclosed2026-09-08
Patch gap36 days — fix public in source before the advisory

Background

Custom property (CSS variable)
an author-defined property such as --a\,fail whose name may contain characters (like ,) that must be escaped when written back out as CSS text.
`CSSUnparsedValue`
the Typed OM object that holds an unparsed CSS value as a list of string and var() reference segments and can serialize itself back to a CSS string.
`SerializeIdentifier`
a Blink helper in css_markup.h that emits an identifier with the CSS escaping rules applied so the output re-parses to the same identifier.
Round-trip serialization
the requirement that a value serialized to text and then re-parsed yields the same value; a name that is not escaped on output breaks this invariant.

Root Cause Analysis

When a CSSUnparsedValue containing a var() reference is serialized, the code path builds the string var( followed by the referenced custom property name. The removed line builder.Append(reference_value->variable()) appended the raw variable name directly, performing no CSS escaping of that identifier. Because custom property names can legally contain characters that are not valid in a bare CSS identifier (the test uses --a\,fail, whose comma must be escaped), the un-escaped output does not re-parse to the original name, so a value like var(--a\,fail) serializes back as var(--a,fail), which refers to a different (or malformed) property.

The fix replaces the raw append with SerializeIdentifier(reference_value->variable(), builder), which applies the identifier escaping rules so the emitted var() name is properly escaped and round-trips correctly, and adds the #include of css_markup.h for that helper. This is purely the addition of correct output encoding on the serialization path.

Key insight
The single mistake was serializing a custom property name into a var() reference as raw text instead of an escaped CSS identifier; the fix routes that name through SerializeIdentifier, so characters requiring escaping are encoded and the serialized value re-parses to the same property.

Attack Path

  1. Author a custom property with an escape-requiring name Create an element whose style defines a custom property whose name contains a character needing escaping, e.g. --a\,fail: pass, and another property --unparsed: var(--a\,fail) that references it.
  2. Obtain the unparsed value via Typed OM Read the value through attributeStyleMap.get("--unparsed"), producing a CSSUnparsedValue whose segment is a var() reference to --a\,fail.
  3. Serialize without escaping Convert the value to a string (e.g. specified.toString()); on the pre-patch path the referenced name is appended raw, so the output becomes var(--a,fail) rather than the escaped var(--a\,fail).
  4. Re-parse yields a different property Feed the serialized string back with setProperty; the un-escaped name now parses as a different/incorrect variable reference, so the computed value no longer resolves to the original pass, breaking the round-trip and corrupting the intended CSS output. Any further security consequence beyond incorrect/mis-encoded CSS output is (inferred) and not shown by the diff.

Impact Assessment

An attacker who can influence a page’s custom property names and cause their Typed OM serialization gains improper encoding of CSS output: a var() reference is emitted without escaping its identifier, so the serialized-then-reparsed value denotes a different property than intended. This runs in the renderer process during CSS serialization; the precondition is a custom property name that contains characters requiring escaping being carried through a var() reference in a CSSUnparsedValue. The advisory classifies this as a LogicError / improper encoding in CSS; exploited-in-the-wild status and any escalation beyond the incorrect output the patch corrects are not stated and are (inferred).

Files Changed

  • third_party/blink/web_tests/external/wpt/css/css-typed-om/stylevalue-serialization/cssUnparsedValue.html
  • third_party/blink/renderer/core/css/cssom/css_unparsed_value.cc

Audit Directions

  • Raw identifier append during serialization
    Search other Typed OM / CSSOM serializers for builder.Append(...) of a property, variable, or identifier name that bypasses SerializeIdentifier/SerializeString, since any such raw append can emit un-escaped output.
  • var() and custom-property emission paths
    Review every code path that reconstructs var( references or custom property names (fallbacks, declarations, computed-style output) to confirm each name is escaped and the output round-trips.
  • Round-trip test coverage
    Look for serialization code that lacks a serialize-then-reparse test with escape-requiring names (commas, whitespace, control characters), as these gaps hide encoding bugs like this one.
From 9ef64552f77b59e11090cdb22ce4c72592c52ddd Mon Sep 17 00:00:00 2001
From: Rune Lillesveen <[email protected]>
Date: Mon, 03 Aug 2026 03:36:13 -0700
Subject: [PATCH] Escape custom property names for unparsed serialization

Bug: 518082852
Change-Id: Ie31aebb22cd83730d44869f475ff3c1ec37b4078
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8180666
Reviewed-by: Javier Fernandez <[email protected]>
Commit-Queue: Rune Lillesveen <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1672568}
---
diff --git a/third_party/blink/renderer/core/css/cssom/css_unparsed_value.cc b/third_party/blink/renderer/core/css/cssom/css_unparsed_value.cc
index 42b91b5e..d4e0189 100644
--- a/third_party/blink/renderer/core/css/cssom/css_unparsed_value.cc
+++ b/third_party/blink/renderer/core/css/cssom/css_unparsed_value.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/core/css/cssom/css_unparsed_value.h"
 
 #include "css_style_value.h"
+#include "third_party/blink/renderer/core/css/css_markup.h"
 #include "third_party/blink/renderer/core/css/css_unparsed_declaration_value.h"
 #include "third_party/blink/renderer/core/css/css_variable_data.h"
 #include "third_party/blink/renderer/core/css/cssom/css_style_variable_reference_value.h"
@@ -244,7 +245,7 @@
         const auto* reference_value =
             segments_[i]->GetAsCSSVariableReferenceValue();
         builder.Append("var(");
-        builder.Append(reference_value->variable());
+        SerializeIdentifier(reference_value->variable(), builder);
         if (reference_value->fallback()) {
           builder.Append(",");
           if (!reference_value->fallback()->AppendUnparsedString(
diff --git a/third_party/blink/web_tests/external/wpt/css/css-typed-om/stylevalue-serialization/cssUnparsedValue.html b/third_party/blink/web_tests/external/wpt/css/css-typed-om/stylevalue-serialization/cssUnparsedValue.html
index 6e9b1a0..691dfb0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-typed-om/stylevalue-serialization/cssUnparsedValue.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-typed-om/stylevalue-serialization/cssUnparsedValue.html
@@ -8,6 +8,7 @@
 <body>
 <div id="tag"></div>
 <div id="elem" style="color-scheme: bar/*comment*/var(--foo)"></div>
+<div id="customEscaped" style="--a\,fail: pass; --unparsed:var(--a\,fail)"></div>
 <script>
 'use strict';
 
@@ -48,4 +49,11 @@
       'bar/**/var(--foo)');
 }, 'attributeStyleMap round-trips correctly, though the comment is gone');
 
+test(() => {
+  let specified = customEscaped.attributeStyleMap.get("--unparsed");
+  assert_equals(getComputedStyle(customEscaped).getPropertyValue("--unparsed"), "pass");
+  customEscaped.style.setProperty("--unparsed", specified.toString());
+  assert_equals(getComputedStyle(customEscaped).getPropertyValue("--unparsed"), "pass");
+}, 'String serialization of CSSUnparsedValue should roundtrip');
+
 </script>
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/web_tests/external/wpt/css/css-typed-om/stylevalue-serialization/cssUnparsedValue.html b/third_party/blink/web_tests/external/wpt/css/css-typed-om/stylevalue-serialization/cssUnparsedValue.html
index 6e9b1a0..691dfb0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-typed-om/stylevalue-serialization/cssUnparsedValue.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-typed-om/stylevalue-serialization/cssUnparsedValue.html
@@ -8,6 +8,7 @@
 <body>
 <div id="tag"></div>
 <div id="elem" style="color-scheme: bar/*comment*/var(--foo)"></div>
+<div id="customEscaped" style="--a\,fail: pass; --unparsed:var(--a\,fail)"></div>
 <script>
 'use strict';
 
@@ -48,4 +49,11 @@
       'bar/**/var(--foo)');
 }, 'attributeStyleMap round-trips correctly, though the comment is gone');
 
+test(() => {
+  let specified = customEscaped.attributeStyleMap.get("--unparsed");
+  assert_equals(getComputedStyle(customEscaped).getPropertyValue("--unparsed"), "pass");
+  customEscaped.style.setProperty("--unparsed", specified.toString());
+  assert_equals(getComputedStyle(customEscaped).getPropertyValue("--unparsed"), "pass");
+}, 'String serialization of CSSUnparsedValue should roundtrip');
+
 </script>
Loading diff…

Original Bug Report

reported by [email protected]

MXSS in CSSUnparsedValue serialization due to missing custom-property name escaping

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential Mutation XSS (mXSS) vulnerability exists in the CSS Typed OM serialization of custom-property variable names. When CSSUnparsedValue.toString() serializes a CSSVariableReferenceValue segment, it emits the decoded identifier name verbatim without performing proper escaping (such as the CSSOM ‘serialize an identifier’ algorithm). This allows unescaped HTML metacharacters to survive in the serialized output, potentially leading to script execution if re-injected into style blocks.

Affected files:

  • third_party/blink/renderer/core/css/cssom/css_unparsed_value.cc
  • third_party/blink/renderer/core/css/cssom/css_style_variable_reference_value.cc

Estimated timestamp from git blame: 2017-12-06

Root Cause

In third_party/blink/renderer/core/css/cssom/css_unparsed_value.cc, the AppendUnparsedString method serializes CSSVariableReferenceValue segments by appending the custom-property variable name raw to the string builder:

case V8CSSUnparsedSegment::ContentType::kCSSVariableReferenceValue: {
  const auto* reference_value =
      segments_[i]->GetAsCSSVariableReferenceValue();
  builder.Append("var(");
  builder.Append(reference_value->variable()); // Missing SerializeIdentifier()
  ...

During parsing of CSS, any CSS escapes inside custom-property identifiers (e.g., \3c for <) are decoded into their literal representation. Thus, the variable name stored in CSSStyleVariableReferenceValue::variable_ can contain literal HTML metacharacters such as < or >. Additionally, the JS constructor for CSSVariableReferenceValue only performs a basic starts_with("--") check on the variable name, allowing developers/attackers to directly supply names with arbitrary characters.

When CSSUnparsedValue.toString() is called, it serializes this raw variable name without utilizing the standard CSSOM SerializeIdentifier algorithm, allowing these metacharacters to be output in raw form.

Potential Trigger Path

Note: These are suggested/potential steps; our tooling agent does not yet have the ability to dynamically run or execute code.

  1. An attacker is able to inject a CSS style containing escaped sequences inside a custom property name, or construct a CSSVariableReferenceValue directly via the JavaScript API: new CSSVariableReferenceValue("--a,</style><script>alert(1)</script>")
  2. A web application or sanitizer reads the custom property using CSS Typed OM: const val = element.attributeStyleMap.get('--x');
  3. The application serializes the value back to a string: const serialized = val.toString(); This outputs: "var(--a,</style><script>alert/**/(1)</script>)"
  4. If the application or sanitizer injects this serialized string back into a <style> block in the DOM, the HTML parser parses </style>, transitions out of the RAWTEXT style state, parses the <script> element, and executes the payload, leading to potential Mutation XSS (mXSS).

Suggested Fix

  1. In third_party/blink/renderer/core/css/cssom/css_unparsed_value.cc, replace: builder.Append(reference_value->variable()); with: SerializeIdentifier(reference_value->variable(), builder);
  2. In third_party/blink/renderer/core/css/cssom/css_style_variable_reference_value.cc, tighten the validation in CSSStyleVariableReferenceValue::Create and setVariable to verify that the variable name conforms to a valid <dashed-ident> token.

Evaluated with Chrome root at commit: fb72408a8493c46bc75fae1c70d03daec96b3040


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker