Severity by source
AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
AV:L because the attacker must write to local node_modules; PR:L for local user or supply-chain actor; C:H for arbitrary JSON file exposure; I:N and A:N as no write or availability impact exists.
Primary rating from Vendor (https://github.com/denoland/deno).
CVSS VectorVendor: https://github.com/denoland/deno
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Lifecycle Timeline
2DescriptionCVE.org
Summary
When Deno was run in BYONM mode (nodeModulesDir: "manual"), the module resolver did not validate that a package's resolved entrypoint stayed within its node_modules/<pkg>/ directory. A malicious package.json whose main field contained .. segments was able to resolve to an arbitrary path on disk, and the resolver then read that file without consulting the --allow-read allowlist. This let a require("evil-pkg") call return the contents of a file that a direct Deno.readTextFileSync(...) call would have been blocked from reading.
Details
In BYONM mode, Deno resolved npm packages directly from a user-managed node_modules tree. Resolution of require("pkg") proceeded by reading pkg/package.json, taking the main field, joining it to the package directory, and loading the result as a module.
The path joined from main was not constrained to the package root. A package.json such as:
{ "main": "../../../secret.json" }resolved to node_modules/pkg/../../../secret.json, escaping node_modules entirely. The BYONM permission check accepted any path that contained a node_modules component and did not reject .. traversal, so the resolved path was loaded without a read-permission check.
Because resolution loaded JSON entrypoints by parsing their contents and returning them through require, this exposed the contents of arbitrary .json files reachable by the OS user to the requiring code, even when --allow-read had been narrowed to a specific directory.
The same file accessed via Deno.readTextFileSync was correctly blocked. The bug was that module resolution did not enforce the same read-permission boundary that the filesystem APIs enforced.
Proof of concept
The reporter supplied a self-contained PoC. Layout:
/tmp/deno_byonm_poc/
├── app/
│ ├── deno.json (BYONM enabled)
│ ├── exploit.ts (require("evil-pkg"))
│ └── node_modules/
│ └── evil-pkg/
│ └── package.json (main: "../../../secret.json")
└── secret.json (outside --allow-read scope)Run:
deno run --no-prompt --allow-read=/tmp/deno_byonm_poc/app exploit.tsObserved:
Deno.readTextFileSync("/tmp/deno_byonm_poc/secret.json")- blocked, as
expected.
require("evil-pkg")- returned the parsed contents ofsecret.json,
bypassing the read allowlist.
A control run with BYONM disabled (--no-config) blocked the require call.
Impact
The vulnerability allowed a hostile npm package installed under a BYONM node_modules to read JSON files outside the directories granted via --allow-read, up to the privileges of the OS user running Deno. In practice this exposed configuration and credential files (.env.json, cloud credentials, package lockfiles, etc.) that the user had deliberately excluded from the read scope.
The vulnerability did not grant any capability beyond what the OS user already held, did not affect runs that granted unrestricted --allow-read, and required the user to have installed and then required a hostile package, i.e. an existing supply-chain compromise. The reason it warranted a security advisory rather than a routine bug fix is that Deno's permission model promised that --allow-read=<scope> was a hard boundary *even over untrusted npm code*, and that promise was broken.
Not affected:
- Runs without BYONM (default npm resolution went through a separate code
path that rejected the traversal).
- Runs with full
--allow-read(no boundary to bypass). - Non-JSON entrypoints, in practice -
.js/.cjs/.mjstargets executed
rather than exposing file contents, which already implied attacker code execution within the granted permission set.
Workarounds
Users on unpatched versions could mitigate by:
- Avoiding BYONM mode (
nodeModulesDir: "manual") for projects that depended
on untrusted packages.
- Auditing
package.jsonmainfields innode_modulesfor..segments
before running.
- Granting
--allow-readonly when the read scope already covered every file
the OS user could see (in which case there was no boundary to bypass and no additional exposure).
AnalysisAI
BYONM module resolution in Deno (≤ 2.7.11) allows path traversal via crafted package.json main fields, bypassing the --allow-read permission sandbox that is central to Deno's security model. A malicious npm package installed under a BYONM node_modules tree can cause require() to read and return the parsed contents of arbitrary JSON files outside the directory scope granted by --allow-read, while a direct Deno.readTextFileSync() call to the same path would be correctly blocked. No active exploitation is confirmed (not in CISA KEV), but a self-contained proof-of-concept was supplied by the reporter and is documented in the GitHub Security Advisory; real-world risk is meaningful in supply-chain scenarios where Deno's permission model is the primary isolation boundary.
Technical ContextAI
Deno is a Rust-based JavaScript and TypeScript runtime (CPE: pkg:rust/deno) that implements a fine-grained permission system, including --allow-read to restrict filesystem reads to scoped directories. BYONM mode (nodeModulesDir: "manual" in deno.json) is an opt-in configuration allowing Deno to resolve npm packages from a user-managed node_modules directory rather than Deno's default managed resolution. Under this path, the resolver reads each package's package.json, extracts the main field, and joins it to the package directory to locate the entrypoint. The root cause is CWE-22 (Path Traversal): the BYONM permission checker only verified the presence of a node_modules component in the path string and did not reject or canonicalize .. segments, so a main value of ../../../secret.json escaped the package root entirely. Because JSON entrypoints are parsed and their contents returned through require(), the traversal specifically exposed .json file contents to requiring code - a distinct exposure path from the filesystem APIs, which correctly enforced the allowlist. Non-JSON entrypoints (.js, .cjs, .mjs) caused code execution within the granted permission set rather than this data-exfiltration vector.
RemediationAI
Upgrade to Deno 2.7.12 or later, which patches the BYONM module resolver to constrain resolved entrypoint paths to the package directory and reject .. traversal; this is the vendor-confirmed fix per GHSA-968w-xfqw-vp9q at https://github.com/denoland/deno/security/advisories/GHSA-968w-xfqw-vp9q. For users who cannot immediately upgrade, three workarounds are available with distinct trade-offs: first, disable BYONM mode by removing nodeModulesDir: "manual" from deno.json and switching to Deno's default managed npm resolution, which uses a separate code path that correctly rejects path traversal - this is the most effective workaround but may require restructuring dependency management and is incompatible with projects that depend on BYONM's user-managed tree; second, manually audit all package.json files within node_modules for .. segments in the main field before executing the application, which is operationally burdensome, error-prone at scale, and must be re-performed after any dependency change; third, grant --allow-read without scope restriction only when every file the OS user can access is already acceptable to expose, which eliminates the bypass boundary entirely but surrenders the filesystem isolation that Deno's permission model is designed to provide.
FortiOS and FortiProxy contain an authentication bypass via the Node.js websocket module allowing unauthenticated remote
Eval injection vulnerability in the internals.batch function in lib/batch.js in the bassmaster plugin before 1.5.2 for t
Flowise version 3.0.5 contains a remote code execution vulnerability in the CustomMCP node. The mcpServerConfig paramete
Node.js 8.5.0 before 8.6.0 allows remote attackers to access unintended files, because a change to ".." handling was inc
An issue was discovered in the node-serialize package 0.0.4 for Node.js. Rated critical severity (CVSS 9.8), this vulner
Directory traversal vulnerability in the st module before 0.2.5 for Node.js allows remote attackers to read arbitrary fi
Multiple SQL injection vulnerabilities in the Manage Accounts page in the AccountManagement.asmx service in the Solarwin
The JS-YAML module before 2.0.5 for Node.js parses input without properly considering the unsafe !!js/function tag, whic
Directory traversal vulnerability in lib/app/index.js in Geddy before 13.0.8 for Node.js allows remote attackers to read
Credential-harvesting malware compromised 84 versions of 42 TanStack npm packages on 2026-05-11 via chained GitHub Actio
Eval injection vulnerability in index.js in the syntax-error package before 1.1.1 for Node.js 0.10.x, as used in IBM Rat
The HTTP server in Node.js 0.10.x before 0.10.21 and 0.8.x before 0.8.26 allows remote attackers to cause a denial of se
Same weakness CWE-22 – Path Traversal
View allSame technique Path Traversal
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-38545
GHSA-968w-xfqw-vp9q