---
id: "CVE-2026-113648"
kind: "vulnerability"
aliases: ["CVE-2026-113648", "GHSA-rw26-wwfc-9wvm"]
title: "ApostropheCMS: Missing Authentication in `cleanExport` Route"
severity: "low"
cvss: 3.1
epss: 0.003280000062659383
exploited: false
status: "open"
category: "package"
products: [{"name": "@apostrophecms/import-export", "fixed": ["3.7.1"], "affected": "< 3.7.1", "ecosystem": "npm"}]
ecosystems: ["npm"]
tags: ["apostrophecms", "cwe-306", "cwe-862", "node", "package", "self-hosted"]
not_affected_if: ["import-export-not-installed", "managed-hosting", "version-3.7.1-or-later"]
published_at: "2026-10-20T17:17:35+00:00"
first_seen_at: "2026-10-23T19:11:32+00:00"
updated_at: "2026-10-23T21:57:20+00:00"
sources: [{"source": "ghsa", "url": "https://github.com/advisories/GHSA-rw26-wwfc-9wvm", "first_seen_at": "2026-10-23T19:11:32+00:00"}, {"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-113648", "first_seen_at": "2026-10-23T21:57:20+00:00"}]
---

# ApostropheCMS: Missing Authentication in `cleanExport` Route

The ApostropheCMS import-export module exposes POST /api/v1/@apostrophecms/import-export/clean-export without an authentication check, so anonymous visitors holding the site's CSRF cookie can trigger cleanup. An attacker can delete temporary export files on the server and mark arbitrary import/export job records as completed or dismiss notifications.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- npm:@apostrophecms/import-export affected < 3.7.1 fixed 3.7.1

## How to check

Check whether @apostrophecms/import-export is below 3.7.1 in package-lock.json or the installed node_modules.
Send an unauthenticated POST to /api/v1/@apostrophecms/import-export/clean-export with only the CSRF cookie and see whether it returns success instead of forbidden.

## Fix

Upgrade @apostrophecms/import-export to 3.7.1 or later.
Until then, add an if (!req.user) guard to the cleanExport and overrideDuplicates handlers in lib/apiRoutes.js as in the advisory patch.

## Timeline

- 2026-10-23T19:11:32+00:00 ghsa https://github.com/advisories/GHSA-rw26-wwfc-9wvm
- 2026-10-23T21:57:20+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-113648

## From the sources

#### 1. Summary
The API endpoint `POST /api/v1/@apostrophecms/import-export/clean-export` in `@apostrophecms/import-export` lacks authentication and authorization checks. Any client—including unauthenticated anonymous users who have obtained the standard CSRF cookie from visiting the site—can directly invoke this route and trigger the underlying `self.cleanExport(req)` method.

---

#### 2. Affected Files & Endpoints
* **Endpoint:** `POST /api/v1/@apostrophecms/import-export/clean-export`
* **Route Definition:** `packages/import-export/lib/apiRoutes.js` (lines 32–34)
* **Method Implementation:** `packages/import-export/lib/methods/import.js` (lines 1271–1324)

---

#### 3. Root Cause Analysis
In `packages/import-export/lib/apiRoutes.js`, the `get.related` handler explicitly validates that the caller is logged in:
```javascript
get: {
  async related(req) {
    if (!req.user) {
      throw self.apos.error('forbidden');
    }
    // ...
  }
}
```
In contrast, `post.cleanExport` (as well as `post.overrideDuplicates`) lacks any verification of `req.user`:
```javascript
post: {
  async overrideDuplicates(req) {
    return self.overrideDuplicates(req);
  },

  async cleanExport(req) {
    return self.cleanExport(req); // <-- No authentication check
  }
}
```
Because ApostropheCMS does not enforce authentication globally on all `apiRoutes`, omitting an explicit `if (!req.user)` guard allows anonymous requests to pass directly through to the business logic layer.

---

#### 4. Evidence
When sending an unauthenticated POST request with the site’s CSRF cookie (issued to any visitor on `GET /`), the request bypasses access control entirely and executes `self.cleanExport(req)`, as demonstrated by the server stack trace:

```text
at Object.cleanExport (.../packages/import-export/lib/methods/import.js)
at .../packages/apostrophe/modules/@apostrophecms/module/index.js:193:30
```
This confirms that execution directly reaches the core import-export handler without an authenticated session.

---

#### 5. Impact
1. **Broken Access Control:** Unauthenticated users can invoke cleanup operations and delete temporary export files and artifacts stored on the server.
2. **Unauthorized State Mutation:** The endpoint allows modifying job records in the database via `req.body.jobId` (forcing them to `ended: true, status: 'completed'`) and dismissing notifications via `req.body.notificationId` without verifying whether the resources belong to the requesting caller.

---

#### 6. Suggested Remediation (Patch)

Enforce an authentication check in `packages/import-export/lib/apiRoutes.js` before executing the underlying methods:

```diff
--- a/packages/import-export/lib/apiRoutes.js
+++ b/packages/import-export/lib/apiRoutes.js
@@ -26,10 +26,16 @@ module.exports = self => {
     get: { ... },
     post: {
       async overrideDuplicates(req) {
+        if (!req.user) {
+          throw self.apos.error('forbidden');
+        }
         return self.overrideDuplicates(req);
       },
 
       async cleanExport(req) {
+        if (!req.user) {
+          throw self.apos.error('forbidden');
+        }
         return self.cleanExport(req);
       }
     }
```

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
