Quick Answer
Deno 2.9.8 moves npm package-link destination construction behind a shared name check. Local and hoisted installer callers now supply a node_modules directory and a package name or alias. The shared helper validates that name before deriving the destination path. Rejected names produce an InvalidInput error before destination directory creation, destination cleanup, or link creation.
The official 2.9.8 release notes, dated October 21, 2026, include PR #37047, which merged on October 20. The release page was marked Latest when the supplied research checked it on November 2, 2026, KST. That establishes release inclusion at the time of review; it is not a claim about a later release listing.
In Plain English
A package link needs a destination inside a package directory. For an ordinary package name, that destination uses one name component. A scoped name uses the familiar two-part layout, such as @scope/pkg. The change concerns how the installer turns those names into destinations.
Previously, the reviewed callers constructed a destination path and passed that path to the link helper. After the patch, they pass the root directory and the name separately. The helper then checks the name and builds the path itself. This distinction is visible in the merged patch, which changes both libs/npm_installer/local.rs and libs/npm_installer/hoisted.rs.
For context, Deno’s package documentation describes local npm package links configured through the links field in deno.json. That feature requires a node_modules directory. The documentation also says the npm package name must exist in the registry even when a local copy is used. These configuration requirements provide context for local links; the reviewed patch addresses the destination-building boundary within the installer.
Code Evidence
The caller supplies the root and alias
In the old local call, the caller used join_package_name to combine the root directory with remote_alias, then passed the resulting destination to symlink_package_dir. The following exact, shortened excerpt from the merged patch shows the replacement call:
symlink_package_dir(
sys.as_ref(),
&local_registry_package_path,
&self.root_node_modules_path,
remote_alias,
)?;
The source directory remains an argument. The important change is that the destination is represented by two inputs: the root directory and the alias. In the released 2.9.8 source, the shared link helper obtains its destination through this call:
let new_path = package_link_path(node_modules, name)?;
These are source excerpts, not pseudocode or independently executed examples. Together, they show where responsibility moves: callers identify the intended link, while the shared helper checks its name before constructing the destination.

The accepted names have a constrained shape
The released helper accepts an unscoped name without a slash, or a scoped name shaped as @scope/package. Each part must be nonempty. A part cannot be . or .., end with a dot or space, or contain a backslash, colon, or NUL. The helper also rejects .bin, .deno, and node_modules case-insensitively.
These are the rules observed in this helper. They should not be read as a complete description of npm naming policy or every name accepted elsewhere in Deno. Their immediate role here is to decide whether this installer helper may derive a package-link destination from a supplied name.
Rejection happens before destination changes
For an invalid name, the released implementation returns std::io::ErrorKind::InvalidInput and includes the rejected name in its error message. The call to package_link_path occurs before creating the link’s parent directory, removing an existing destination entry, or creating a symlink or junction. That ordering is an observed property of the released source.
The ordering matters because validation is useful only in relation to the operations it guards. Here, an invalid name stops this helper before its destination setup and replacement work. That conclusion is limited to the reviewed helper and its operation sequence; it does not establish that no earlier installer activity has occurred.
The tests check rejection and ordinary links
The added test covers empty names, traversal-like inputs, absolute inputs, malformed scoped names, extra slashes, backslashes, trailing dots or spaces, and reserved names. For each rejected input, it checks for InvalidInput and verifies that an outside marker file still contains its original content. Separate accepted cases check that links named pkg and @scope/pkg resolve to the marker. Both the inputs and assertions appear in the patch and released source.
The released source also retains an existing replacement test. It links pkg to one target, replaces that link with a second target, and checks the new target. Its comment identifies stale directory symlink or junction replacement on Windows as the earlier regression context. This retained test addresses replacement behavior alongside the newly added name checks.
Why It Matters
The editorial inference is that a shared destination-building boundary makes the local and hoisted callers more consistent. Both supply a root and a name to the common link helper, which places the reviewed check where those inputs become a destination. That reduces the opportunity for callers to construct destinations under different assumptions. It is an architectural interpretation of the code change, not a measured reliability result.
There is also a practical diagnostic implication. If this helper reports InvalidInput, the package name or alias supplied for the link is a specific place to investigate. The reviewed material does not establish which real inputs prompted the patch, how often developers encountered them, or whether any particular project will see a new installation failure.
Validation results need attribution. In the PR, the author reports all 49 npm-installer unit tests passing, installer Clippy passing with Rust 1.95.0, and formatting completing successfully. The author also says full workspace lint was not completed. GitHub reports 135 passed checks on the PR. These are reported results from the reviewed material; no tests, builds, or runtime experiments were run for this article’s research.
An Open Question
Should the installer document why this helper enforces these particular filesystem-safe name rules, and should an InvalidInput failure identify the dependency or alias that supplied the rejected name?
The code supplies a generic invalid-name message containing the name, while the reviewed PR does not explain the intended diagnostic context. Documentation explaining the rule choices, together with evidence of how the error reaches users and whether it identifies the supplying dependency or alias, would resolve that question. The present evidence supports describing the validation boundary, but not judging the complete troubleshooting experience.
Scope and Limitations
This article reviews a released npm-installer change. The reviewed PR and release notes do not identify a triggering incident, describe an exploit, or designate the patch as a CVE or security advisory. It should not be presented as a confirmed vulnerability fix.
The tests provide concrete assertions for their listed inputs, an unchanged outside marker, accepted ordinary and scoped links, and retained replacement behavior. They do not prove correctness across every filesystem, platform, or npm metadata edge case. Likewise, this helper’s validation does not establish path safety for every Deno filesystem operation, every dependency-resolution path, or unrelated installers.
The supplied research verifies a merged target commit and release inclusion, but supplies no exact base commit. No base SHA is inferred here. The review date records when the supplied evidence was checked, rather than an independent execution or a guarantee that the release remains the latest.
Sources
- Deno v2.9.8 release notes — release date and inclusion of PR #37047.
- Deno PR #37047 — scope, merge information, and reported validation.
- Merged patch — caller changes, validation, and test additions.
- 5f2241642635008739292d5afca75217de0c469f — verified merged commit.
- Released v2.9.8 local installer source — helper implementation, operation ordering, and regression tests.
- Deno package documentation — local npm links and configuration requirements.
