Skip to content

fix(stage): sanitize the download filename from staged manifest fields - #10070

Open
ManoharPaturi wants to merge 1 commit into
npm:latestfrom
ManoharPaturi:stage-download-path
Open

ManoharPaturi wants to merge 1 commit into
npm:latestfrom
ManoharPaturi:stage-download-path

Conversation

@ManoharPaturi

Copy link
Copy Markdown

Problem

npm stage download <stage-id> builds the output filename from the staged tarball's manifest:

const safeName = pkgContents.name.replace('@', '').replace('/', '-')
const filename = `${safeName}-${pkgContents.version}-${stageId}.tgz`

The name and version are read out of the tarball's own package.json (via #readManifestFromTarball), so they are registry controlled. The sanitization only removed the first slash of the name and left the version untouched, so a crafted manifest could push path separators into the filename. With a version such as ../../../outside, resolve(process.cwd(), filename) walks out of the working directory and the tarball is written to an arbitrary location, for example:

$ npm stage download <stage-id>   # manifest version: ../../../outside
# before: writes ../outside-<stage-id>.tgz outside the cwd
# after:  writes ./evil-..-..-..-outside-<stage-id>.tgz inside the cwd

This is the same class of defect fixed for the linked install strategy store key in #9758, where an untrusted package.json version became a path segment.

Solution

Build the filename from the raw name and version and strip every slash and backslash, exactly like libnpmpack already does for the same manifest fields (workspaces/libnpmpack/lib/index.js). With no separators surviving, neither field can move the destination outside the working directory.

Test Evidence

Added a test in test/lib/commands/stage/download.js that stages a tarball whose manifest carries a traversal version and asserts the file is written inside the cwd and that no file escapes it. The test fails on the previous code with tarball does not escape the cwd and passes with this change. Full file is green: npx tap test/lib/commands/stage/download.js.

References

The name and version used to build the downloaded tarball filename come
from the staged tarball's own package.json, so they are registry
controlled. Only the first slash was stripped from the name and the
version was left untouched, so a crafted manifest could push path
separators into the filename and write the tarball outside the current
working directory.

Sanitize the full filename the same way libnpmpack does for the same
manifest fields, stripping every slash and backslash, so neither field
can contribute path separators.
@ManoharPaturi
ManoharPaturi requested a review from a team as a code owner October 4, 2026 02:23
Copilot AI balanced review requested due to automatic review settings October 4, 2026 02:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants