A critical path traversal bug in @adonisjs/bodyparser — the default file-handling package shipped with AdonisJS — lets unauthenticated attackers write files to arbitrary locations on the server filesystem. Tracked as CVE-2026-21440 and rated CVSS v3.1 9.2, the flaw turns any unpatched upload endpoint into a reliable path to remote code execution. Patched releases (10.1.2 and 11.0.0-next.6) shipped on January 2, 2026, and every AdonisJS app that accepts uploads should treat upgrading as urgent.

What the vulnerability is

The issue is a classic CWE-22 (Improper Limitation of a Pathname to a Restricted Directory) condition living inside the MultipartFile.move() method. When a developer calls file.move(destination) and does not pass an explicit, sanitized name option, the library quietly falls back to this.clientName — the raw filename the HTTP client supplied in the multipart form data — and uses it as the destination filename without any validation.

Two defaults make this exploitable. First, the destination path is assembled with Node's path.join(), which happily collapses ../ traversal sequences embedded in the filename. Second, the overwrite option defaults to true, so the target file is replaced even if it already exists. An attacker therefore only needs to send an upload whose filename climbs out of the intended directory.

Root cause

The whole bug reduces to a single missing sanitization step. Because clientName is passed to path.join() untouched, a traversal-laden filename resolves cleanly to a location of the attacker's choosing. As the source describes it:


path.join('/var/www/uploads', '../../etc/cron.d/pwned')  →  /etc/cron.d/pwned

With overwrite: true as the default, that write is silent and clobbers any existing file at the destination.

Exploitation

The attack needs no authentication, no elevated privileges, and almost no skill — the three factors that push this to a 9.2. The preconditions are simply:

  • The app runs a vulnerable build of @adonisjs/bodyparser (≤ 10.1.1 on the stable channel, or 11.0.0-next.1 through 11.0.0-next.5 on the next channel).
  • An upload route calls file.move() without an explicit, sanitized name.
  • The web server process can write to the targeted directory.

A complete exploit is one curl invocation:


curl -X POST http://target:3333/upload -F "file=@payload.txt;filename=../../tmp/pwned.txt"

Once an attacker has an arbitrary file write, the post-exploitation menu is broad: drop a job into /etc/cron.d/ for scheduled command execution, overwrite an application config file to inject logic, plant a key into ~/.ssh/authorized_keys for persistence, or simply replace application source files outright. In containerized setups with careless volume mounts, the write can even reach the host.

Vulnerable vs. secure handler

The vulnerable pattern is the convenient-looking one — it lets the library pick the destination filename from attacker-controlled clientName:


// Vulnerable: destination filename comes from the client
await file.move(app.tmpPath())

The fix is to never let the client name the stored file. The patched advisory and AdonisJS guidance describe generating a server-side identifier with cuid(), validating the extension against an allowlist, and disabling overwrites. The following is an illustrative secure pattern built from those elements (not a verbatim copy of the official patch):


import { cuid } from '@adonisjs/core/helpers'

const file = request.file('file', {
  extnames: ['jpg', 'png', 'pdf'], // explicit allowlist
})

await file.move(app.tmpPath('uploads'), {
  name: `${cuid()}.${file.extname}`, // server-generated name, ignores clientName
  overwrite: false,                  // never clobber existing files
})

The underlying principles: never trust a client-supplied filename, mint your own identifier for stored files, restrict accepted extensions explicitly, and leave overwrites off unless your logic genuinely needs them.

Affected and patched versions

| Channel | Vulnerable | Patched | |---|---|---| | Stable | up to and including 10.1.1 | 10.1.2 | | Next | 11.0.0-next.111.0.0-next.5 | 11.0.0-next.6 |

The CVE was assigned and the patches published on January 2, 2026, alongside advisory GHSA-gvq6-hvvp-h34h.

Detection and mitigation

To find exposure in your own code, grep for every .move( call on objects returned by request.file() or request.files(). Any call missing a second argument — or where name isn't set to a server-generated value — should be treated as vulnerable and fixed. SAST tools that model taint from HTTP inputs into filesystem writes will flag this if tuned for Node.js.

Layered defenses worth applying:

  • Upgrade now to @adonisjs/bodyparser 10.1.2 or 11.0.0-next.6.
  • Allowlist extensions at the application layer and reject anything unexpected.
  • Isolate the filesystem — run the app under a low-privilege user or container whose write access is scoped only to the upload directory.
  • Least privilege — the web process should never be able to write to /etc, home directories, or application source paths.
  • Audit handlers — review every file.move() lacking an explicit sanitized name.
  • WAF rules — flag multipart filenames containing ../ or ..\.

In production logs, multipart uploads whose reported filename contains ../, the URL-encoded %2e%2e%2f, or null bytes are strong signals of active exploitation.

Takeaway

CVE-2026-21440 is a reminder that an insecure default buried in a widely deployed framework library scales the risk to every app that depends on it. No exotic tooling is required here — a single curl request yields arbitrary file write and, in many real deployments, full server compromise. Auditing your file.move() calls and bumping the package version is a same-day priority.

References: NVD — CVE-2026-21440 · GHSA-gvq6-hvvp-h34h advisory · Ashwesker/Ashwesker-CVE-2026-21440 · k0nnect — PoC and write-up