Skip to content

BUG: node:fs operations throw raw NotCapable error instead of EACCES #32583

@baaabaaayaaagaaa

Description

@baaabaaayaaagaaa

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 });
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions