CVE-2026-112951Medium· 5.4▾ TwilightPoC availableApostropheCMS: Color field validation bypass allows stored XSS via HTML appended after rgb()/hsl() prefix
▾ Twilight zone — High severity, or a signal on a lesser flaw
impact 29.7 · likelihood 0.1 · exploitation 12
A public proof-of-concept already exists for this vulnerability — see Exploit availability below.
Stakeholder-Specific Vulnerability Categorization from CISA's ADP record at CVE.org: whether exploitation is observed, whether an attack can be automated, and how much of the system is at stake.
Exploit-prediction probability, daily snapshots since Oct 21.
Disclosure to exploitation, from the record and what we observed since indexing it.
Exploit / PoC code exists
Disclosed via GHSA
0.3%
0.3% → 0.3%
The color field accepts any string that begins with a valid rgb() / hsl() color, because TinyColor.isValid does not anchor its match at the end of the string. An authenticated user with edit rights can store arbitrary HTML after a valid color prefix. When the stored value is rendered , the payload executes, resulting in stored XSS.
Server-side validation of the color field relies on TinyColor:
const { TinyColor } = require('@ctrl/tinycolor');
if (!new TinyColor(value).isValid) throw self.apos.error('invalid');
TinyColor's rgb/hsl matchers are not anchored at the end of the string, while hex matchers are. Tested with @ctrl/tinycolor
| Input | isValid |
|---|---|
rgb(1,2,3)<img src=x onerror=alert(1)> | true |
rgb(1,2,3)</textarea><script>alert(1)</script> | true |
hsl(0,100%,50%);--x:1}</style><script>alert(1)</script> | true |
#ff0000<img src=x onerror=alert(1)> | false |
The value is saved to MongoDB unchanged (not normalized to hex8).
GHSA-97v6 addressed CSS custom property injection escaping a <style> context. The root cause (validation that accepts trailing content after a valid color) still exists in <= 4.32.2, so [FILL IN: either "the style-tag sink remains exploitable via </style>" OR "a separate HTML sink at X is exploitable"]. Fixing the validation (requiring the full string to be a color, or storing only TinyColor's normalized output) would close both.
Any user with edit permission can plant persistent JavaScript that runs in the browser of visitors and administrators viewing the affected page, enabling session hijacking and actions as an admin.
Reject values unless the entire string is a valid color, e.g. normalize on save:
const c = new TinyColor(value);
if (!c.isValid) throw self.apos.error('invalid');
value = c.toHex8String(); // store only normalized output
apostrophe < 4.34.0Upgrade to a patched release:
apostrophe 4.34.0Field changes observed since this record was first indexed.
Connected by shared product, vendor, weakness, or advisory.
CVE-2026-112958Medium· 5.4ApostropheCMS: Stored XSS via Unescaped Widget Style Attribute Serialization
CVE-2026-112954Medium· 5.3ApostropheCMS: Unauthenticated publicApiProjection bypass via search and sort query builders in the public REST API
CVE-2026-112957Medium· 4.3ApostropheCMS: GET /api/v1/@apostrophecms/page/:id bypasses viewRole restriction and exposes viewPermission-gated fields to lower-privileged users
CVE-2026-113649Medium· 6.5ApostropheCMS: Import API bypasses publication roles for protected content
CVE-2026-112952Medium· 4.3ApostropheCMS: POST /api/v1/@apostrophecms/doc/slugTaken discloses existence of private/admin-only content to any authenticated user
CVE-2026-113650Medium· 5.3ApostropheCMS sanitize-html: unbounded recursion when re-sanitizing the fallback of a discarded iframe (stack overflow, nestingLimit not enforced)