GHSA-8XPQ-CJCF-3WH9
Vulnerability from github – Published: 2026-06-16 19:11 – Updated: 2026-06-16 19:11Summary
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-ffias 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.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.7.13"
},
"package": {
"ecosystem": "crates.io",
"name": "deno"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.7.14"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-49401"
],
"database_specific": {
"cwe_ids": [
"CWE-176",
"CWE-41"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-16T19:11:52Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "## Summary\n\nDeno\u0027s permission system enforces filesystem and execution restrictions by\ncomparing the requested path against the path supplied to `--deny-read`,\n`--deny-write`, `--deny-run`, or `--deny-ffi`. On macOS, that comparison was\ndone at the raw-byte level while the APFS filesystem treats different Unicode\nspellings of the same name as the same file.\n\nThat means a program could reach a denied path by spelling it differently than\nthe deny rule. For example, with `--deny-read=/secrets/passw\u00f6rter.txt`, a\nscript could still read the file by opening `/secrets/passwo\\u0308rter.txt`\n(NFD instead of NFC), or `/SECRETS/PASSW\u00d6RTER.txt` (different case, since\ndefault APFS volumes are case-insensitive). Other forms include ligature\ncharacters (`\ufb01` vs `fi`, `\ufb00` vs `ff`, \u2026) and German `\u00df` vs `ss`.\n\nThe denied path and the requested path differed at the byte level, so Deno\u0027s\npermission check passed; the kernel then resolved them to the same inode and\nserved the file anyway. The same flaw affected `--deny-write`, `--deny-run`,\nand `--deny-ffi`, which share the same path-comparison code.\n\n## Am I affected?\n\nYou are potentially affected if **all** of the following are true:\n\n1. You run Deno on **macOS** (the issue is specific to APFS path-equivalence\n rules; Linux and Windows are not affected by this variant).\n2. You rely on `--deny-read`, `--deny-write`, `--deny-run`, or `--deny-ffi`\n as a security boundary against less-trusted code \u2014 a dependency, plugin,\n or attacker-controlled input.\n3. The protected path contains characters that have alternate Unicode\n spellings \u2014 most commonly accented characters (`\u00e9`, `\u00f1`, `\u00f6`, \u2026), German\n `\u00df`, or Latin ligatures \u2014 or you rely on case-sensitivity on a default\n APFS volume.\n\nIf you only run fully trusted code, or your deny rules cover paths that are\npure ASCII with no case-sensitive aliases, you are not exposed to this\nspecific bypass.\n\n## Impact\n\nA program running with broad `--allow-read` (or `--allow-write` /\n`--allow-run` / `--allow-ffi`) but with `--deny-*` carve-outs for specific\npaths could read, write, execute, or load via FFI those denied paths by\nreferring to them through a Unicode- or case-equivalent spelling. The sandbox\nmodel on macOS was weaker than the flags suggested.\n\n## Workaround\n\nIf you cannot upgrade immediately:\n\n- Prefer `--allow-*` allowlists over `--deny-*` denylists. Allow rules match\n against the original specifier, so an attacker-supplied alternate spelling\n will not match a path you didn\u0027t explicitly grant.\n- Do not rely on case-sensitivity of paths on macOS for security boundaries;\n default APFS volumes are case-insensitive.\n\n## Fix\n\nOn macOS, Deno now normalizes both the deny-rule path and the requested path\nto NFC and applies Unicode case folding before comparing them. This matches\nhow APFS resolves paths at the inode level, so byte-different but equivalent\nspellings are now rejected by the same deny rule.",
"id": "GHSA-8xpq-cjcf-3wh9",
"modified": "2026-06-16T19:11:52Z",
"published": "2026-06-16T19:11:52Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/denoland/deno/security/advisories/GHSA-8xpq-cjcf-3wh9"
},
{
"type": "PACKAGE",
"url": "https://github.com/denoland/deno"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "Deno: Permission Bypass via Unicode Normalization Mismatch on macOS (APFS)"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.