ApostropheCMS versions prior to 3.7.1 and 4.34.0 have a missing authentication vulnerability in the import-export module. The API endpoint POST /api/v1/@apostrophecms/import-export/clean-export lacks authentication and authorization checks, allowing any client, including unauthenticated users with a standard CSRF cookie, to invoke a critical function. This issue is fixed in versions 3.7.1 and 4.34.0.
## 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
A missing authorization vulnerability in apostrophecms apostrophe allows editor-level users to bypass type-level and field-level access controls on the `getOne` REST endpoint for pages. While the `getAll` endpoint correctly enforces `viewRole` and `viewPermission` restrictions, the `getOne` endpoint does not, enabling editors who know a document ID to access admin-only pages and privileged fields.
The npm package unicode-title-normalizer@1.0.0 contained a malicious install script designed to exfiltrate GitHub Actions CI tokens to a third-party webhook URL. The package's code itself was benign, but the install lifecycle script attempted to send the GITHUB_TOKEN environment variable to an attacker-controlled endpoint. The package was removed by npm shortly after discovery. This package appears to be a typosquatting or dependency confusion attempt targeting CI environments.
The npm package 'formatting-subtitles-for-claude' contains malicious code executed during installation via a postinstall hook that runs arbitrary shell commands from a separate dependency. The package masquerades as a clone of 'unicode-title-normalizer' but executes untrusted code at install time without integrity checks. Systems with this package installed should be considered fully compromised, and all secrets should be rotated. Removal of the package does not guarantee full remediation due to potential persistent compromise.
## Review scope and evidence limits
Package: @apostrophecms/import-export, version 3.7.0.
Release tag reviewed: @apostrophecms/import-export@3.7.0, commit 9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce (September 30, 2026).
Current main reviewed: 5ce3732be0ebdebb4db0f8ef160c81e8671157e6.
The importer, gzip extractor, import permission gate, and core permission implementation have no differences between that release tag and the reviewed main. This follow-up is a static remediation review. We have not repeated the original end-to-end reproductions against the latest release or tested a deployed site. Current exploitability and impact therefore still require maintainer validation; the observations below establish that the reported security checks remain absent in public source. The original emailed PDF contains the original reproduction material. Please refer to that existing private attachment.
## Archive duplicate continuation permits unauthorized published-content replacement
The overrideDuplicates() continuation still accepts client-supplied replacement choices, rather than binding them to a server-maintained, user-owned duplicate analysis. The replacement and write helpers do not establish a single canonical resource identity and authorization context before the update. This leaves the reported gap between draft editing authority and the published target selected for replacement unresolved at source level.
The original report describes a contributor crossing the publication boundary through duplicate replacement on an import-enabled localized type. We have not repeated that end-to-end sequence on 3.7.0.
Suggested remediation: bind continuation state to the initiating user, archive, job, permitted source/target mapping, locale and mode; reject inconsistent identity metadata; derive permissions from the actual loaded target and require publication authority for published targets.
Source: overrideDuplicates(), replaceDocId(), and insertOrUpdateDoc() in the [importer](https://github.com/apostrophecms/apostrophe/blob/5ce3732be0ebdebb4db0f8ef160c81e8671157e6/packages/import-export/lib/methods/import.js).
## Coordination
Vivek R / Winfunc
Original reporting team: Mufeed VH / Winfunc
ApostropheCMS versions prior to 3.7.1 of the import-export module and versions before 4.34.0 are affected by an uncontrolled resource consumption vulnerability. An authenticated user importing archives can submit data with unbounded decompressed size, entry count, and entry sizes before authorization checks, potentially causing denial of service. Extraction or parsing failures may leave residual staging and extracted artifacts without guaranteed cleanup. This issue is fixed in version 3.7.1.
ApostropheCMS versions prior to 4.34.0 have a vulnerability where a public REST API using publicApiProjection can allow inference of excluded searchable or sortable field information. Although the returned fields are filtered by the projection, this flaw permits targeted inference about hidden values rather than a full data leak. The issue is resolved in version 4.34.0.
A vulnerability in apostrophecms apostrophe before version 4.34.0 allows any authenticated user to enumerate all slugs in the CMS, including private, draft, and archived content, via the slugTaken API endpoint. This endpoint does not enforce permission checks or filter by document visibility or publication status, leading to unauthorized exposure of sensitive information.
### Summary
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.
### Details
Server-side validation of the color field relies on TinyColor:
```js
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).
### Relation to GHSA-97v6-998m-fp4g
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.
### Impact
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.
### Suggested fix
Reject values unless the entire string is a valid color, e.g. normalize on save:
```js
const c = new TinyColor(value);
if (!c.isValid) throw self.apos.error('invalid');
value = c.toHex8String(); // store only normalized output
```
CVE-2026-112947 is a path traversal vulnerability in the Vite development server that allows an attacker to retrieve the first 4 bytes of arbitrary files on disk by sending a request with the query parameter '?vite-wasm-instance'. This occurs because the internal Wasm plugin's load hook reads files without applying server.fs access restrictions. The vulnerability affects Vite versions >=0.2.8 <1.1.0, >=8.2.0 <8.2.4, and >=8.3.0 <8.3.3. It only impacts applications that expose the Vite dev server to the network, run on Node.js or Deno, and have sensitive information in the first 4 bytes of a file. The issue is fixed in versions 1.1.0, 8.2.4, and 8.3.3.
### Summary
The content of files outside `server.fs` setting can be returned to the browser if the project imports a file path concatenated with the `root`. For example, if the project references `<path-to-project-root>/etc/hosts`, the content of `/etc/hosts` was possible to be returned.
The content of files outside the `server.fs.allow` option can be returned to the browser when a project imports a file whose root-relative path matches an absolute path on the host. For example, referencing `<project-root>/etc/hosts` could make Vite return the host's `/etc/hosts` file.
### Impact
Only apps that meet all of the following conditions are affected:
- explicitly expose the Vite dev server to the network (using `--host` or the [`server.host` option](https://vite.dev/config/server-options#server-host))
- contain an imported file whose root-relative browser URL is also the absolute path of a readable file on the host
- The sensitive file is not matched by [`server.fs.deny`](https://vite.dev/config/server-options#server-fs-deny)
- the dev server is not running on Windows
### Details
When Vite analyzes an in-root import, it rewrites the resolved module ID to a browser URL such as `/etc/hostname`. In affected versions, Vite converts that URL with `fsPathFromUrl` and adds the result to `safeModulePaths`. Because `fsPathFromUrl` ensures a leading slash without resolving the URL against the project root, the project URL `/etc/hostname` is recorded as if it were the absolute filesystem path `/etc/hostname`.
For a later request to `/@fs/etc/hostname`, the raw-filesystem middleware interprets `/etc/hostname` as an absolute path. The access check applies `server.fs.deny`, then accepts paths present in `safeModulePaths` before checking `server.fs.allow`. As a result, the request can return the host file instead of the project file that originally populated `safeModulePaths`.
### PoC
The following example requires Linux with a readable `/etc/hostname`:
```bash
$ mkdir vite-safe-module-path-poc
$ cd vite-safe-module-path-poc
$ npm init -y
$ npm install vite@8.3.1
$ mkdir -p etc
$ echo 'PROJECT-DECOY\n' > etc/hostname
$ echo 'import "./etc/hostname"\n' > main.js
$ npx vite --host 127.0.0.1
```
In another terminal:
```bash
$ curl -o /dev/null -s -w '%{http_code}\n' http://127.0.0.1:5173/main.js
200
$ curl -sS -o response -w '%{http_code}\n' http://127.0.0.1:5173/@fs/etc/hostname
200
$ cmp -s response /etc/hostname && echo 'response matches host file'
response matches host file
```
CVE-2026-112945 is a cross-site scripting (XSS) vulnerability in the Vite frontend tooling framework affecting versions prior to 6.4.4, 7.3.7, 8.1.6, 8.2.4, and 8.3.3. The flaw arises when applications using custom middleware pass an unchanged browser request URL to server.transformIndexHtml, allowing attacker-controlled module execution via crafted development-server pages. This can expose sensitive data or permit critical actions in the affected application. The issue has been fixed in the specified versions.
### Summary
`launch-editor` forwarded file paths directly to editors without sanitization. If a path resembles a CLI flag, editors like Vim may interpret it as a command, potentially causing unintended file overwrites or command execution.
### Impact
#### Direct `launch-editor` users
Only apps that match all of the following conditions are affected:
- The attacker is able to create a file system entry (e.g. a file or directory) starting with `+` or `-` in the working directory
- The user uses or the app specifies any of the following editors:
- Vim (including gVim)
- MacVim
- Emacs (including Emacsclient)
- Notepad++ (only if using a plugin exposing dangerous operations via plugin messages)
- The first argument of `launchEditor` can start with `+` or `-` or the user is using WSL
#### `launch-editor-middleware` users
Only apps that match the following conditions are affected:
- The attacker is able to create a file system entry (e.g. a file or directory) starting with `+` or `-` in the working directory
- The user uses or the app specifies any of the following editors:
- Vim (including gVim)
- MacVim
- Emacs (including Emacsclient)
- Notepad++ (only if using a plugin exposing dangerous operations via plugin messages)
- launch-editor older than v2.2.0 is used, or the user is using WSL
#### Vite users
Only apps that match the following conditions are affected:
- The attacker is able to create a file system entry (e.g. a file or directory) starting with `+` or `-` in the working directory
- The user uses or the app specifies any of the following editors:
- Vim (including gVim)
- MacVim
- Emacs (including Emacsclient)
- Notepad++ (only if using a plugin exposing dangerous operations via plugin messages)
- The user is using WSL
### Details
`launchEditor(file)` function passes the file name as-is to the editor if the file with that file name exists. If the file name starts with `-` (also `+` for Vim), the editor treats them as flags rather than positional arguments. For example, Vim treats arguments starting with `+` as an Ex command. As a result, an existing relative directory such as:
```
+call writefile(["SAFE_MARKER"],"LAUNCH_EDITOR_MARKER")|qa!
```
is not treated as a filename when passed to Vim. Vim executes the embedded Ex command.
The following editor has a potentially destructive operation exposed to the CLI arguments:
- Vim (including gVim)
- MacVim
- Emacs (including Emacsclient)
- Notepad++ (only if using a plugin exposing dangerous operations via plugin messages)
`launch-editor-middleware` v2.2.0+ converts the passed file path to an absolute path so those are not affected as long as the user is not using WSL. For WSL, `launch-editor` converts the paths to relative paths internally, so those are still affected.
Vite 2.0.0+ uses `launch-editor-middleware` v2.2.0+ so the WSL users are only affected.
CVE-2026-112942 is a command injection vulnerability in the vitejs launch-editor package prior to version 2.14.2. The flaw arises from improper handling of Windows file arguments containing parent-directory components and line-and-column suffixes, allowing an attacker to bypass filename existence checks. This can lead to OS command injection when combined with a related Windows percent-expansion issue. The vulnerability is fixed in version 2.14.2 and later.
A denial of service (DoS) vulnerability exists in @angular/router when Server-Side Rendering (SSR) is enabled on Node.js. The router fails to validate auxiliary outlet segments in URLs, allowing crafted requests with multiple empty-path outlet segments to cause excessive memory and CPU usage. This leads to Node.js process termination due to heap exhaustion. The vulnerability affects SSR configurations with empty-path or named outlet routes and can be mitigated by upgrading to patched versions or rejecting malicious URL patterns upstream.
An open redirect vulnerability exists in @angular/platform-server when Server-Side Rendering (SSR) is enabled. The vulnerability arises from improper URL normalization allowing crafted URLs with dot-segments and multiple slashes to bypass checks and cause the server to emit a protocol-relative redirect in the HTTP Location header. This can redirect users to arbitrary external domains. Pure client-side Angular applications without SSR are not affected. The issue is fixed in versions 22.2.1, 21.2.25, and 20.3.33.
A denial of service vulnerability exists in markdown-it when the typographer option is enabled. Specially crafted Markdown input with many opening double quotes followed by unmatched closing single quotes causes quadratic processing time in the smartquotes rule. This can lead to significant delays or service disruption when processing untrusted input. The default configuration without typographer enabled is not affected. The issue is fixed in versions 15.0.2 and 14.3.2. Workarounds include disabling the typographer option or disabling the smartquotes rule.
The npm package '@platform-services/hrm-mfe-core' version 99.9.1 is identified as malicious. It declares a dependency on itself using a URL pointing to an external S3 bucket (puffnetwork.com) rather than a standard registry version. This setup allows arbitrary code from the external source to be executed during installation without version pinning or integrity verification. The package also communicates with a domain linked to malicious activity.
The npm package 'sc-announcer' version 99.9.1 is identified as malicious. It acts as a stub that depends on an off-registry tarball 'ltidisafe' fetched from an untrusted HTTPS URL without version pinning or integrity verification. Installing this package executes lifecycle scripts from the fetched tarball, potentially compromising the host system. The package communicates with domains linked to malicious activity. Systems with this package installed should be considered fully compromised, and all secrets and keys on those systems should be rotated immediately from a separate, secure machine.