CVE-2026-113664

CVE-2026-113664 is a medium-severity path traversal vulnerability in @apostrophecms/import-export (npm), affecting versions < 3.7.0. It is fixed in 3.7.0.

Does this CVE actually affect you?

Kodem shows which CVEs are reachable and running in your applications, so you fix what's exploitable, not just what's listed.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Runtime intelligence, not another scanner.

Summary

ApostropheCMS import-export tar guard misses './..' because it is a literal '../' substring test

The tar-extraction guard added to @apostrophecms/import-export for GHSA-mwxc-m426-3f78 is a literal substring test for ../, and ./.. does not contain that substring. An entry named ./.. therefore passes the guard, path.join normalizes it to the parent of the export directory, and the resulting fs.WriteStream opens a directory and emits EISDIR with no error listener attached, which terminates the Node process.

Confirmed on default-branch HEAD 1bd5d0a7a6fe880836fa75adad6964f1c577d640, package version 3.6.2.

Details

packages/import-export/lib/formats/gzip.js, in the tar-stream entry handler inside extract(filepath, exportPath):

const name = header.name.replace(/\\/g, '/');
if (name.includes('../')) {
  // skipped
}
...
stream.pipe(fs.WriteStream(path.join(exportPath, name)));

Two separate problems sit in those four lines. The guard tests the raw entry name for a substring instead of resolving the joined path and requiring containment, and the write stream has no error handler, so any stream error becomes an unhandled 'error' event.

Reaching it needs an account that can import, nothing more. packages/import-export/modules/@apostrophecms/import-export-page/index.js exposes post.importExportImport, which calls into lib/methods/import.js; that handler requires req.user and then calls format.input(exportPath), which for a .gz upload is gzip.js calling extract() on the uploaded archive.

PoC

This is not an end-to-end exploit. What follows is each of the three primitives the crash depends on, executed individually, plus a source-level trace of the route. The chain from post.importExportImport to extract() was read, not observed.

The guard misses the payload:

node -e "console.log('./..'.includes('../')); console.log('..'.includes('../')); console.log('a/./..'.includes('../'));"
false
false
false

path.join then resolves it out of the export directory:

node -e "const path=require('path'); const exportPath='/tmp/exportRoot'; console.log(path.join(exportPath,'./..'));"
/tmp

And a write stream opened on a directory, with no error listener, kills the process rather than failing the request:

node -e "const fs=require('fs'); const s=fs.createWriteStream('/tmp'); s.write('x'); s.end(); setTimeout(()=>console.log('still alive'),500);"
events.js:291
      throw er; // Unhandled 'error' event
      ^

Error: EISDIR: illegal operation on a directory, open '/tmp'
Emitted 'error' event on WriteStream instance at:
    at internal/fs/streams.js:361:12
    at FSReqCallback.oncomplete (fs.js:156:23) {
  errno: -21,
  code: 'EISDIR',
  syscall: 'open',
  path: '/tmp'
}

The events.js:291 frame shows that transcript came off the Node 14 line my audit box had. The unhandled-error-event behavior is not version specific, but the exact message on your supported Node versions is worth a re-run.

Source-side confirmation without running anything:

grep -n "includes('../')" packages/import-export/lib/formats/gzip.js
grep -n "WriteStream" packages/import-export/lib/formats/gzip.js

Why this is not GHSA-mwxc-m426-3f78

The name.includes('../') check is the mitigation shipped for GHSA-mwxc-m426-3f78 / CVE-2026-32731. It stops names carrying the literal ../ sequence. It does not canonicalize the joined path, so ./.. and a bare .. still get through, and it did not touch the missing error listener. This report is about what the substring test leaves behind.

AI assistance was used for the code audit and drafting. The exact guard and WriteStream lines in packages/import-export/lib/formats/gzip.js at the stated HEAD were manually verified, the import route from the page module through lib/methods/import.js into the entry handler were manually traced, and the three Node one-liners were manually ran and copied verbatim.

Impact

An authenticated user who can reach the import UI uploads one crafted archive and takes down the Node process handling it. Every worker that picks up such an archive dies the same way.

Input manipulates file paths to reach files outside the intended directory, such as configuration or credential files. Typical impact: unauthorized file read or write outside the intended directory.

Affected versions

@apostrophecms/import-export (< 3.7.0)

Security releases

@apostrophecms/import-export → 3.7.0 (npm)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

Already deployed Kodem?

See it in your environmentNew to Kodem? Get a demo →

Remediation advice

Resolve the target and require containment, and attach an error handler:

const base = path.resolve(exportPath);
const target = path.resolve(path.join(exportPath, name));

if (target !== base && !target.startsWith(base + path.sep)) {
  stream.on('end', next);
  stream.resume();
  return;
}

if (header.type === 'directory') {
  fsp.mkdir(target).then(next).catch(reject);
} else {
  const out = fs.WriteStream(target);
  out.on('error', reject);
  stream.pipe(out);
  stream.on('end', next);
}

The error handler earns its place independently. Any tar entry that lands on an existing directory name has the same effect whether or not traversal is involved.

Frequently Asked Questions

  1. What is CVE-2026-113664? CVE-2026-113664 is a medium-severity path traversal vulnerability in @apostrophecms/import-export (npm), affecting versions < 3.7.0. It is fixed in 3.7.0. Input manipulates file paths to reach files outside the intended directory, such as configuration or credential files.
  2. Which versions of @apostrophecms/import-export are affected by CVE-2026-113664? @apostrophecms/import-export (npm) versions < 3.7.0 is affected.
  3. Is there a fix for CVE-2026-113664? Yes. CVE-2026-113664 is fixed in 3.7.0. Upgrade to this version or later.
  4. Is CVE-2026-113664 exploitable, and should I be worried? Whether CVE-2026-113664 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  5. What actually determines whether CVE-2026-113664 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  6. How do I fix CVE-2026-113664? Upgrade @apostrophecms/import-export to 3.7.0 or later.

Other vulnerabilities in @apostrophecms/import-export

Stop the waste.
Protect your environment with Kodem.