CVE-2026-112958: apostrophecms apostrophe · Cross-site Scripting
Affects apostrophecms apostropheSource CVE Database V5
Mentioned vulnerabilities
Not known to be exploited · CVSS 5.4 Medium : upgrade to 4.34.0 when convenient.
Data as of 26 Oct 2026, 17:16 UTC
- EPSS
- 0.3% chance of exploitation in 30 days – higher than 22% of CVEsExploit Prediction Scoring System: the likelihood attackers will use this flaw soon.
- Patch
- Available – fixed in 4.34.0
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:NExploitation evidence
Disclosed: evidence found.
CVE published 20 Oct 2026
Detectable: no evidence on record.
No public nuclei template indexed
Weaponised: no evidence on record.
No public exploit code indexed in Metasploit or Exploit-DB
Exploited in the wildExploited: no evidence on record.
Not in CISA KEV and no source reports exploitation
Observed by OffSeq MirageSeen by Mirage: no evidence on record.
No attack traffic recorded by OffSeq Mirage sensors
A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), the Metasploit, Exploit-DB and nuclei-templates index as of 26 Oct 2026.
What to do
Upgrade apostrophe and apostrophecms/apostrophe to 4.34.0.
Affected software
| Product | Affected | Fixed in |
|---|---|---|
| apostrophecmsapostrophe | Affected
| Fixed in
|
npmapostrophecms/apostrophepkg:npm/apostrophecms/apostropheOther threats | Affected
| Fixed in
|
Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.
AI-suggested
Upgrade ApostropheCMS to version 4.34.0 or later, where this vulnerability has been fixed. No other specific mitigations are indicated by the vendor advisory.
Description
## Summary ApostropheCMS widget styles support project-defined schema fields. If a project defines a free-form `string` style field with a CSS `property` and no selector, an editor-controlled value is rendered as an inline CSS declaration. The resulting declaration is then inserted into the widget wrapper's `style="..."` attribute without HTML-attribute escaping. An attacker who can edit the widget can include a double quote in the style value, terminate the `style` attribute, and inject new HTML event-handler attributes. The payload is stored with the widget and executes in the Apostrophe site origin whenever the affected published page is rendered. This is a sibling of CVE-2026-33889 / `GHSA-97v6-998m-fp4g`. The earlier issue escaped from a `<style>` element through a malicious color value. This variant reaches a different sink related to the automatically generated widget wrapper's inline `style` and `class` attributes. ## Details ### Root Cause 1: Free-form string style values retain HTML metacharacters The standard string field converter uses `launder.string()` and length validation. This performs type normalization, not output encoding for an HTML attribute context: [`packages/apostrophe/modules/@apostrophecms/schema/lib/addFieldTypes.js:296-333`](https://github.com/apostrophecms/apostrophe/blob/aa5170c7df9ff9ea5ac2e72a008f12e28dae6ca8/packages/apostrophe/modules/%40apostrophecms/schema/lib/addFieldTypes.js#L296-L333) ```javascript self.addFieldType({ name: 'string', // ... convert(req, field, data, destination) { destination[field.name] = self.apos.launder.string(data[field.name]); destination[field.name] = checkStringLength( destination[field.name], field.min, field.max ); } }); ``` Quotes therefore remain in a value supplied through a style field. ### Root Cause 2: The value is converted into inline CSS without value validation For a field with `property` but no `selector` or media query, `renderScopedStyles()` selects inline rendering. The generic extractor concatenates the configured property and editor-controlled value: [`packages/apostrophe/modules/@apostrophecms/styles/ui/apos/universal/render.mjs:171-228`](https://github.com/apostrophecms/apostrophe/blob/aa5170c7df9ff9ea5ac2e72a008f12e28dae6ca8/packages/apostrophe/modules/%40apostrophecms/styles/ui/apos/universal/render.mjs#L171-L228) [`packages/apostrophe/modules/@apostrophecms/styles/ui/apos/universal/render.mjs:598-647`](https://github.com/apostrophecms/apostrophe/blob/aa5170c7df9ff9ea5ac2e72a008f12e28dae6ca8/packages/apostrophe/modules/%40apostrophecms/styles/ui/apos/universal/render.mjs#L598-L647) ```javascript rule = `${property}: ${normalized.value}${normalized.unit}`; // ... if (isInline) { return { css: '', classes: [ ...storage.classes ], inline: stringifyRules(storage.styles, true) }; } ``` For example, the value below becomes part of a `font-family` declaration: ```text serif" tabindex="0" autofocus onfocus="document.body.dataset.inline='executed' ``` ### Root Cause 3: Attribute values are serialized without HTML escaping `getWidgetAttributes()` places the inline CSS into an attribute tuple, then concatenates every tuple into `key="value"` without encoding `&`, `<`, `>`, `'`, or `"`: [`packages/apostrophe/modules/@apostrophecms/styles/lib/methods.js:251-322`](https://github.com/apostrophecms/apostrophe/blob/aa5170c7df9ff9ea5ac2e72a008f12e28dae6ca8/packages/apostrophe/modules/%40apostrophecms/styles/lib/methods.js#L251-L322) ```javascript if (styleParts.length) { attrs.push( [ 'style', `${styleParts.join(';')};` ] ); } if (options.asObject) { return Object.fromEntries(attrs); } return attrs .map(([ key, value ]) => `${key}="${value}"`) .join(' '); ``` The same raw serializer also makes a project-defined free-form `string` style with `class: true` a potential sibling path, because the string is added to `classes` and later serialized into `class="..."`. The inline-property path was the path dynamically verified. ### Root Cause 4: The unsafe string is used by the automatic public wrapper Styled widgets use `getWidgetAttributes()` to create a wrapper around the rendered widget: [`packages/apostrophe/modules/@apostrophecms/widget-type/index.js:448-457`](https://github.com/apostrophecms/apostrophe/blob/aa5170c7df9ff9ea5ac2e72a008f12e28dae6ca8/packages/apostrophe/modules/%40apostrophecms/widget-type/index.js#L448-L457) ```javascript if (hasStyles && self.options.stylesWrapper !== false) { const styles = self.apos.styles.prepareWidgetStyles(widget); const styleTag = self.apos.styles .getWidgetElements(styles, { scene: req.scene }); const wrapperAttrs = self.apos.styles.getWidgetAttributes(styles); return `${styleTag}<div${wrapperAttrs ? ' ' + wrapperAttrs : ''}>${markup}</div>`; } ``` Consequently, the injected attribute is present on normal public page renders, not only in the editor UI. ### Why the CVE-2026-33889 defense does
References
- github.com/apostrophecms/apostrophe/security/advisories/GHSA-7jg5-frgc-c67w (opens in a new tab)
- github.com/apostrophecms/apostrophe/commit/06e4b377f3c360f9e89c32dccd7e505ad5ddf498 (opens in a new tab)
- github.com/apostrophecms/apostrophe/commit/40b989b1a9dd23d30029b8ce7668634265cfe5ea (opens in a new tab)
- github.com/apostrophecms/apostrophe/releases/tag/apostrophe@4.34.0 (opens in a new tab)