-
Notifications
You must be signed in to change notification settings - Fork 5.9k
Description
Version: Deno 2.7.4
node:fs throws raw NotCapable instead of Node-compatible error when Deno permissions deny access.
When using node:fs with restricted --allow-write permissions, filesystem operations throw Deno's raw NotCapable error instead of a Node.js-compatible error with .code, .errno, and .syscall properties.
This breaks any Node.js library that catches fs errors by .code property - notably Emscripten's NODEFS, which causes Pyodide (Python in WebAssembly) to crash instead of raising Python's PermissionError.
Repro
// deno run --allow-read --allow-write=/tmp/allowed repro.ts
import { writeFileSync, openSync, mkdirSync } from 'node:fs';
try {
writeFileSync('/tmp/blocked.txt', 'test');
} catch (e: any) {
console.log('name:', e.name); // "NotCapable"
console.log('code:', e.code); // undefined
console.log('errno:', e.errno); // undefined
console.log('syscall:', e.syscall); // undefined
}Actual: NotCapable with no .code, .errno, or .syscall properties.
Expected: A Node.js-compatible error with .code (e.g. EACCES or EPERM), .errno, and .syscall set, matching how Node.js surfaces permission errors from filesystem operations.
Affects writeFileSync, openSync, mkdirSync, and likely all other node:fs operations that go through the error conversion functions.
Root Cause
In ext/node/polyfills/internal/errors.ts, the two error conversion functions used by node:fs (denoErrorToNodeError and denoWriteFileErrorToNodeError) rely on extractOsErrorNumberFromErrorMessage, which regex-matches (os error N) from the error message. This works for OS-level errors like PermissionDenied (which includes (os error 13)), but NotCapable is a Deno sandbox error with no OS error number in its message, so the regex returns undefined and the raw Deno error is returned unconverted.
The DNS error handler already handles NotCapable, mapping it to EPERM. The fs error functions are missing the same treatment.
Suggested Fix
Add a NotCapable check to denoErrorToNodeError and denoWriteFileErrorToNodeError, after the existing BadResource check:
if (ObjectPrototypeIsPrototypeOf(Deno.errors.NotCapable.prototype, e)) {
return uvException({ errno: codeMap.get("EACCES"), ...ctx });
}