Severity by source
AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N
Attacker needs local code execution inside the sandbox (AV:L, PR:L); default case-insensitive APFS makes the bypass low-complexity (AC:L); escaping the permission boundary changes scope (S:C) to read/write protected files (C:H/I:H).
Primary rating from NVD.
CVSS VectorNVD
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N
Lifecycle Timeline
10DescriptionNVD
Summary
Deno's permission system enforces filesystem and execution restrictions by comparing the requested path against the path supplied to --deny-read, --deny-write, --deny-run, or --deny-ffi. On macOS, that comparison was done at the raw-byte level while the APFS filesystem treats different Unicode spellings of the same name as the same file.
That means a program could reach a denied path by spelling it differently than the deny rule. For example, with --deny-read=/secrets/passwörter.txt, a script could still read the file by opening /secrets/passwo\u0308rter.txt (NFD instead of NFC), or /SECRETS/PASSWÖRTER.txt (different case, since default APFS volumes are case-insensitive). Other forms include ligature characters (fi vs fi, ff vs ff, …) and German ß vs ss.
The denied path and the requested path differed at the byte level, so Deno's permission check passed; the kernel then resolved them to the same inode and served the file anyway. The same flaw affected --deny-write, --deny-run, and --deny-ffi, which share the same path-comparison code.
Am I affected?
You are potentially affected if all of the following are true:
- You run Deno on macOS (the issue is specific to APFS path-equivalence
rules; Linux and Windows are not affected by this variant).
- You rely on
--deny-read,--deny-write,--deny-run, or--deny-ffi
as a security boundary against less-trusted code - a dependency, plugin, or attacker-controlled input.
- The protected path contains characters that have alternate Unicode
spellings - most commonly accented characters (é, ñ, ö, …), German ß, or Latin ligatures - or you rely on case-sensitivity on a default APFS volume.
If you only run fully trusted code, or your deny rules cover paths that are pure ASCII with no case-sensitive aliases, you are not exposed to this specific bypass.
Impact
A program running with broad --allow-read (or --allow-write / --allow-run / --allow-ffi) but with --deny-* carve-outs for specific paths could read, write, execute, or load via FFI those denied paths by referring to them through a Unicode- or case-equivalent spelling. The sandbox model on macOS was weaker than the flags suggested.
Workaround
If you cannot upgrade immediately:
- Prefer
--allow-*allowlists over--deny-*denylists. Allow rules match
against the original specifier, so an attacker-supplied alternate spelling will not match a path you didn't explicitly grant.
- Do not rely on case-sensitivity of paths on macOS for security boundaries;
default APFS volumes are case-insensitive.
Fix
On macOS, Deno now normalizes both the deny-rule path and the requested path to NFC and applies Unicode case folding before comparing them. This matches how APFS resolves paths at the inode level, so byte-different but equivalent spellings are now rejected by the same deny rule.
AnalysisAI
Permission-sandbox bypass in the Deno runtime (versions <= 2.7.13) on macOS lets untrusted code reach paths that operators explicitly blocked with --deny-read, --deny-write, --deny-run, or --deny-ffi. Because Deno compared paths byte-for-byte while APFS treats Unicode-equivalent and case-equivalent spellings as the same file, a script granted broad --allow-* but with --deny-* carve-outs can read, write, execute, or FFI-load a protected file by referring to it with an alternate spelling (NFD vs NFC, case folding, ligatures, or German ss vs ß). No public exploit identified at time of analysis, and EPSS is low (0.14%), but the bypass is trivial to perform once an attacker controls the path string.
Technical ContextAI
Deno enforces its capability sandbox in userspace by string-matching a requested filesystem/exec path against the operator-supplied deny rules before issuing the syscall. The root cause is CWE-41 (Improper Resolution of Path Equivalence): the kernel and the userspace check disagree on what 'the same path' means. macOS APFS normalizes filenames and, on default volumes, is case-insensitive, so /secrets/passwörter.txt, its NFD form /secrets/passwo\u0308rter.txt, and /SECRETS/PASSWÖRTER.txt all resolve to one inode - yet they are distinct byte strings to Deno's comparator. The denied string therefore fails to match the requested string, the permission gate opens, and the kernel serves the underlying inode. The CPE pkg:rust/deno reflects that the vulnerable comparison lives in the Rust core permissions code shared by all four --deny-* flags. The flaw is macOS/APFS-specific; the same logic on Linux and Windows is not affected by this variant.
RemediationAI
Primary fix: upgrade Deno to 2.7.14 or later (Vendor-released patch: 2.7.14), where macOS now normalizes both the deny-rule path and the requested path to NFC and applies Unicode case folding before comparing, matching how APFS resolves inodes. If you cannot upgrade immediately, the vendor-recommended workaround is to stop relying on --deny-* denylists for security and instead express the boundary as --allow-* allowlists, because allow rules match against the original specifier so an attacker-supplied alternate spelling will not match a grant you did not explicitly issue (trade-off: you must enumerate every legitimately needed path, which is more restrictive and may break code paths that depended on broad allow plus narrow deny). Additionally, do not treat macOS path case-sensitivity as a security property, since default APFS volumes are case-insensitive. Full guidance is in advisory GHSA-8xpq-cjcf-3wh9 (https://github.com/denoland/deno/security/advisories/GHSA-8xpq-cjcf-3wh9).
Same weakness CWE-41 – Improper Resolution of Path Equivalence
View allSame technique Information Disclosure
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-38547
GHSA-8xpq-cjcf-3wh9