GHSA-rw26-wwfc-9wvm: CSRF
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); } }
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/@apostrophecms/import-exportto a version that resolves this vulnerability.Fixed in 3.7.1 - Compensating control
In packages/import-export/lib/apiRoutes.js, add an authentication guard before executing post.cleanExport: if (!req.user) { throw self.apos.error('forbidden'); } so unauthenticated requests cannot invoke self.cleanExport(req).
Event History
Frequently Asked Questions
Who can invoke the affected route?
Unauthenticated anonymous clients can invoke it after obtaining the standard CSRF cookie by visiting the site. The route does not verify that req.user is present before calling cleanExport.
What does an attacker need to exploit this?
The attacker needs network access to the POST endpoint and a standard CSRF cookie. No authenticated account is required.
How can I determine whether my deployment has this issue?
Inspect the clean-export POST route in packages/import-export/lib/apiRoutes.js. Affected code calls self.cleanExport(req) without checking req.user or otherwise enforcing authentication and authorization.