Deno's --deny-read check does not prevent permission bypass
Description
Deno is a JavaScript, TypeScript, and WebAssembly runtime. In versions prior to 2.5.3 and 2.2.15, 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. Versions 2.5.3 and 2.2.15 fix the issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
denocrates.io | < 2.5.3 | 2.5.3 |
Affected products
1Patches
11ab2268c0bcbfix(fs): improve file stat checks (#30876)
3 files changed · +42 −4
ext/fs/lib.rs+2 −2 modified@@ -166,8 +166,8 @@ deno_core::extension!(deno_fs, op_fs_file_sync_data_async, op_fs_file_sync_sync, op_fs_file_sync_async, - op_fs_file_stat_sync, - op_fs_file_stat_async, + op_fs_file_stat_sync<P>, + op_fs_file_stat_async<P>, op_fs_fchmod_async, op_fs_fchmod_sync, op_fs_fchown_async,
ext/fs/ops.rs+16 −2 modified@@ -1772,13 +1772,20 @@ pub async fn op_fs_file_sync_async( } #[op2(fast)] -pub fn op_fs_file_stat_sync( +pub fn op_fs_file_stat_sync<P: FsPermissions + 'static>( state: &mut OpState, #[smi] rid: ResourceId, #[buffer] stat_out_buf: &mut [u32], ) -> Result<(), FsOpsError> { let file = FileResource::get_file(state, rid).map_err(FsOpsErrorKind::Resource)?; + if let Some(path) = file.maybe_path() { + _ = state.borrow::<P>().check_open( + Cow::Borrowed(path), + OpenAccessKind::Read, + "Deno.FsFile.prototype.statSync()", + )?; + } let stat = file.stat_sync()?; let serializable_stat = SerializableStat::from(stat); serializable_stat.write(stat_out_buf); @@ -1787,12 +1794,19 @@ pub fn op_fs_file_stat_sync( #[op2(async)] #[serde] -pub async fn op_fs_file_stat_async( +pub async fn op_fs_file_stat_async<P: FsPermissions + 'static>( state: Rc<RefCell<OpState>>, #[smi] rid: ResourceId, ) -> Result<SerializableStat, FsOpsError> { let file = FileResource::get_file(&state.borrow(), rid) .map_err(FsOpsErrorKind::Resource)?; + if let Some(path) = file.maybe_path() { + _ = state.borrow().borrow::<P>().check_open( + Cow::Borrowed(path), + OpenAccessKind::Read, + "Deno.FsFile.prototype.stat()", + )?; + } let stat = file.stat_async().await?; Ok(stat.into()) }
tests/unit/stat_test.ts+24 −0 modified@@ -329,3 +329,27 @@ Deno.test( assert(!s.isSocket); }, ); + +Deno.test( + { permissions: { read: false, write: true } }, + async function fsFileStatFailPermissions() { + const testDir = Deno.makeTempDirSync(); + const filename = testDir + "/file.txt"; + using file = await Deno.open(filename, { + read: false, + write: true, + create: true, + }); + + await assertRejects( + () => file.stat(), + Deno.errors.NotCapable, + "Requires read access to", + ); + assertThrows( + () => file.statSync(), + Deno.errors.NotCapable, + "Requires read access to", + ); + }, +);
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-qq26-84mh-26j9ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-61786ghsaADVISORY
- github.com/denoland/deno/commit/1ab2268c0bcbf9b0468e0e36963f77f8c31c73ecghsax_refsource_MISCWEB
- github.com/denoland/deno/pull/30876ghsax_refsource_MISCWEB
- github.com/denoland/deno/releases/tag/v2.2.15ghsax_refsource_MISCWEB
- github.com/denoland/deno/releases/tag/v2.5.3ghsax_refsource_MISCWEB
- github.com/denoland/deno/security/advisories/GHSA-qq26-84mh-26j9ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.